diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..4c5105fe68 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,181 @@ +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: Africa/Abidjan + open-pull-requests-limit: 99 + ignore: + - dependency-name: com.google.code.gson:gson + versions: + - "> 2.8.6" + - dependency-name: io.fabric8:fabric8-maven-plugin + versions: + - "> 4.4.0" + - dependency-name: javax.servlet:javax.servlet-api + versions: + - "> 3.0.1" + - dependency-name: org.acegisecurity:acegi-security + versions: + - "> 0.8.2_patched" + - dependency-name: org.activiti:activiti-engine + versions: + - "> 5.23.0" + - dependency-name: org.activiti:activiti-engine + versions: + - ">= 7.1.a, < 7.2" + - dependency-name: org.activiti:activiti-spring + versions: + - "> 5.23.0" + - dependency-name: org.activiti:activiti-spring + versions: + - ">= 7.1.a, < 7.2" + - dependency-name: org.apache.camel:camel-activemq + versions: + - "> 3.7.1" + - dependency-name: org.apache.camel:camel-amqp + versions: + - "> 3.7.1" + - dependency-name: org.apache.camel:camel-direct + versions: + - "> 3.7.1" + - dependency-name: org.apache.camel:camel-directvm + versions: + - "> 3.7.1" + - dependency-name: org.apache.camel:camel-jackson + versions: + - "> 3.7.1" + - dependency-name: org.apache.camel:camel-mock + versions: + - "> 3.7.1" + - dependency-name: org.apache.camel:camel-spring + versions: + - "> 3.7.1" + - dependency-name: org.apache.chemistry.opencmis:chemistry-opencmis-client-impl + versions: + - "> 1.0.0" + - dependency-name: org.apache.chemistry.opencmis:chemistry-opencmis-commons-impl + versions: + - "> 1.0.0" + - dependency-name: org.apache.chemistry.opencmis:chemistry-opencmis-server-bindings + versions: + - "> 1.0.0" + - dependency-name: org.apache.chemistry.opencmis:chemistry-opencmis-test-tck + versions: + - "> 1.0.0" + - dependency-name: org.freemarker:freemarker + versions: + - "> 2.3.20-alfresco-patched-20200421" + - dependency-name: org.keycloak:keycloak-adapter-core + versions: + - "> 12.0.2" + - dependency-name: org.keycloak:keycloak-adapter-spi + versions: + - "> 12.0.2" + - dependency-name: org.keycloak:keycloak-authz-client + versions: + - "> 12.0.2" + - dependency-name: org.keycloak:keycloak-common + versions: + - "> 12.0.2" + - dependency-name: org.keycloak:keycloak-core + versions: + - "> 12.0.2" + - dependency-name: org.keycloak:keycloak-servlet-adapter-spi + versions: + - "> 12.0.2" + - dependency-name: org.eclipse.jetty:jetty-server + versions: + - 9.4.38.v20210224 + - dependency-name: org.alfresco.tas:cmis + versions: + - "1.28" + - dependency-name: org.springframework:spring-webmvc + versions: + - 5.3.4 + - 5.3.5 + - dependency-name: org.springframework:spring-web + versions: + - 5.3.4 + - 5.3.5 + - dependency-name: org.springframework:spring-tx + versions: + - 5.3.4 + - 5.3.5 + - dependency-name: org.springframework:spring-orm + versions: + - 5.3.4 + - 5.3.5 + - dependency-name: org.springframework:spring-test + versions: + - 5.3.4 + - 5.3.5 + - dependency-name: org.springframework:spring-jms + versions: + - 5.3.4 + - 5.3.5 + - dependency-name: org.springframework:spring-jdbc + versions: + - 5.3.4 + - 5.3.5 + - dependency-name: org.springframework:spring-expression + versions: + - 5.3.4 + - 5.3.5 + - dependency-name: org.springframework:spring-core + versions: + - 5.3.4 + - 5.3.5 + - dependency-name: org.springframework:spring-context-support + versions: + - 5.3.4 + - 5.3.5 + - dependency-name: org.springframework:spring-context + versions: + - 5.3.4 + - 5.3.5 + - dependency-name: org.springframework:spring-beans + versions: + - 5.3.4 + - 5.3.5 + - dependency-name: org.springframework:spring-aop + versions: + - 5.3.4 + - 5.3.5 + - dependency-name: org.alfresco.tas:restapi + versions: + - "1.55" + - dependency-name: org.eclipse.jetty:jetty-security + versions: + - 11.0.1 + - dependency-name: org.alfresco.aos-module:alfresco-vti-bin + versions: + - 1.4.0-M1 + - dependency-name: org.alfresco.aos-module:alfresco-aos-module-distributionzip + versions: + - 1.4.0-M1 + - dependency-name: org.alfresco.aos-module:alfresco-aos-module + versions: + - 1.4.0-M1 + - dependency-name: org.alfresco.surf:spring-webscripts-api + versions: + - "8.16" + - dependency-name: org.alfresco.surf:spring-webscripts:tests + versions: + - "8.16" + - dependency-name: org.alfresco.surf:spring-webscripts + versions: + - "8.16" + - dependency-name: org.alfresco.surf:spring-surf-core-configservice + versions: + - "8.16" + registries: + - maven-repository-artifacts-alfresco-com-nexus-content-groups-int diff --git a/.travis.yml b/.travis.yml index fab7f4a49e..5ab883176c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -55,7 +55,7 @@ jobs: before_script: - docker run -d -p 5433:5432 -e POSTGRES_PASSWORD=alfresco -e POSTGRES_USER=alfresco -e POSTGRES_DB=alfresco postgres:13.3 postgres -c 'max_connections=300' - docker run -d -p 61616:61616 -p 5672:5672 alfresco/alfresco-activemq:5.16.1 - - docker run -d -p 8090:8090 -e JAVA_OPTS=" -Xms256m -Xmx256m" alfresco/alfresco-transform-core-aio:2.5.2 + - docker run -d -p 8090:8090 -e JAVA_OPTS=" -Xms256m -Xmx256m" alfresco/alfresco-transform-core-aio:2.5.3 script: travis_wait 20 mvn -B test -pl repository -Dtest=AppContext01TestSuite -Ddb.driver=org.postgresql.Driver -Ddb.name=alfresco -Ddb.url=jdbc:postgresql://localhost:5433/alfresco -Ddb.username=alfresco -Ddb.password=alfresco - name: "Repository - AppContext02TestSuite" @@ -70,7 +70,7 @@ jobs: before_script: - docker run -d -p 5433:5432 -e POSTGRES_PASSWORD=alfresco -e POSTGRES_USER=alfresco -e POSTGRES_DB=alfresco postgres:13.3 postgres -c 'max_connections=300' - docker run -d -p 61616:61616 -p 5672:5672 alfresco/alfresco-activemq:5.16.1 - - docker run -d -p 8090:8090 -e JAVA_OPTS=" -Xms256m -Xmx256m" alfresco/alfresco-transform-core-aio:2.5.2 + - docker run -d -p 8090:8090 -e JAVA_OPTS=" -Xms256m -Xmx256m" alfresco/alfresco-transform-core-aio:2.5.3 script: travis_wait 20 mvn -B test -pl repository -Dtest=AppContext03TestSuite -Ddb.driver=org.postgresql.Driver -Ddb.name=alfresco -Ddb.url=jdbc:postgresql://localhost:5433/alfresco -Ddb.username=alfresco -Ddb.password=alfresco - name: "Repository - AppContext04TestSuite" @@ -78,7 +78,7 @@ jobs: before_script: - docker run -d -p 5433:5432 -e POSTGRES_PASSWORD=alfresco -e POSTGRES_USER=alfresco -e POSTGRES_DB=alfresco postgres:13.3 postgres -c 'max_connections=300' - docker run -d -p 61616:61616 -p 5672:5672 alfresco/alfresco-activemq:5.16.1 - - docker run -d -p 8090:8090 -e JAVA_OPTS=" -Xms256m -Xmx256m" alfresco/alfresco-transform-core-aio:2.5.2 + - docker run -d -p 8090:8090 -e JAVA_OPTS=" -Xms256m -Xmx256m" alfresco/alfresco-transform-core-aio:2.5.3 script: travis_wait 20 mvn -B test -pl repository -Dtest=AppContext04TestSuite -Ddb.driver=org.postgresql.Driver -Ddb.name=alfresco -Ddb.url=jdbc:postgresql://localhost:5433/alfresco -Ddb.username=alfresco -Ddb.password=alfresco - name: "Repository - AppContext05TestSuite" @@ -97,7 +97,7 @@ jobs: before_script: - docker run -d -p 5433:5432 -e POSTGRES_PASSWORD=alfresco -e POSTGRES_USER=alfresco -e POSTGRES_DB=alfresco postgres:13.3 postgres -c 'max_connections=300' - docker run -d -p 61616:61616 -p 5672:5672 alfresco/alfresco-activemq:5.16.1 - - docker run -d -p 8090:8090 -e JAVA_OPTS=" -Xms256m -Xmx256m" alfresco/alfresco-transform-core-aio:2.5.2 + - docker run -d -p 8090:8090 -e JAVA_OPTS=" -Xms256m -Xmx256m" alfresco/alfresco-transform-core-aio:2.5.3 script: travis_wait 20 mvn -B test -pl repository -Dtest=AppContext06TestSuite -Ddb.driver=org.postgresql.Driver -Ddb.name=alfresco -Ddb.url=jdbc:postgresql://localhost:5433/alfresco -Ddb.username=alfresco -Ddb.password=alfresco - name: "Repository - AppContextExtraTestSuite" @@ -105,7 +105,7 @@ jobs: before_script: - docker run -d -p 5433:5432 -e POSTGRES_PASSWORD=alfresco -e POSTGRES_USER=alfresco -e POSTGRES_DB=alfresco postgres:13.3 postgres -c 'max_connections=300' - docker run -d -p 61616:61616 -p 5672:5672 alfresco/alfresco-activemq:5.16.1 - - docker run -d -p 8090:8090 -e JAVA_OPTS=" -Xms256m -Xmx256m" alfresco/alfresco-transform-core-aio:2.5.2 + - docker run -d -p 8090:8090 -e JAVA_OPTS=" -Xms256m -Xmx256m" alfresco/alfresco-transform-core-aio:2.5.3 script: travis_wait 20 mvn -B test -pl repository -Dtest=AppContextExtraTestSuite -Ddb.driver=org.postgresql.Driver -Ddb.name=alfresco -Ddb.url=jdbc:postgresql://localhost:5433/alfresco -Ddb.username=alfresco -Ddb.password=alfresco - name: "Repository - MiscContextTestSuite" @@ -113,7 +113,7 @@ jobs: before_script: - docker run -d -p 5433:5432 -e POSTGRES_PASSWORD=alfresco -e POSTGRES_USER=alfresco -e POSTGRES_DB=alfresco postgres:13.3 postgres -c 'max_connections=300' - docker run -d -p 61616:61616 -p 5672:5672 alfresco/alfresco-activemq:5.16.1 - - docker run -d -p 8090:8090 -e JAVA_OPTS=" -Xms256m -Xmx256m" alfresco/alfresco-transform-core-aio:2.5.2 + - docker run -d -p 8090:8090 -e JAVA_OPTS=" -Xms256m -Xmx256m" alfresco/alfresco-transform-core-aio:2.5.3 script: travis_wait 20 mvn -B test -pl repository -Dtest=MiscContextTestSuite -Ddb.driver=org.postgresql.Driver -Ddb.name=alfresco -Ddb.url=jdbc:postgresql://localhost:5433/alfresco -Ddb.username=alfresco -Ddb.password=alfresco - name: "Repository - SearchTestSuite" @@ -145,7 +145,7 @@ jobs: script: travis_wait 20 mvn -B test -pl repository -Dtest=AllDBTestsTestSuite -Ddb.name=alfresco -Ddb.url=jdbc:mariadb://localhost:3307/alfresco?useUnicode=yes\&characterEncoding=UTF-8 -Ddb.username=alfresco -Ddb.password=alfresco -Ddb.driver=org.mariadb.jdbc.Driver - name: "Repository - MariaDB 10.6 tests" - if: (branch =~ /(release\/.*$|master)/ AND commit_message !~ /\[skip db\]/ AND type != pull_request) OR commit_message =~ /\[db\]/ + if: (branch =~ /(release\/.*$|master)/ AND commit_message !~ /\[skip db\]/ AND type != pull_request) OR commit_message =~ /\[db\]/ OR commit_message =~ /\[latest db\]/ before_script: - docker run -d -p 3307:3306 --name mariadb -e MYSQL_ROOT_PASSWORD=alfresco -e MYSQL_USER=alfresco -e MYSQL_DATABASE=alfresco -e MYSQL_PASSWORD=alfresco mariadb:10.6 --transaction-isolation=READ-COMMITTED --max-connections=300 --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci - docker run -d -p 61616:61616 -p 5672:5672 alfresco/alfresco-activemq:5.16.1 @@ -159,7 +159,7 @@ jobs: script: travis_wait 20 mvn -B test -pl repository -Dtest=AllDBTestsTestSuite -Ddb.driver=com.mysql.jdbc.Driver -Ddb.name=alfresco -Ddb.url=jdbc:mysql://localhost:3307/alfresco -Ddb.username=alfresco -Ddb.password=alfresco - name: "Repository - MySQL 8 tests" - if: (branch =~ /(release\/.*$|master)/ AND commit_message !~ /\[skip db\]/ AND type != pull_request) OR commit_message =~ /\[db\]/ + if: (branch =~ /(release\/.*$|master)/ AND commit_message !~ /\[skip db\]/ AND type != pull_request) OR commit_message =~ /\[db\]/ OR commit_message =~ /\[latest db\]/ before_script: - docker run -d -p 3307:3306 -e MYSQL_ROOT_PASSWORD=alfresco -e MYSQL_USER=alfresco -e MYSQL_DATABASE=alfresco -e MYSQL_PASSWORD=alfresco mysql:8 --transaction-isolation='READ-COMMITTED' - docker run -d -p 61616:61616 -p 5672:5672 alfresco/alfresco-activemq:5.16.1 @@ -210,7 +210,7 @@ jobs: - name: "Repository - PostgreSQL 13.3 tests" # We only run DB tests on the latest version of PostgreSQL on feature branches - if: commit_message !~ /\[skip db\]/ + if: commit_message !~ /\[skip db\]/ OR commit_message =~ /\[latest db\]/ before_script: - docker run -d -p 5433:5432 -e POSTGRES_PASSWORD=alfresco -e POSTGRES_USER=alfresco -e POSTGRES_DB=alfresco postgres:13.3 postgres -c 'max_connections=300' - docker run -d -p 61616:61616 -p 5672:5672 alfresco/alfresco-activemq:5.16.1 @@ -234,7 +234,7 @@ jobs: before_script: - docker run -d -p 5433:5432 -e POSTGRES_PASSWORD=alfresco -e POSTGRES_USER=alfresco -e POSTGRES_DB=alfresco postgres:13.3 postgres -c 'max_connections=300' - docker run -d -p 61616:61616 -p 5672:5672 alfresco/alfresco-activemq:5.16.1 - - docker run -d -p 8090:8090 -e JAVA_OPTS=" -Xms256m -Xmx256m" alfresco/alfresco-transform-core-aio:2.5.2 + - docker run -d -p 8090:8090 -e JAVA_OPTS=" -Xms256m -Xmx256m" alfresco/alfresco-transform-core-aio:2.5.3 script: travis_wait 20 mvn -B test -pl remote-api -Dtest=AppContext02TestSuite -Ddb.driver=org.postgresql.Driver -Ddb.name=alfresco -Ddb.url=jdbc:postgresql://localhost:5433/alfresco -Ddb.username=alfresco -Ddb.password=alfresco - name: "Remote-api - AppContext03TestSuite" @@ -242,7 +242,7 @@ jobs: before_script: - docker run -d -p 5433:5432 -e POSTGRES_PASSWORD=alfresco -e POSTGRES_USER=alfresco -e POSTGRES_DB=alfresco postgres:13.3 postgres -c 'max_connections=300' - docker run -d -p 61616:61616 -p 5672:5672 alfresco/alfresco-activemq:5.16.1 - - docker run -d -p 8090:8090 -e JAVA_OPTS=" -Xms256m -Xmx256m" alfresco/alfresco-transform-core-aio:2.5.2 + - docker run -d -p 8090:8090 -e JAVA_OPTS=" -Xms256m -Xmx256m" alfresco/alfresco-transform-core-aio:2.5.3 script: travis_wait 20 mvn -B test -pl remote-api -Dtest=AppContext03TestSuite -Ddb.driver=org.postgresql.Driver -Ddb.name=alfresco -Ddb.url=jdbc:postgresql://localhost:5433/alfresco -Ddb.username=alfresco -Ddb.password=alfresco - name: "Remote-api - AppContext04TestSuite" @@ -250,7 +250,7 @@ jobs: before_script: - docker run -d -p 5433:5432 -e POSTGRES_PASSWORD=alfresco -e POSTGRES_USER=alfresco -e POSTGRES_DB=alfresco postgres:13.3 postgres -c 'max_connections=300' - docker run -d -p 61616:61616 -p 5672:5672 alfresco/alfresco-activemq:5.16.1 - - docker run -d -p 8090:8090 -e JAVA_OPTS=" -Xms256m -Xmx256m" alfresco/alfresco-transform-core-aio:2.5.2 + - docker run -d -p 8090:8090 -e JAVA_OPTS=" -Xms256m -Xmx256m" alfresco/alfresco-transform-core-aio:2.5.3 script: travis_wait 20 mvn -B test -pl remote-api -Dtest=AppContext04TestSuite -Ddb.driver=org.postgresql.Driver -Ddb.name=alfresco -Ddb.url=jdbc:postgresql://localhost:5433/alfresco -Ddb.username=alfresco -Ddb.password=alfresco - name: "Remote-api - AppContextExtraTestSuite" diff --git a/amps/ags/pom.xml b/amps/ags/pom.xml index 900d3abd42..61bf76c197 100644 --- a/amps/ags/pom.xml +++ b/amps/ags/pom.xml @@ -7,7 +7,7 @@ org.alfresco alfresco-community-repo-amps - 11.94-SNAPSHOT + 14.7-SNAPSHOT diff --git a/amps/ags/rm-automation/pom.xml b/amps/ags/rm-automation/pom.xml index 6a0f22a16c..6cdbbd76bf 100644 --- a/amps/ags/rm-automation/pom.xml +++ b/amps/ags/rm-automation/pom.xml @@ -7,7 +7,7 @@ org.alfresco alfresco-governance-services-community-parent - 11.94-SNAPSHOT + 14.7-SNAPSHOT diff --git a/amps/ags/rm-automation/rm-automation-community-rest-api/pom.xml b/amps/ags/rm-automation/rm-automation-community-rest-api/pom.xml index a1cbc8779c..c34fb1464f 100644 --- a/amps/ags/rm-automation/rm-automation-community-rest-api/pom.xml +++ b/amps/ags/rm-automation/rm-automation-community-rest-api/pom.xml @@ -7,7 +7,7 @@ org.alfresco alfresco-governance-services-automation-community-repo - 11.94-SNAPSHOT + 14.7-SNAPSHOT diff --git a/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/record/RecordProperties.java b/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/record/RecordProperties.java index 4616e7d474..b9fda4f696 100644 --- a/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/record/RecordProperties.java +++ b/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/record/RecordProperties.java @@ -55,6 +55,7 @@ import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanCo import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentFields.PROPERTIES_RECORD_SEARCH_DISPOSITION_ACTION_AS_OF; import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentFields.PROPERTIES_RECORD_SEARCH_DISPOSITION_ACTION_NAME; import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentFields.PROPERTIES_RECORD_SEARCH_DISPOSITION_AUTHORITY; +import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentFields.PROPERTIES_RECORD_SEARCH_DISPOSITION_EVENTS; import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentFields.PROPERTIES_RECORD_SEARCH_DISPOSITION_EVENTS_ELIGIBLE; import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentFields.PROPERTIES_RECORD_SEARCH_DISPOSITION_INSTRUCTIONS; import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentFields.PROPERTIES_RECORD_SEARCH_DISPOSITION_PERIOD; @@ -248,6 +249,9 @@ public class RecordProperties extends TestModel @JsonProperty (PROPERTIES_RECORD_SEARCH_DISPOSITION_EVENTS_ELIGIBLE) private Boolean recordSearchDispositionEventsEligible; + @JsonProperty (PROPERTIES_RECORD_SEARCH_DISPOSITION_EVENTS) + private List recordSearchDispositionEvents; + @JsonProperty (PROPERTIES_RECORD_SEARCH_DISPOSITION_INSTRUCTIONS) private String recordSearchDispositionInstructions; diff --git a/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/recordcategory/RecordCategoryChildProperties.java b/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/recordcategory/RecordCategoryChildProperties.java index 9494bf37c9..c3b28748ec 100644 --- a/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/recordcategory/RecordCategoryChildProperties.java +++ b/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/recordcategory/RecordCategoryChildProperties.java @@ -47,6 +47,7 @@ import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanCo import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentFields.PROPERTIES_VITAL_RECORD_INDICATOR; import java.util.Date; +import java.util.List; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonSerialize; @@ -134,7 +135,7 @@ public class RecordCategoryChildProperties extends TestModel private String recordSearchDispositionInstructions; @JsonProperty (PROPERTIES_RECORD_SEARCH_DISPOSITION_EVENTS) - private Boolean recordSearchDispositionEvents; + private List recordSearchDispositionEvents; @JsonProperty (PROPERTIES_OWNER) private Owner owner; diff --git a/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/v0/ExportAPI.java b/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/v0/ExportAPI.java new file mode 100644 index 0000000000..12d322fdb2 --- /dev/null +++ b/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/v0/ExportAPI.java @@ -0,0 +1,103 @@ +/* + * #%L + * Alfresco Records Management Module + * %% + * 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 . + * #L% + */ +package org.alfresco.rest.v0; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import org.alfresco.rest.core.v0.BaseAPI; +import org.apache.http.HttpResponse; +import org.json.JSONArray; +import org.json.JSONObject; +import org.springframework.stereotype.Component; + +/** + * Methods to make API requests using v0 API for Exporting Items + * + * @author Shubham Jain + * @since 7.1.0 + */ + +@Component +public class ExportAPI extends BaseAPI +{ + /** + * The URI to export an item + */ + private static final String EXPORT_API = "{0}rma/admin/export"; + + /** + * Export a single Record/Record Folder/Record Category using V0 Export API + * + * @param user User performing the export + * @param password User's Password + * @param expectedStatusCode Expected Response Code + * @param nodeID ID of the Node(Record/RecordFolder) to be exported + * @return HTTP Response + */ + public HttpResponse exportRMNode(String user, String password, int expectedStatusCode, String nodeID) + { + return export(user, password, expectedStatusCode, Collections.singletonList(getNodeRefSpacesStore() + nodeID)); + } + + /** + * Export a list of nodes using V0 Export API + * + * @param user User performing the export + * @param password User's Password + * @param expectedStatusCode Expected Response Code + * @param nodeIDList List of the nodes to be exported + * @return HTTP Response + */ + public HttpResponse exportRMNodes(String user, String password, int expectedStatusCode, List nodeIDList) + { + + List nodeRefs = + nodeIDList.stream().map(nodeID -> getNodeRefSpacesStore() + nodeID).collect(Collectors.toList()); + + return export(user, password, expectedStatusCode, nodeRefs); + } + + /** + * Export API function to perform Export Operation on items with given noderefs using V0 Export Rest API + * + * @param user User performing the export + * @param password User's Password + * @param expectedStatusCode Expected Response Code + * @param nodeRefs list of the noderefs for the items to be exported + * @return Rest API Post Request + */ + public HttpResponse export(String user, String password, int expectedStatusCode, List nodeRefs) + { + final JSONObject requestParams = new JSONObject(); + + requestParams.put("nodeRefs", new JSONArray(nodeRefs)); + + return doPostJsonRequest(user, password, expectedStatusCode, requestParams, EXPORT_API); + } +} diff --git a/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/base/BaseRMRestTest.java b/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/base/BaseRMRestTest.java index 31ffe7a5ee..83e9bdf0c4 100644 --- a/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/base/BaseRMRestTest.java +++ b/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/base/BaseRMRestTest.java @@ -619,11 +619,27 @@ public class BaseRMRestTest extends RestTest * @return */ public List searchForContentAsUser(UserModel user, String term) + { + String query = "cm:name:*" + term + "*"; + return searchForContentAsUser(user,query,"afts"); + } + + /** + * Returns search results for the given search term + * + * @param user + * @param term + * @param query language + * @return + * @throws Exception + */ + public List searchForContentAsUser(UserModel user, String q, String queryLanguage) { getRestAPIFactory().getRmRestWrapper().authenticateUser(user); RestRequestQueryModel queryReq = new RestRequestQueryModel(); SearchRequest query = new SearchRequest(queryReq); - queryReq.setQuery("cm:name:*" + term + "*"); + queryReq.setQuery(q); + queryReq.setLanguage(queryLanguage); List names = new ArrayList<>(); // wait for solr indexing diff --git a/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/hold/PreventActionsOnFrozenContentTests.java b/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/hold/PreventActionsOnFrozenContentTests.java index 62926a3930..f36627241e 100644 --- a/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/hold/PreventActionsOnFrozenContentTests.java +++ b/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/hold/PreventActionsOnFrozenContentTests.java @@ -67,7 +67,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; - +import static org.apache.commons.httpclient.HttpStatus.SC_INTERNAL_SERVER_ERROR; /** * API tests to check actions on frozen content * @@ -309,11 +309,11 @@ public class PreventActionsOnFrozenContentTests extends BaseRMRestTest STEP("Execute the retain action"); rmRolesAndActionsAPI.executeAction(getAdminUser().getUsername(), getAdminUser().getPassword(), record.getName(), - RM_ACTIONS.END_RETENTION); + RM_ACTIONS.END_RETENTION, null, SC_INTERNAL_SERVER_ERROR); STEP("Check the record search disposition properties"); Record recordUpdated = getRestAPIFactory().getRecordsAPI().getRecord(record.getId()); - assertTrue(recordUpdated.getProperties().getRecordSearchDispositionActionName().contains(RM_ACTIONS.DESTROY.getAction())); + assertTrue(recordUpdated.getProperties().getRecordSearchDispositionActionName().contains(RM_ACTIONS.END_RETENTION.getAction())); assertTrue(recordUpdated.getProperties().getRecordSearchDispositionPeriod().contains("immediately")); } diff --git a/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/recordcategories/MoveRecCategoriesWithRSTests.java b/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/recordcategories/MoveRecCategoriesWithRSTests.java new file mode 100644 index 0000000000..6f904b527d --- /dev/null +++ b/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/recordcategories/MoveRecCategoriesWithRSTests.java @@ -0,0 +1,268 @@ +/*- + * #%L + * Alfresco Records Management Module + * %% + * 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 . + * #L% + */ +package org.alfresco.rest.rm.community.recordcategories; + +import static org.alfresco.rest.rm.community.model.recordcategory.RetentionPeriodProperty.CREATED_DATE; +import static org.alfresco.rest.rm.community.model.recordcategory.RetentionPeriodProperty.CUT_OFF_DATE; +import static org.alfresco.rest.rm.community.model.recordcategory.RetentionPeriodProperty.DATE_FILED; +import static org.alfresco.rest.rm.community.utils.CoreUtil.createBodyForMoveCopy; +import static org.alfresco.rest.rm.community.utils.CoreUtil.toContentModel; +import static org.alfresco.utility.data.RandomData.getRandomName; +import static org.alfresco.utility.report.log.Step.STEP; +import static org.springframework.http.HttpStatus.OK; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +import org.alfresco.rest.core.v0.BaseAPI.RM_ACTIONS; +import org.alfresco.rest.core.v0.RMEvents; +import org.alfresco.rest.rm.community.base.BaseRMRestTest; +import org.alfresco.rest.rm.community.model.record.Record; +import org.alfresco.rest.rm.community.model.recordcategory.RecordCategory; +import org.alfresco.rest.rm.community.model.recordcategory.RecordCategoryChild; +import org.alfresco.rest.v0.service.DispositionScheduleService; +import org.alfresco.test.AlfrescoTest; +import org.springframework.beans.factory.annotation.Autowired; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +/** + * Tests for moving record categories between record categories with different retention schedule + */ +public class MoveRecCategoriesWithRSTests extends BaseRMRestTest +{ + private RecordCategory rootCategory, rootCategory2; + private Record elRecord, nonElRecord; + @Autowired + private DispositionScheduleService dispositionScheduleService; + + /** + * Create two root categories with some retention schedules on record level + */ + @BeforeMethod + private void setUpMoveRecCategoriesWithRSTests() + { + STEP("Create record category with retention schedule and apply it to records."); + rootCategory = createRootCategory(getRandomName("rootCategory1")); + dispositionScheduleService.createCategoryRetentionSchedule(rootCategory.getName(), true); + + STEP("Create record category with retention schedule and apply it to records."); + rootCategory2 = createRootCategory(getRandomName("rootCategory2")); + dispositionScheduleService.createCategoryRetentionSchedule(rootCategory2.getName(), true); + } + + /** + * Given following structure is created: + * rootCategory1 with RS applied on record level with cut off and destroy after 1 day + * - subCategory1 without RS + * - recFolder + * - incomplete electronic record + * - complete non-electronic record + * rootCategory2 with RS with retain and destroy both after 2 day + * When moving subcategory1 within rootCategory2 + * Then the records will inherit the RS from rootCategory2 + */ + @Test + @AlfrescoTest (jira = "APPS-1005") + public void testInheritWhenMoveToDifferentRSStep() throws Exception + { + STEP("Add retention schedule cut off step after 1 day period."); + dispositionScheduleService.addCutOffAfterPeriodStep(rootCategory.getName(), "day|1", CREATED_DATE); + + STEP("Add retention schedule destroy step after 1 Day period."); + dispositionScheduleService.addDestroyWithGhostingAfterPeriodStep(rootCategory.getName(), "day|1", CUT_OFF_DATE); + + STEP("Create a subcategory with a record folder and records."); + RecordCategoryChild subCategory = createSubCategoryWithRecords(); + + STEP("Add retention schedule retain step after 2 day period."); + dispositionScheduleService.addRetainAfterPeriodStep(rootCategory2.getName(), "day|2"); + + STEP("Add retention schedule destroy step after 2 Day period."); + dispositionScheduleService.addDestroyWithGhostingAfterPeriodStep(rootCategory2.getName(), "day|2", DATE_FILED); + + STEP("Move the subcategory within the rootCategory2."); + getRestAPIFactory().getNodeAPI(toContentModel(subCategory.getId())).move(createBodyForMoveCopy(rootCategory2.getId())); + assertStatusCode(OK); + + STEP("Check that both records inherit rootCategory2 retention schedule"); + elRecord = getRestAPIFactory().getRecordsAPI().getRecord(elRecord.getId()); + nonElRecord = getRestAPIFactory().getRecordsAPI().getRecord(nonElRecord.getId()); + assertTrue(elRecord.getProperties().getRecordSearchDispositionActionName().equalsIgnoreCase(RM_ACTIONS.END_RETENTION.getAction()), + "Disposition action should be retain"); + assertTrue(elRecord.getProperties().getRecordSearchDispositionPeriod().equalsIgnoreCase("day"), + "Disposition period property should be day"); + assertTrue(elRecord.getProperties().getRecordSearchDispositionPeriodExpression().equalsIgnoreCase("2"), + "Disposition period expression should be 2"); + assertTrue(nonElRecord.getProperties().getRecordSearchDispositionActionName().equalsIgnoreCase(RM_ACTIONS.END_RETENTION.getAction()), + "Disposition action should be retain"); + assertTrue(nonElRecord.getProperties().getRecordSearchDispositionPeriod().equalsIgnoreCase("day"), + "Disposition period property should be day"); + assertTrue(nonElRecord.getProperties().getRecordSearchDispositionPeriodExpression().equalsIgnoreCase("2"), + "Disposition period expression should be 2"); + } + + /** + * Given following structure is created: + * rootCategory1 with RS applied on record level with retain and destroy after 1 day + * - subCategory without RS + * - recFolder + * - incomplete electronic record + * - complete non-electronic record + * rootCategory2 with RS with cut off on event case closed and destroy both after 2 day + * When moving subcategory within rootCategory2 + * Then the records will inherit the RS from rootCategory2 + */ + @Test + @AlfrescoTest (jira = "APPS-1004") + public void testInheritWhenMoveToDifferentRSStepOnEventBase() throws Exception + { + STEP("Add retention schedule retain step after 1 day period."); + dispositionScheduleService.addRetainAfterPeriodStep(rootCategory.getName(), "day|1"); + + STEP("Add retention schedule destroy step after 1 Day period."); + dispositionScheduleService.addDestroyWithGhostingAfterPeriodStep(rootCategory.getName(), "day|1", CUT_OFF_DATE); + + STEP("Create a subcategory with a record folder and records."); + RecordCategoryChild subCategory = createSubCategoryWithRecords(); + + STEP("Add retention schedule cut off step on event case closed."); + dispositionScheduleService.addCutOffAfterEventStep(rootCategory2.getName(), RMEvents.CASE_CLOSED.getEventName()); + + STEP("Add retention schedule destroy step after 1 Day period."); + dispositionScheduleService.addDestroyWithGhostingAfterPeriodStep(rootCategory2.getName(), "day|2", DATE_FILED); + + STEP("Move the subcategory within the rootCategory2."); + getRestAPIFactory().getNodeAPI(toContentModel(subCategory.getId())).move(createBodyForMoveCopy(rootCategory2.getId())); + assertStatusCode(OK); + + STEP("Check that both records inherit rootCategory2 retention schedule"); + elRecord = getRestAPIFactory().getRecordsAPI().getRecord(elRecord.getId()); + nonElRecord = getRestAPIFactory().getRecordsAPI().getRecord(nonElRecord.getId()); + assertTrue(elRecord.getProperties().getRecordSearchDispositionActionName().equalsIgnoreCase(RM_ACTIONS.CUT_OFF.getAction()), + "Disposition action should be cut off"); + assertTrue(elRecord.getProperties().getRecordSearchDispositionPeriod().equalsIgnoreCase("none"), + "Disposition period property should none"); + assertTrue(elRecord.getProperties().getRecordSearchDispositionPeriodExpression().equalsIgnoreCase("0"), + "Disposition period expression should be 0"); + assertTrue(elRecord.getProperties().getRecordSearchDispositionEvents().contains(RMEvents.CASE_CLOSED.getEventName()), + "Disposition event list doesn't contain case closed event"); + assertTrue(nonElRecord.getProperties().getRecordSearchDispositionActionName().equalsIgnoreCase(RM_ACTIONS.CUT_OFF.getAction()), + "Disposition action should be cut off"); + assertTrue(nonElRecord.getProperties().getRecordSearchDispositionPeriod().equalsIgnoreCase("none"), + "Disposition period property should be none"); + assertTrue(nonElRecord.getProperties().getRecordSearchDispositionPeriodExpression().equalsIgnoreCase("0"), + "Disposition period expression should be 0"); + assertTrue(nonElRecord.getProperties().getRecordSearchDispositionEvents().contains(RMEvents.CASE_CLOSED.getEventName()), + "Disposition event list doesn't contain case closed event"); + } + + /** + * Given following structure is created: + * rootCategory1 with RS applied on record level with cut off on event case closed and destroy after 1 day + * - subCategory2 without RS + * - recFolder + * - incomplete electronic record + * - complete non-electronic record + * rootCategory2 with cut off on event Obsolete and destroy both after 2 day + * When moving subcategory2 within rootCategory2 + * Then the records will inherit the RS from rootCategory2 + */ + @Test + @AlfrescoTest (jira = "APPS-1004") + public void testInheritWhenMoveToSameStepDifferentEvent() throws Exception + { + STEP("Add retention schedule cut off on case closed."); + dispositionScheduleService.addCutOffAfterEventStep(rootCategory.getName(), RMEvents.CASE_CLOSED.getEventName()); + + STEP("Add retention schedule destroy step after 1 Day period."); + dispositionScheduleService.addDestroyWithGhostingAfterPeriodStep(rootCategory.getName(), "day|1", CUT_OFF_DATE); + + STEP("Create a subcategory with a record folder and records."); + RecordCategoryChild subCategory = createSubCategoryWithRecords(); + + STEP("Add retention schedule cut off step on event separation."); + dispositionScheduleService.addCutOffAfterEventStep(rootCategory2.getName(), RMEvents.OBSOLETE.getEventName()); + + STEP("Add retention schedule destroy step after 2 Day period."); + dispositionScheduleService.addDestroyWithGhostingAfterPeriodStep(rootCategory2.getName(), "day|2", DATE_FILED); + + STEP("Move the subcategory within the rootCategory2."); + getRestAPIFactory().getNodeAPI(toContentModel(subCategory.getId())).move(createBodyForMoveCopy(rootCategory2.getId())); + assertStatusCode(OK); + + STEP("Check that both records inherit rootCategory2 retention schedule"); + elRecord = getRestAPIFactory().getRecordsAPI().getRecord(elRecord.getId()); + nonElRecord = getRestAPIFactory().getRecordsAPI().getRecord(nonElRecord.getId()); + assertTrue(elRecord.getProperties().getRecordSearchDispositionActionName().equalsIgnoreCase(RM_ACTIONS.CUT_OFF.getAction()), + "Disposition action should be cut off"); + assertTrue(elRecord.getProperties().getRecordSearchDispositionPeriod().equalsIgnoreCase("none"), + "Disposition period property should be none"); + assertTrue(elRecord.getProperties().getRecordSearchDispositionPeriodExpression().equalsIgnoreCase("0"), + "Disposition period expression should be 0"); + assertFalse(elRecord.getProperties().getRecordSearchDispositionEvents().contains(RMEvents.CASE_CLOSED.getEventName()), + "Event list contain the event from the previous RS "); + assertTrue(elRecord.getProperties().getRecordSearchDispositionEvents().contains(RMEvents.OBSOLETE.getEventName()), + "Event list doesn't contain the event from the current RS "); + assertTrue(nonElRecord.getProperties().getRecordSearchDispositionActionName().equalsIgnoreCase(RM_ACTIONS.CUT_OFF.getAction()), + "Disposition action should be cut off"); + assertTrue(nonElRecord.getProperties().getRecordSearchDispositionPeriod().equalsIgnoreCase("none"), + "Disposition period property should be none"); + assertTrue(nonElRecord.getProperties().getRecordSearchDispositionPeriodExpression().equalsIgnoreCase("0"), + "Disposition period expression should be 0"); + assertFalse(nonElRecord.getProperties().getRecordSearchDispositionEvents().contains(RMEvents.CASE_CLOSED.getEventName()), + "Event list contain the event from the previous RS "); + assertTrue(nonElRecord.getProperties().getRecordSearchDispositionEvents().contains(RMEvents.OBSOLETE.getEventName()), + "Event list doesn't contain the event from the current RS "); + } + + @AfterMethod (alwaysRun = true) + public void cleanupMoveRecCategoriesWithRSTests() + { + getRestAPIFactory().getRecordCategoryAPI().deleteRecordCategory(rootCategory.getId()); + getRestAPIFactory().getRecordCategoryAPI().deleteRecordCategory(rootCategory2.getId()); + } + + /** + * Helper method to create a sub-category with a folder, an incomplete electronic record and a complete + * electronic record + * @return + */ + private RecordCategoryChild createSubCategoryWithRecords() + { + STEP("Create a subcategory with a record folder"); + RecordCategoryChild subCategory = createRecordCategory(rootCategory.getId(), getRandomName("subCategory")); + RecordCategoryChild recFolder = createFolder(subCategory.getId(), getRandomName("recFolder")); + + STEP("Create 2 records in the record folder. Complete one of them."); + elRecord = createElectronicRecord(recFolder.getId(), getRandomName("elRecord")); + nonElRecord = createNonElectronicRecord(recFolder.getId(), getRandomName("nonElRecord")); + getRestAPIFactory().getRecordsAPI().completeRecord(nonElRecord.getId()); + return subCategory; + } + +} diff --git a/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/records/ExportRecordsTests.java b/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/records/ExportRecordsTests.java new file mode 100644 index 0000000000..d3e7bde53f --- /dev/null +++ b/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/records/ExportRecordsTests.java @@ -0,0 +1,136 @@ +/* + * #%L + * Alfresco Records Management Module + * %% + * 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 . + * #L% + */ +package org.alfresco.rest.rm.community.records; + +import static java.util.Arrays.asList; + +import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentType.CONTENT_TYPE; +import static org.alfresco.rest.rm.community.utils.FilePlanComponentsUtil.createTempFile; +import static org.alfresco.utility.data.RandomData.getRandomName; +import static org.alfresco.utility.report.log.Step.STEP; +import static org.apache.http.HttpStatus.SC_OK; + +import org.alfresco.rest.rm.community.base.BaseRMRestTest; +import org.alfresco.rest.rm.community.model.record.Record; +import org.alfresco.rest.rm.community.model.recordcategory.RecordCategory; +import org.alfresco.rest.rm.community.model.recordcategory.RecordCategoryChild; +import org.alfresco.rest.v0.ExportAPI; +import org.alfresco.test.AlfrescoTest; +import org.springframework.beans.factory.annotation.Autowired; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * This class contains tests for testing the Export functionality on RM site + * + * @author Shubham Jain + * @since 7.1.0 + */ +public class ExportRecordsTests extends BaseRMRestTest +{ + private RecordCategory rootCategory; + + private RecordCategoryChild recordFolder; + + @Autowired + private ExportAPI exportAPI; + + @BeforeClass (alwaysRun = true) + public void exportRecordsTestsBeforeClass() + { + STEP("Create root level category"); + rootCategory = createRootCategory(getRandomName("Category")); + + STEP("Create the record folder inside the rootCategory"); + recordFolder = createRecordFolder(rootCategory.getId(), getRandomName("Folder")); + + } + + @DataProvider (name = "CreateRMNodes") + public Object[][] getRMNodeID() + { + return new String[][] { + { createRecord("Record_4MB", 4).getId() }, + { createRecord("Record_200MB", 200).getId() }, + { recordFolder.getId() } + }; + } + + /** + * Given a record with size > 4 MB + * When I export the record using API + * Then the request is successful + */ + @Test (description = "Testing the RM Export functionality for records of size >4MB and Record " + + "Folder containing records with size >4MB", + dataProvider = "CreateRMNodes") + @AlfrescoTest (jira = "APPS-986") + public void exportRMNodeTest(String nodeID) + { + STEP("Export the created record/record folder with size greater than 4 MB and verifying the expected response" + + " code"); + exportAPI.exportRMNode(getAdminUser().getUsername(), getAdminUser().getPassword(), SC_OK, nodeID); + } + + /** + * I would change this to + * Given a list of records with a size > 4MB + * When I export the records + * Then the request is succesfull + */ + @Test (description = "Testing the RM Export functionality using API for a list of Records at once with " + + "collective size of more than 4MB") + public void exportRecordsTest() + { + STEP("Export all the created records at once and verifying the expected response code"); + exportAPI.exportRMNodes(getAdminUser().getUsername(), getAdminUser().getPassword(), + SC_OK, asList(createRecord("Record_2MB", 2).getId(), createRecord("Record_3MB", 3).getId())); + } + + /** + * Create a Record with a specific size in RM Site inside already created Record Folder + * + * @param recordName Name of the record to be created + * @param sizeInMegaBytes Size of the record to be created in MegaBytes + * @return Created record with defined size + */ + public Record createRecord(String recordName, int sizeInMegaBytes) + { + return getRestAPIFactory().getRecordFolderAPI().createRecord(Record.builder().name(recordName) + .nodeType(CONTENT_TYPE).build(), recordFolder.getId(), + createTempFile("TempFile", sizeInMegaBytes)); + } + + @AfterClass (alwaysRun = true) + public void exportRecordsTestsAfter() + { + STEP("Delete the created rootCategory along with corresponding record folders/records present in it"); + getRestAPIFactory().getRecordCategoryAPI().deleteRecordCategory(rootCategory.getId()); + } +} diff --git a/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/utils/FilePlanComponentsUtil.java b/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/utils/FilePlanComponentsUtil.java index 56dd5da2bb..fe91643217 100644 --- a/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/utils/FilePlanComponentsUtil.java +++ b/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/utils/FilePlanComponentsUtil.java @@ -41,6 +41,7 @@ import static org.testng.Assert.assertTrue; import java.io.File; import java.io.FileOutputStream; import java.io.OutputStreamWriter; +import java.io.RandomAccessFile; import org.alfresco.rest.rm.community.model.record.Record; import org.alfresco.rest.rm.community.model.record.RecordProperties; @@ -66,13 +67,19 @@ public class FilePlanComponentsUtil // Intentionally blank } - /** Name of the image resource file to be used for records body */ + /** + * Name of the image resource file to be used for records body + */ public static final String IMAGE_FILE = "money.JPG"; - /** Title prefix for record category children */ + /** + * Title prefix for record category children + */ public static final String TITLE_PREFIX = "Title for "; - /** Description prefix for record category children */ + /** + * Description prefix for record category children + */ public static final String DESCRIPTION_PREFIX = "This is the description for"; @@ -87,7 +94,7 @@ public class FilePlanComponentsUtil } /** - * Creates a record model with the given type and a random name (with "Record " prefix) + * Creates a record model with the given type and a random name (with "Record " prefix) * * @param nodeType The node type * @return The {@link Record} with for the given node type @@ -95,9 +102,9 @@ public class FilePlanComponentsUtil private static Record createRecordModel(String nodeType) { return Record.builder() - .name("Record " + getRandomAlphanumeric()) - .nodeType(nodeType) - .build(); + .name("Record " + getRandomAlphanumeric()) + .nodeType(nodeType) + .build(); } /** @@ -133,22 +140,22 @@ public class FilePlanComponentsUtil /** * Creates an unfiled records container child record model with the given name and type * - * @param name The name of the unfiled records container child + * @param name The name of the unfiled records container child * @param nodeType The type of the record category child * @return The {@link UnfiledContainerChild} with the given details */ public static UnfiledContainerChild createUnfiledContainerChildRecordModel(String name, String nodeType) { return UnfiledContainerChild.builder() - .name(name) - .nodeType(nodeType) - .build(); + .name(name) + .nodeType(nodeType) + .build(); } /** * Creates a nonElectronic container child record model with all available properties for the non electronic records * - * @param name The name of the unfiled records container child + * @param name The name of the unfiled records container child * @param nodeType The type of the record category child * @return The {@link UnfiledContainerChild} with the given details */ @@ -156,19 +163,19 @@ public class FilePlanComponentsUtil String shelf, String storageLocation, Integer numberOfCopies, Integer physicalSize) { return UnfiledContainerChild.builder() - .name(name) - .nodeType(NON_ELECTRONIC_RECORD_TYPE) - .properties(UnfiledContainerChildProperties.builder() - .title(title) - .description(description) - .box(box) - .file(file) - .shelf(shelf) - .storageLocation(storageLocation) - .numberOfCopies(numberOfCopies) - .physicalSize(physicalSize) - .build()) - .build(); + .name(name) + .nodeType(NON_ELECTRONIC_RECORD_TYPE) + .properties(UnfiledContainerChildProperties.builder() + .title(title) + .description(description) + .box(box) + .file(file) + .shelf(shelf) + .storageLocation(storageLocation) + .numberOfCopies(numberOfCopies) + .physicalSize(physicalSize) + .build()) + .build(); } /** @@ -190,110 +197,110 @@ public class FilePlanComponentsUtil String shelf, String storageLocation, Integer numberOfCopies, Integer physicalSize) { return Record.builder() - .name(name) - .nodeType(NON_ELECTRONIC_RECORD_TYPE) - .properties(RecordProperties.builder() - .title(title) - .description(description) - .box(box) - .file(file) - .shelf(shelf) - .storageLocation(storageLocation) - .numberOfCopies(numberOfCopies) - .physicalSize(physicalSize) - .build()) - .build(); + .name(name) + .nodeType(NON_ELECTRONIC_RECORD_TYPE) + .properties(RecordProperties.builder() + .title(title) + .description(description) + .box(box) + .file(file) + .shelf(shelf) + .storageLocation(storageLocation) + .numberOfCopies(numberOfCopies) + .physicalSize(physicalSize) + .build()) + .build(); } /** * Creates a record model with the given name, description and title * - * @param name The name of the record + * @param name The name of the record * @param description The description of the record - * @param title The title of the record + * @param title The title of the record * @return The {@link Record} with the given details */ public static Record createRecordModel(String name, String description, String title) { return Record.builder() - .name(name) - .properties(RecordProperties.builder() - .description(description) - .title(title) - .build()) - .build(); + .name(name) + .properties(RecordProperties.builder() + .description(description) + .title(title) + .build()) + .build(); } /** * Creates a record category child model with the given name and type * - * @param name The name of the record category child + * @param name The name of the record category child * @param nodeType The type of the record category child * @return The {@link RecordCategoryChild} with the given details */ public static RecordCategoryChild createRecordCategoryChildModel(String name, String nodeType) { return RecordCategoryChild.builder() - .name(name) - .nodeType(nodeType) - .properties(RecordCategoryChildProperties.builder() - .title(TITLE_PREFIX + name) - .build()) - .build(); + .name(name) + .nodeType(nodeType) + .properties(RecordCategoryChildProperties.builder() + .title(TITLE_PREFIX + name) + .build()) + .build(); } /** * Creates a record category model with the given name and title * - * @param name The name of the record category + * @param name The name of the record category * @param title The title of the record category * @return The {@link RecordCategory} with the given details */ public static RecordCategory createRecordCategoryModel(String name, String title) { return RecordCategory.builder() - .name(name) - .nodeType(RECORD_CATEGORY_TYPE) - .properties(RecordCategoryProperties.builder() - .title(title) - .build()) - .build(); + .name(name) + .nodeType(RECORD_CATEGORY_TYPE) + .properties(RecordCategoryProperties.builder() + .title(title) + .build()) + .build(); } /** * Creates a record folder model with the given name and title * - * @param name The name of the record folder + * @param name The name of the record folder * @param title The title of the record folder * @return The {@link RecordFolder} with the given details */ public static RecordFolder createRecordFolderModel(String name, String title) { return RecordFolder.builder() - .name(name) - .nodeType(RECORD_FOLDER_TYPE) - .properties(RecordFolderProperties.builder() - .title(title) - .build()) - .build(); + .name(name) + .nodeType(RECORD_FOLDER_TYPE) + .properties(RecordFolderProperties.builder() + .title(title) + .build()) + .build(); } /** * Creates an unfiled records container child model with the given name and type * - * @param name The name of the unfiled records container child + * @param name The name of the unfiled records container child * @param nodeType The type of the record category child * @return The {@link UnfiledContainerChild} with the given details */ public static UnfiledContainerChild createUnfiledContainerChildModel(String name, String nodeType) { return UnfiledContainerChild.builder() - .name(name) - .nodeType(nodeType) - .properties(UnfiledContainerChildProperties.builder() - .title(TITLE_PREFIX + name) - .build()) - .build(); + .name(name) + .nodeType(nodeType) + .properties(UnfiledContainerChildProperties.builder() + .title(TITLE_PREFIX + name) + .build()) + .build(); } /** @@ -324,6 +331,32 @@ public class FilePlanComponentsUtil } } + /** + * Method to create a temporary file with specific size + * + * @param name file name + * @param sizeInMegaBytes size + * @return temporary file + */ + public static File createTempFile(final String name, long sizeInMegaBytes) + { + try + { + // Create file + final File file = File.createTempFile(name, ".txt"); + + RandomAccessFile raf = new RandomAccessFile(file, "rw"); + raf.setLength(sizeInMegaBytes * 1024 * 1024); + raf.close(); + + return file; + } + catch (Exception exception) + { + throw new RuntimeException("Unable to create test file.", exception); + } + } + /** * Helper method to verify all properties of a nonElectronic record * diff --git a/amps/ags/rm-community/pom.xml b/amps/ags/rm-community/pom.xml index daefc515f0..03926c8098 100644 --- a/amps/ags/rm-community/pom.xml +++ b/amps/ags/rm-community/pom.xml @@ -7,7 +7,7 @@ org.alfresco alfresco-governance-services-community-parent - 11.94-SNAPSHOT + 14.7-SNAPSHOT diff --git a/amps/ags/rm-community/rm-community-repo/.env b/amps/ags/rm-community/rm-community-repo/.env index ace4179284..469ec19f29 100644 --- a/amps/ags/rm-community/rm-community-repo/.env +++ b/amps/ags/rm-community/rm-community-repo/.env @@ -1,4 +1,4 @@ -TRANSFORMERS_TAG=2.5.2 +TRANSFORMERS_TAG=2.5.3 SOLR6_TAG=2.0.2-RC1 POSTGRES_TAG=13.3 ACTIVEMQ_TAG=5.16.1 diff --git a/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service.properties b/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service.properties index eeac19f2e6..dae1c555e2 100644 --- a/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service.properties +++ b/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service.properties @@ -45,4 +45,4 @@ rm.action.create.transfer.container.child-error-message=You can't create items i rm.action.create.transfer.child-error-message=You can't create items in Transfer Folders. rm.action.create.record.folder.child-error-message=You can only create records in record folders and this was a {0}. rm.action.transfer-non-editable=You can't edit transfer folder or container metadata. - +rm.action.node.frozen.error-message=Unable to perform action {0} because the node is frozen or has frozen children. diff --git a/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_cs.properties b/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_cs.properties index 170edacbd7..f35b08ebfa 100644 --- a/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_cs.properties +++ b/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_cs.properties @@ -45,4 +45,4 @@ rm.action.create.transfer.container.child-error-message=Nelze vytv\u00e1\u0159et rm.action.create.transfer.child-error-message=Nelze vytv\u00e1\u0159et polo\u017eky ve slo\u017ece p\u0159enosu. rm.action.create.record.folder.child-error-message=Z\u00e1znamy je mo\u017en\u00e9 vytv\u00e1\u0159et pouze ve slo\u017ek\u00e1ch z\u00e1znam\u016f a toto bylo {0}. rm.action.transfer-non-editable=Nelze upravovat metadata kontejneru nebo slo\u017eky pro p\u0159enos. - +rm.action.node.frozen.error-message=Akci {0} nelze prov\u00e9st, proto\u017ee uzel je zablokovan\u00fd nebo m\u00e1 zablokovan\u00e9 pod\u0159\u00edzen\u00e9 prvky. diff --git a/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_da.properties b/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_da.properties index a28e721da7..9964ea265f 100644 --- a/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_da.properties +++ b/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_da.properties @@ -45,4 +45,4 @@ rm.action.create.transfer.container.child-error-message=Du kan ikke oprette elem rm.action.create.transfer.child-error-message=Du kan ikke oprette elementer i Overf\u00f8r-mapper. rm.action.create.record.folder.child-error-message=Du kan kun oprette poster i postmapper, og dette var en {0}. rm.action.transfer-non-editable=Du kan ikke redigere overf\u00f8rselsmappe- eller container-metadata. - +rm.action.node.frozen.error-message=Handlingen kan ikke udf\u00f8res {0}, fordi noden er l\u00e5st eller har l\u00e5ste underordnede noder. diff --git a/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_de.properties b/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_de.properties index 03b0890712..82932abbbf 100644 --- a/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_de.properties +++ b/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_de.properties @@ -45,4 +45,4 @@ rm.action.create.transfer.container.child-error-message=Sie k\u00f6nnen keine El rm.action.create.transfer.child-error-message=Sie k\u00f6nnen keine Elemente in \u00dcbertragungsordnern erstellen. rm.action.create.record.folder.child-error-message=In Record-Ordnern k\u00f6nnen Sie nur Records erstellen. Das war aber ein {0}. rm.action.transfer-non-editable=Sie k\u00f6nnen Metadaten von \u00dcbertragungsordnern oder -containern nicht bearbeiten. - +rm.action.node.frozen.error-message=Die Aktion ''{0}'' kann nicht ausgef\u00fchrt werden, da der Knoten oder untergeordnete Elemente von ihm festgefahren ist bzw. sind. diff --git a/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_es.properties b/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_es.properties index 5ea2ca053c..070323ce99 100644 --- a/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_es.properties +++ b/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_es.properties @@ -45,4 +45,4 @@ rm.action.create.transfer.container.child-error-message=No se pueden crear eleme rm.action.create.transfer.child-error-message=No se pueden crear elementos en las carpetas de transferencia. rm.action.create.record.folder.child-error-message=Solo puede crear documentos de archivo en carpetas de documentos de archivo. {0} no se puede crear aqu\u00ed. rm.action.transfer-non-editable=No se puede editar una carpeta de transferencia ni los metadatos de un contenedor. - +rm.action.node.frozen.error-message=No se puede realizar la acci\u00f3n {0} porque el nodo est\u00e1 congelado o tiene elementos secundarios congelados. diff --git a/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_fi.properties b/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_fi.properties index 2fb1c281ab..d255b6ec22 100644 --- a/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_fi.properties +++ b/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_fi.properties @@ -45,4 +45,4 @@ rm.action.create.transfer.container.child-error-message=Siirtos\u00e4ili\u00f6\u rm.action.create.transfer.child-error-message=Siirtokansioihin ei voi luoda kohteita. rm.action.create.record.folder.child-error-message=Tietuekansioihin voi luoda ainoastaan tietueita, mutta t\u00e4m\u00e4 oli {0}. rm.action.transfer-non-editable=Siirtokansion tai -s\u00e4ili\u00f6n metatietoja ei voi muokata. - +rm.action.node.frozen.error-message=Toimintoa {0} ei voitu suorittaa, koska solmu on j\u00e4\u00e4dytetty tai sill\u00e4 on j\u00e4\u00e4dytettyj\u00e4 alatasoja. diff --git a/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_fr.properties b/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_fr.properties index d7d4a3ab52..c2b2bd947a 100644 --- a/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_fr.properties +++ b/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_fr.properties @@ -45,4 +45,4 @@ rm.action.create.transfer.container.child-error-message=Impossible de cr\u00e9er rm.action.create.transfer.child-error-message=Impossible de cr\u00e9er des \u00e9l\u00e9ments dans les dossiers de transfert. rm.action.create.record.folder.child-error-message=Vous ne pouvez cr\u00e9er des documents d''archives que dans les dossiers d''archives, dans le cas pr\u00e9sent {0}. rm.action.transfer-non-editable=Impossible de modifier les m\u00e9tadonn\u00e9es de dossier de transfert ou de contenant. - +rm.action.node.frozen.error-message=Impossible d''effectuer l''action {0} car le n\u0153ud ou ses enfants sont gel\u00e9s. diff --git a/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_it.properties b/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_it.properties index 81c2032be7..8fd5091f30 100644 --- a/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_it.properties +++ b/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_it.properties @@ -45,4 +45,4 @@ rm.action.create.transfer.container.child-error-message=Impossibile creare eleme rm.action.create.transfer.child-error-message=Impossibile creare elementi nelle cartelle Trasferimento. rm.action.create.record.folder.child-error-message=I record possono essere creati solo nelle cartelle dei record e questa era {0}. rm.action.transfer-non-editable=Impossibile modificare la cartella di trasferimento o i metadati dei contenitori. - +rm.action.node.frozen.error-message=Impossibile eseguire l''azione {0} poich\u00e9 il nodo \u00e8 congelato o presenta nodi figlio congelati. diff --git a/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_ja.properties b/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_ja.properties index 8be3966fa5..679424bcc4 100644 --- a/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_ja.properties +++ b/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_ja.properties @@ -45,4 +45,4 @@ rm.action.create.transfer.container.child-error-message=\u8ee2\u9001\u30b3\u30f3 rm.action.create.transfer.child-error-message=\u8ee2\u9001\u30d5\u30a9\u30eb\u30c0\u5185\u3067\u306f\u30a2\u30a4\u30c6\u30e0\u3092\u4f5c\u6210\u3067\u304d\u307e\u305b\u3093\u3002 rm.action.create.record.folder.child-error-message=\u30ec\u30b3\u30fc\u30c9\u3092\u4f5c\u6210\u3067\u304d\u308b\u306e\u306f\u30ec\u30b3\u30fc\u30c9\u30d5\u30a9\u30eb\u30c0\u5185\u306e\u307f\u3067\u3001\u3053\u308c\u306f {0} \u3067\u3059\u3002 rm.action.transfer-non-editable=\u8ee2\u9001\u30d5\u30a9\u30eb\u30c0\u307e\u305f\u306f\u30b3\u30f3\u30c6\u30ca\u30e1\u30bf\u30c7\u30fc\u30bf\u306f\u7de8\u96c6\u3067\u304d\u307e\u305b\u3093\u3002 - +rm.action.node.frozen.error-message=\u30ce\u30fc\u30c9\u304c\u30d5\u30ea\u30fc\u30ba\u3057\u3066\u3044\u308b\u304b\u3001\u307e\u305f\u306f\u5b50\u304c\u51cd\u7d50\u3057\u3066\u3044\u308b\u305f\u3081\u3001\u30a2\u30af\u30b7\u30e7\u30f3 {0}\u3092\u5b9f\u884c\u3067\u304d\u307e\u305b\u3093 diff --git a/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_nb.properties b/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_nb.properties index 423c2f79c9..e5946ca805 100644 --- a/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_nb.properties +++ b/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_nb.properties @@ -45,4 +45,4 @@ rm.action.create.transfer.container.child-error-message=Elementer kan ikke oppre rm.action.create.transfer.child-error-message=Elementer kan ikke opprettes i overf\u00f8ringsmapper. rm.action.create.record.folder.child-error-message=Oppf\u00f8ringer kan bare opprettes i oppf\u00f8ringsmapper, og dette er en {0}. rm.action.transfer-non-editable=Overf\u00f8ringsmapper eller beholdermetadata kan ikke redigeres. - +rm.action.node.frozen.error-message=Kan ikke utf\u00f8re handlingen {0} fordi noden er frossen eller har frosne underordnede elementer. diff --git a/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_nl.properties b/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_nl.properties index 397f2ef9aa..70aeaf3026 100755 --- a/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_nl.properties +++ b/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_nl.properties @@ -45,4 +45,4 @@ rm.action.create.transfer.container.child-error-message=U kunt geen onderdelen m rm.action.create.transfer.child-error-message=U kunt geen onderdelen maken in overdrachtsmappen. rm.action.create.record.folder.child-error-message=U kunt alleen archiefstukken maken in archiefmappen en dit was een {0}. rm.action.transfer-non-editable=U kunt metagegevens in een overdrachtsmap of -container niet bewerken. - +rm.action.node.frozen.error-message=Kan de actie {0} niet uitvoeren omdat de node geblokkeerd is of geblokkeerde onderliggende elementen heeft. diff --git a/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_pl.properties b/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_pl.properties index 56e2d2966f..755f8afee9 100644 --- a/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_pl.properties +++ b/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_pl.properties @@ -45,4 +45,4 @@ rm.action.create.transfer.container.child-error-message=Nie mo\u017cna utworzy\u rm.action.create.transfer.child-error-message=Nie mo\u017cna utworzy\u0107 pozycji w folderach przesy\u0142ania. rm.action.create.record.folder.child-error-message=W folderach rekord\u00f3w mo\u017cna tworzy\u0107 tylko rekordy, a to by\u0142o {0}. rm.action.transfer-non-editable=Nie mo\u017cna edytowa\u0107 folderu przesy\u0142ania ani metadanych kontenera. - +rm.action.node.frozen.error-message=Nie mo\u017cna wykona\u0107 czynno\u015bci {0}, poniewa\u017c w\u0119ze\u0142 jest zablokowany lub ma zablokowane elementy podrz\u0119dne. diff --git a/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_pt_BR.properties b/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_pt_BR.properties index b02afa69cd..b07b94ad1b 100644 --- a/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_pt_BR.properties +++ b/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_pt_BR.properties @@ -45,4 +45,4 @@ rm.action.create.transfer.container.child-error-message=N\u00e3o \u00e9 poss\u00 rm.action.create.transfer.child-error-message=N\u00e3o \u00e9 poss\u00edvel criar itens nas pastas de Transfer\u00eancia. rm.action.create.record.folder.child-error-message=\u00c9 poss\u00edvel apenas criar documentos arquiv\u00edsticos em pastas de documentos arquiv\u00edsticos; esta foi {0}. rm.action.transfer-non-editable=N\u00e3o \u00e9 poss\u00edvel editar os metadados das pastas de transfer\u00eancia ou cont\u00eainer. - +rm.action.node.frozen.error-message=N\u00e3o \u00e9 poss\u00edvel executar a a\u00e7\u00e3o {0} porque o n\u00f3 est\u00e1 congelado ou tem filhos congelados. diff --git a/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_ru.properties b/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_ru.properties index 01174e4738..be30f4cfd8 100644 --- a/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_ru.properties +++ b/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_ru.properties @@ -45,4 +45,4 @@ rm.action.create.transfer.container.child-error-message=\u041d\u0435\u0432\u043e rm.action.create.transfer.child-error-message=\u041d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b \u0432 \u043f\u0430\u043f\u043a\u0430\u0445 \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438. rm.action.create.record.folder.child-error-message=\u0417\u0430\u043f\u0438\u0441\u0438 \u043c\u043e\u0436\u043d\u043e \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u0432 \u043f\u0430\u043f\u043a\u0430\u0445 \u0437\u0430\u043f\u0438\u0441\u0435\u0439, \u0430 \u044d\u0442\u043e {0}. rm.action.transfer-non-editable=\u041d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0435 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430 \u0438\u043b\u0438 \u043f\u0430\u043f\u043a\u0438 \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438. - +rm.action.node.frozen.error-message=\u041d\u0435 \u0443\u0434\u0430\u0435\u0442\u0441\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435 {0}, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u0443\u0437\u0435\u043b \u0437\u0430\u043a\u0440\u0435\u043f\u043b\u0435\u043d \u0438\u043b\u0438 \u0438\u043c\u0435\u0435\u0442 \u0437\u0430\u043a\u0440\u0435\u043f\u043b\u0435\u043d\u043d\u044b\u0435 \u0434\u043e\u0447\u0435\u0440\u043d\u0438\u0435 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b. diff --git a/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_sv.properties b/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_sv.properties index 6f22da4f88..9e5ea4b8f1 100644 --- a/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_sv.properties +++ b/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_sv.properties @@ -45,4 +45,4 @@ rm.action.create.transfer.container.child-error-message=Du kan inte skapa objekt rm.action.create.transfer.child-error-message=Du kan inte skapa objekt i \u00d6verf\u00f6ringsmappar. rm.action.create.record.folder.child-error-message=Du kan endast skapa handlingar i handlingsmappar och detta var en {0}. rm.action.transfer-non-editable=Du kan inte redigera \u00f6verf\u00f6ringsmappen eller beh\u00e5llarens metadata. - +rm.action.node.frozen.error-message=Det gick inte att utf\u00f6ra \u00e5tg\u00e4rd {0} eftersom noden \u00e4r frusen eller har frysta underordnade. diff --git a/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_zh_CN.properties b/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_zh_CN.properties index 4ff55da979..326bde95fa 100644 --- a/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_zh_CN.properties +++ b/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service_zh_CN.properties @@ -45,4 +45,4 @@ rm.action.create.transfer.container.child-error-message=\u60a8\u65e0\u6cd5\u5728 rm.action.create.transfer.child-error-message=\u60a8\u65e0\u6cd5\u5728 Transfer \u6587\u4ef6\u5939\u4e2d\u521b\u5efa\u9879\u76ee\u3002 rm.action.create.record.folder.child-error-message=\u60a8\u53ea\u80fd\u5728\u8bb0\u5f55\u6587\u4ef6\u5939\u4e2d\u521b\u5efa\u8bb0\u5f55\uff0c\u4e14\u8fd9\u662f\u4e00\u4e2a {0}\u3002 rm.action.transfer-non-editable=\u60a8\u65e0\u6cd5\u7f16\u8f91\u4f20\u8f93\u6587\u4ef6\u5939\u6216\u5bb9\u5668\u5143\u6570\u636e\u3002 - +rm.action.node.frozen.error-message=\u65e0\u6cd5\u6267\u884c\u64cd\u4f5c{0}\uff0c\u56e0\u4e3a\u8282\u70b9\u5df2\u51bb\u7ed3\u6216\u51bb\u7ed3\u7684\u5b50\u8282\u70b9\u3002 diff --git a/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-job-context.xml b/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-job-context.xml index 8dd01c5ac8..2f76bdf22a 100644 --- a/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-job-context.xml +++ b/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-job-context.xml @@ -80,9 +80,7 @@ - - - + diff --git a/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-model-context.xml b/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-model-context.xml index 8218bd49d0..23b950f2ea 100644 --- a/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-model-context.xml +++ b/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-model-context.xml @@ -102,9 +102,11 @@ + + diff --git a/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-service-context.xml b/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-service-context.xml index 95b668b250..ae80740b66 100644 --- a/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-service-context.xml +++ b/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-service-context.xml @@ -695,6 +695,13 @@ init-method="init" depends-on="org_alfresco_module_rm_resourceBundles"> + + + + retain + + + @@ -899,6 +906,8 @@ + + @@ -944,6 +953,7 @@ org.alfresco.module.org_alfresco_module_rm.freeze.FreezeService.hasFrozenChildren=RM_ALLOW org.alfresco.module.org_alfresco_module_rm.freeze.FreezeService.getFreezeDate=RM_ALLOW org.alfresco.module.org_alfresco_module_rm.freeze.FreezeService.getFreezeInitiator=RM_ALLOW + org.alfresco.module.org_alfresco_module_rm.freeze.FreezeService.isFrozenOrHasFrozenChildren=RM_ALLOW org.alfresco.module.org_alfresco_module_rm.freeze.FreezeService.*=RM_DENY ]]> diff --git a/amps/ags/rm-community/rm-community-repo/docker-compose.yml b/amps/ags/rm-community/rm-community-repo/docker-compose.yml index bdd8d01456..6cae53e5b7 100644 --- a/amps/ags/rm-community/rm-community-repo/docker-compose.yml +++ b/amps/ags/rm-community/rm-community-repo/docker-compose.yml @@ -5,7 +5,7 @@ version: "3" services: alfresco: # acs repo community image with ags repo community amp applied - image: alfresco/alfresco-governance-repository-community:latest + image: alfresco/alfresco-governance-repository-community-base:latest environment: CATALINA_OPTS : " -agentlib:jdwp=transport=dt_socket,address=8000,server=y,suspend=n diff --git a/amps/ags/rm-community/rm-community-repo/pom.xml b/amps/ags/rm-community/rm-community-repo/pom.xml index b273a2aff5..30db11b4ca 100644 --- a/amps/ags/rm-community/rm-community-repo/pom.xml +++ b/amps/ags/rm-community/rm-community-repo/pom.xml @@ -8,13 +8,13 @@ org.alfresco alfresco-governance-services-community-repo-parent - 11.94-SNAPSHOT + 14.7-SNAPSHOT ${project.build.directory}/${project.build.finalName}-war - alfresco/alfresco-governance-repository-community + alfresco/alfresco-governance-repository-community-base @@ -141,6 +141,11 @@ spring-test test + + org.projectlombok + lombok + provided + diff --git a/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/action/RecordsManagementActionServiceImpl.java b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/action/RecordsManagementActionServiceImpl.java index 69ecc6585d..6dbdc424be 100644 --- a/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/action/RecordsManagementActionServiceImpl.java +++ b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/action/RecordsManagementActionServiceImpl.java @@ -35,17 +35,17 @@ import java.util.List; import java.util.Map; import java.util.Set; +import lombok.extern.slf4j.Slf4j; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.module.org_alfresco_module_rm.RecordsManagementPolicies.BeforeRMActionExecution; import org.alfresco.module.org_alfresco_module_rm.RecordsManagementPolicies.OnRMActionExecution; +import org.alfresco.module.org_alfresco_module_rm.freeze.FreezeService; import org.alfresco.module.org_alfresco_module_rm.util.PoliciesUtil; import org.alfresco.repo.policy.ClassPolicyDelegate; import org.alfresco.repo.policy.PolicyComponent; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.namespace.QName; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.springframework.extensions.surf.util.I18NUtil; /** @@ -53,14 +53,13 @@ import org.springframework.extensions.surf.util.I18NUtil; * * @author Roy Wetherall */ +@Slf4j public class RecordsManagementActionServiceImpl implements RecordsManagementActionService { /** I18N */ private static final String MSG_NOT_DEFINED = "rm.action.not-defined"; private static final String MSG_NO_IMPLICIT_NODEREF = "rm.action.no-implicit-noderef"; - - /** Logger */ - private static Log logger = LogFactory.getLog(RecordsManagementActionServiceImpl.class); + private static final String MSG_NODE_FROZEN = "rm.action.node.frozen.error-message"; /** Registered records management actions */ private Map rmActions = new HashMap<>(13); @@ -78,6 +77,16 @@ public class RecordsManagementActionServiceImpl implements RecordsManagementActi private ClassPolicyDelegate beforeRMActionExecutionDelegate; private ClassPolicyDelegate onRMActionExecutionDelegate; + /** + * Freeze Service + */ + private FreezeService freezeService; + + /** + * list of retention actions to automatically execute + */ + private List retentionActions; + /** * @return Policy component */ @@ -94,6 +103,19 @@ public class RecordsManagementActionServiceImpl implements RecordsManagementActi return this.nodeService; } + /** + * @param freezeService freeze service + */ + public void setFreezeService(FreezeService freezeService) + { + this.freezeService = freezeService; + } + + public void setRetentionActions(List retentionActions) + { + this.retentionActions = retentionActions; + } + /** * Set the policy component * @@ -267,21 +289,23 @@ public class RecordsManagementActionServiceImpl implements RecordsManagementActi */ public RecordsManagementActionResult executeRecordsManagementAction(NodeRef nodeRef, String name, Map parameters) { - if (logger.isDebugEnabled()) - { - logger.debug("Executing record management action on " + nodeRef); - logger.debug(" actionName = " + name); - logger.debug(" parameters = " + parameters); - } + log.debug("Executing record management action on " + nodeRef); + log.debug(" actionName = " + name); + log.debug(" parameters = " + parameters); RecordsManagementAction rmAction = this.rmActions.get(name); if (rmAction == null) { String msg = I18NUtil.getMessage(MSG_NOT_DEFINED, name); - if (logger.isWarnEnabled()) - { - logger.warn(msg); - } + log.warn(msg); + throw new AlfrescoRuntimeException(msg); + } + + if (retentionActions.contains(name.toLowerCase()) && freezeService.isFrozenOrHasFrozenChildren(nodeRef)) + { + String msg = I18NUtil.getMessage(MSG_NODE_FROZEN, name); + log.debug(msg); + throw new AlfrescoRuntimeException(msg); } @@ -307,10 +331,7 @@ public class RecordsManagementActionServiceImpl implements RecordsManagementActi if (implicitTargetNode == null) { String msg = I18NUtil.getMessage(MSG_NO_IMPLICIT_NODEREF, name); - if (logger.isWarnEnabled()) - { - logger.warn(msg); - } + log.warn(msg); throw new AlfrescoRuntimeException(msg); } else diff --git a/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/capability/RMAfterInvocationProvider.java b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/capability/RMAfterInvocationProvider.java index cf5c8d36b0..d8ebc610eb 100644 --- a/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/capability/RMAfterInvocationProvider.java +++ b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/capability/RMAfterInvocationProvider.java @@ -306,7 +306,7 @@ public class RMAfterInvocationProvider extends RMSecurityCommon } } - private boolean isUnfiltered(NodeRef nodeRef) + protected boolean isUnfiltered(NodeRef nodeRef) { return !nodeService.hasAspect(nodeRef, RecordsManagementModel.ASPECT_FILE_PLAN_COMPONENT); diff --git a/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/disposition/DispositionServiceImpl.java b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/disposition/DispositionServiceImpl.java index b28ff08326..9c6897139d 100644 --- a/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/disposition/DispositionServiceImpl.java +++ b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/disposition/DispositionServiceImpl.java @@ -45,7 +45,6 @@ import org.alfresco.module.org_alfresco_module_rm.disposition.property.Dispositi import org.alfresco.module.org_alfresco_module_rm.event.RecordsManagementEvent; import org.alfresco.module.org_alfresco_module_rm.fileplan.FilePlanComponentKind; import org.alfresco.module.org_alfresco_module_rm.fileplan.FilePlanService; -import org.alfresco.module.org_alfresco_module_rm.freeze.FreezeService; import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel; import org.alfresco.module.org_alfresco_module_rm.record.RecordService; import org.alfresco.module.org_alfresco_module_rm.recordfolder.RecordFolderService; @@ -59,7 +58,6 @@ import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; -import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.repository.ChildAssociationRef; diff --git a/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/freeze/FreezeService.java b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/freeze/FreezeService.java index eb9f8a1c7a..bbd84ea295 100644 --- a/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/freeze/FreezeService.java +++ b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/freeze/FreezeService.java @@ -150,4 +150,12 @@ public interface FreezeService */ @Deprecated Set getHolds(NodeRef filePlan); + + /** + * Check given node or its children are frozen + * The node should be record or record folder for retention schedule + * + * @param nodeRef + */ + boolean isFrozenOrHasFrozenChildren(NodeRef nodeRef); } diff --git a/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/freeze/FreezeServiceImpl.java b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/freeze/FreezeServiceImpl.java index 9f77ed6121..191f0309ef 100644 --- a/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/freeze/FreezeServiceImpl.java +++ b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/freeze/FreezeServiceImpl.java @@ -43,6 +43,8 @@ import org.alfresco.model.ContentModel; import org.alfresco.module.org_alfresco_module_rm.fileplan.FilePlanService; import org.alfresco.module.org_alfresco_module_rm.hold.HoldService; import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel; +import org.alfresco.module.org_alfresco_module_rm.record.RecordService; +import org.alfresco.module.org_alfresco_module_rm.recordfolder.RecordFolderService; import org.alfresco.module.org_alfresco_module_rm.util.ServiceBaseImpl; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; @@ -75,6 +77,32 @@ public class FreezeServiceImpl extends ServiceBaseImpl /** Hold service */ private HoldService holdService; + /** + * Record Folder Service + */ + private RecordFolderService recordFolderService; + + /** + * Record Service + */ + private RecordService recordService; + + /** + * @param recordFolderService record folder service + */ + public void setRecordFolderService(RecordFolderService recordFolderService) + { + this.recordFolderService = recordFolderService; + } + + /** + * @param recordService record service + */ + public void setRecordService(RecordService recordService) + { + this.recordService = recordService; + } + /** * @return File plan service */ @@ -392,4 +420,24 @@ public class FreezeServiceImpl extends ServiceBaseImpl // create hold return getHoldService().createHold(filePlan, holdName, reason, null); } + + /** + * Helper method to determine if a node is frozen or has frozen children + * + * @param nodeRef Node to be checked + * @return true if the node is frozen or has frozen children, false otherwise + */ + @Override + public boolean isFrozenOrHasFrozenChildren(NodeRef nodeRef) + { + if (recordFolderService.isRecordFolder(nodeRef)) + { + return isFrozen(nodeRef) || hasFrozenChildren(nodeRef); + } + else if (recordService.isRecord(nodeRef)) + { + return isFrozen(nodeRef); + } + return Boolean.FALSE; + } } diff --git a/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/job/DispositionLifecycleJobExecuter.java b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/job/DispositionLifecycleJobExecuter.java index f450bb9fb5..4b1081deae 100644 --- a/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/job/DispositionLifecycleJobExecuter.java +++ b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/job/DispositionLifecycleJobExecuter.java @@ -30,15 +30,14 @@ package org.alfresco.module.org_alfresco_module_rm.job; import static org.alfresco.module.org_alfresco_module_rm.action.RMDispositionActionExecuterAbstractBase.PARAM_NO_ERROR_CHECK; import java.io.Serializable; -import java.util.HashMap; import java.util.List; import java.util.Map; +import lombok.extern.slf4j.Slf4j; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.module.org_alfresco_module_rm.action.RecordsManagementActionService; + import org.alfresco.module.org_alfresco_module_rm.freeze.FreezeService; -import org.alfresco.module.org_alfresco_module_rm.record.RecordService; -import org.alfresco.module.org_alfresco_module_rm.recordfolder.RecordFolderService; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; @@ -48,8 +47,8 @@ import org.alfresco.service.cmr.search.ResultSet; import org.alfresco.service.cmr.search.SearchParameters; import org.alfresco.service.cmr.search.SearchService; import org.alfresco.service.cmr.security.PersonService; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.springframework.extensions.surf.util.I18NUtil; + /** * The Disposition Lifecycle Job Finds all disposition action nodes which are for disposition actions specified Where @@ -58,14 +57,14 @@ import org.apache.commons.logging.LogFactory; * @author mrogers * @author Roy Wetherall */ +@Slf4j public class DispositionLifecycleJobExecuter extends RecordsManagementJobExecuter { - /** logger */ - private static Log logger = LogFactory.getLog(DispositionLifecycleJobExecuter.class); /** batching properties */ private int batchSize; public static final int DEFAULT_BATCH_SIZE = 500; + private static final String MSG_NODE_FROZEN = "rm.action.node.frozen.error-message"; /** list of disposition actions to automatically execute */ private List dispositionActions; @@ -88,11 +87,13 @@ public class DispositionLifecycleJobExecuter extends RecordsManagementJobExecute /** freeze service */ private FreezeService freezeService; - /** record service */ - private RecordService recordService; - - /** record folder service */ - private RecordFolderService recordFolderService; + /** + * @param freezeService freeze service + */ + public void setFreezeService(FreezeService freezeService) + { + this.freezeService = freezeService; + } /** * List of disposition actions to automatically execute when eligible. @@ -133,30 +134,6 @@ public class DispositionLifecycleJobExecuter extends RecordsManagementJobExecute this.searchService = searchService; } - /** - * @param freezeService freeze service - */ - public void setFreezeService(FreezeService freezeService) - { - this.freezeService = freezeService; - } - - /** - * @param recordService record service - */ - public void setRecordService(RecordService recordService) - { - this.recordService = recordService; - } - - /** - * @param recordFolderService record folder service - */ - public void setRecordFolderService(RecordFolderService recordFolderService) - { - this.recordFolderService = recordFolderService; - } - /** * Get the search query string. * @@ -207,11 +184,11 @@ public class DispositionLifecycleJobExecuter extends RecordsManagementJobExecute { try { - logger.debug("Job Starting"); + log.debug("Job Starting"); if (dispositionActions == null || dispositionActions.isEmpty()) { - logger.debug("Job Finished as disposition action is empty"); + log.debug("Job Finished as disposition action is empty"); return; } @@ -220,16 +197,11 @@ public class DispositionLifecycleJobExecuter extends RecordsManagementJobExecute if (batchSize < 1) { - if (logger.isDebugEnabled()) - { - logger.debug("Invalid value for batch size: " + batchSize + " default value used instead."); - } + log.debug("Invalid value for batch size: " + batchSize + " default value used instead."); batchSize = DEFAULT_BATCH_SIZE; } - if (logger.isTraceEnabled()) - { - logger.trace("Using batch size of " + batchSize); - } + + log.trace("Using batch size of " + batchSize); while (hasMore) { @@ -247,10 +219,7 @@ public class DispositionLifecycleJobExecuter extends RecordsManagementJobExecute skipCount += resultNodes.size(); // increase by page size results.close(); - if (logger.isDebugEnabled()) - { - logger.debug("Processing " + resultNodes.size() + " nodes"); - } + log.debug("Processing " + resultNodes.size() + " nodes"); // process search results if (!resultNodes.isEmpty()) @@ -258,14 +227,11 @@ public class DispositionLifecycleJobExecuter extends RecordsManagementJobExecute executeAction(resultNodes); } } - logger.debug("Job Finished"); + log.debug("Job Finished"); } catch (AlfrescoRuntimeException exception) { - if (logger.isDebugEnabled()) - { - logger.debug(exception); - } + log.debug(exception.getMessage()); } } @@ -299,12 +265,9 @@ public class DispositionLifecycleJobExecuter extends RecordsManagementJobExecute } Map props = Map.of(PARAM_NO_ERROR_CHECK, false); - if (isFrozenOrHasFrozenChildren(parent.getParentRef())) + if (freezeService.isFrozenOrHasFrozenChildren(parent.getParentRef())) { - if (logger.isDebugEnabled()) { - logger.debug("unable to perform action " + dispAction + - " because node is frozen or has frozen children"); - } + log.debug(I18NUtil.getMessage(MSG_NODE_FROZEN, dispAction)); continue; } @@ -314,17 +277,13 @@ public class DispositionLifecycleJobExecuter extends RecordsManagementJobExecute recordsManagementActionService .executeRecordsManagementAction(parent.getParentRef(), dispAction, props); - if (logger.isDebugEnabled()) - { - logger.debug("Processed action: " + dispAction + "on" + parent); - } + log.debug("Processed action: " + dispAction + "on" + parent); + } catch (AlfrescoRuntimeException exception) { - if (logger.isDebugEnabled()) - { - logger.debug(exception); - } + log.debug(exception.getMessage()); + } } return Boolean.TRUE; @@ -332,25 +291,6 @@ public class DispositionLifecycleJobExecuter extends RecordsManagementJobExecute retryingTransactionHelper.doInTransaction(processTranCB, false, true); } - /** - * Helper method to determine if a node is frozen or has frozen children - * - * @param nodeRef Node to be checked - * @return true if the node is frozen or has frozen children, false otherwise - */ - private boolean isFrozenOrHasFrozenChildren(NodeRef nodeRef) - { - if (recordFolderService.isRecordFolder(nodeRef)) - { - return freezeService.isFrozen(nodeRef) || freezeService.hasFrozenChildren(nodeRef); - } - if (recordService.isRecord(nodeRef)) - { - return freezeService.isFrozen(nodeRef); - } - throw new AlfrescoRuntimeException("The nodeRef '" + nodeRef + "' is neither a record nor a record folder."); - } - public PersonService getPersonService() { return personService; diff --git a/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/model/behaviour/AbstractDisposableItem.java b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/model/behaviour/AbstractDisposableItem.java index bd0d9e17dd..a064d97c38 100644 --- a/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/model/behaviour/AbstractDisposableItem.java +++ b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/model/behaviour/AbstractDisposableItem.java @@ -30,6 +30,10 @@ package org.alfresco.module.org_alfresco_module_rm.model.behaviour; import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionAction; import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService; import org.alfresco.module.org_alfresco_module_rm.model.BaseBehaviourBean; +import org.alfresco.module.org_alfresco_module_rm.record.RecordService; +import org.alfresco.module.org_alfresco_module_rm.recordfolder.RecordFolderService; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.namespace.QName; @@ -52,7 +56,13 @@ public abstract class AbstractDisposableItem extends BaseBehaviourBean /** disposition service */ protected DispositionService dispositionService; - + + /** record service */ + protected RecordService recordService; + + /** record folder service */ + protected RecordFolderService recordFolderService; + /** * @param dispositionService disposition service */ @@ -60,6 +70,22 @@ public abstract class AbstractDisposableItem extends BaseBehaviourBean { this.dispositionService = dispositionService; } + + /** + * @param recordService record service + */ + public void setRecordService(RecordService recordService) + { + this.recordService = recordService; + } + + /** + * @param recordFolderService record folder service + */ + public void setRecordFolderService(RecordFolderService recordFolderService) + { + this.recordFolderService = recordFolderService; + } /** * Removes unwanted aspects @@ -86,4 +112,35 @@ public abstract class AbstractDisposableItem extends BaseBehaviourBean } } + /** + * Cleans and re-initiates the containing records + * + * @param childAssociationRef + */ + protected void reinitializeRecordFolder(ChildAssociationRef childAssociationRef) + { + + NodeRef newNodeRef = childAssociationRef.getChildRef(); + + AuthenticationUtil.runAs(() -> { + // clean record folder + cleanDisposableItem(nodeService, newNodeRef); + + // re-initialise the record folder + recordFolderService.setupRecordFolder(newNodeRef); + + // sort out the child records + for (NodeRef record : recordService.getRecords(newNodeRef)) + { + // clean record + cleanDisposableItem(nodeService, record); + + // Re-initiate the records in the new folder. + recordService.file(record); + } + + return null; + }, AuthenticationUtil.getSystemUserName()); + } + } diff --git a/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/model/rma/type/RecordCategoryType.java b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/model/rma/type/RecordCategoryType.java index 500aec6f6d..9a09513f15 100644 --- a/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/model/rma/type/RecordCategoryType.java +++ b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/model/rma/type/RecordCategoryType.java @@ -27,13 +27,14 @@ package org.alfresco.module.org_alfresco_module_rm.model.rma.type; +import static org.alfresco.model.ContentModel.TYPE_CONTENT; + import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.alfresco.model.ContentModel; -import org.alfresco.module.org_alfresco_module_rm.model.BaseBehaviourBean; -import org.alfresco.module.org_alfresco_module_rm.recordfolder.RecordFolderService; +import org.alfresco.module.org_alfresco_module_rm.model.behaviour.AbstractDisposableItem; import org.alfresco.module.org_alfresco_module_rm.security.FilePlanPermissionService; import org.alfresco.module.org_alfresco_module_rm.vital.VitalRecordService; import org.alfresco.repo.copy.CopyBehaviourCallback; @@ -49,6 +50,7 @@ import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.namespace.QName; +import org.alfresco.service.namespace.RegexQNamePattern; /** * rma:recordCategory behaviour bean @@ -60,9 +62,10 @@ import org.alfresco.service.namespace.QName; ( defaultType = "rma:recordCategory" ) -public class RecordCategoryType extends BaseBehaviourBean +public class RecordCategoryType extends AbstractDisposableItem implements NodeServicePolicies.OnCreateChildAssociationPolicy, - NodeServicePolicies.OnCreateNodePolicy + NodeServicePolicies.OnCreateNodePolicy, + NodeServicePolicies.OnMoveNodePolicy { private final static List ACCEPTED_UNIQUE_CHILD_TYPES = new ArrayList<>(); private final static List ACCEPTED_NON_UNIQUE_CHILD_TYPES = Arrays.asList(TYPE_RECORD_CATEGORY, TYPE_RECORD_FOLDER); @@ -73,9 +76,6 @@ public class RecordCategoryType extends BaseBehaviourBean /** file plan permission service */ protected FilePlanPermissionService filePlanPermissionService; - /** record folder service */ - private RecordFolderService recordFolderService; - /** * @param vitalRecordService vital record service */ @@ -92,14 +92,6 @@ public class RecordCategoryType extends BaseBehaviourBean this.filePlanPermissionService = filePlanPermissionService; } - /** - * @param recordFolderService record folder service - */ - public void setRecordFolderService(RecordFolderService recordFolderService) - { - this.recordFolderService = recordFolderService; - } - /** * On every event * @@ -204,6 +196,53 @@ public class RecordCategoryType extends BaseBehaviourBean } + /** + * Record Category move behaviour + * + * @see org.alfresco.repo.node.NodeServicePolicies.OnMoveNodePolicy#onMoveNode(org.alfresco.service.cmr.repository.ChildAssociationRef, org.alfresco.service.cmr.repository.ChildAssociationRef) + */ + @Override + @Behaviour + ( + kind = BehaviourKind.CLASS, + notificationFrequency = NotificationFrequency.FIRST_EVENT + ) + public void onMoveNode(ChildAssociationRef oldChildAssocRef, ChildAssociationRef newChildAssocRef) + { + // clean the child folders and records only if the old parent category has a disposition schedule set + // if it doesn't, then there are no old properties on the child nodes that have to be cleaned in order + // for new ones to be set + if (nodeService.getType(newChildAssocRef.getChildRef()).equals(TYPE_RECORD_CATEGORY) + && dispositionService.getDispositionSchedule(oldChildAssocRef.getParentRef()) != null) + { + reinitializeRecordFolders(newChildAssocRef); + } + } + + /** + * Recursively reinitialize each folder in a structure of categories + * Unwanted aspects will be removed from the child records and the records will be re-filed + * Disposition schedule aspects and properties will be inherited from the new parent category + * + * @param childAssociationRef + */ + private void reinitializeRecordFolders(ChildAssociationRef childAssociationRef) + { + for (ChildAssociationRef newChildRef : nodeService.getChildAssocs(childAssociationRef.getChildRef(), + ContentModel.ASSOC_CONTAINS, + RegexQNamePattern.MATCH_ALL)) + { + if (nodeService.getType(newChildRef.getChildRef()).equals(TYPE_RECORD_CATEGORY)) + { + reinitializeRecordFolders(newChildRef); + } + else if (!nodeService.getType(newChildRef.getChildRef()).equals(TYPE_CONTENT)) + { + reinitializeRecordFolder(newChildRef); + } + } + } + /** * Copy callback for record category */ diff --git a/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/model/rma/type/RecordFolderType.java b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/model/rma/type/RecordFolderType.java index f9eb29dbfb..f6dee11ea2 100644 --- a/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/model/rma/type/RecordFolderType.java +++ b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/model/rma/type/RecordFolderType.java @@ -34,8 +34,6 @@ import org.alfresco.model.ContentModel; import org.alfresco.module.org_alfresco_module_rm.identifier.IdentifierService; import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel; import org.alfresco.module.org_alfresco_module_rm.model.behaviour.AbstractDisposableItem; -import org.alfresco.module.org_alfresco_module_rm.record.RecordService; -import org.alfresco.module.org_alfresco_module_rm.recordfolder.RecordFolderService; import org.alfresco.module.org_alfresco_module_rm.vital.VitalRecordService; import org.alfresco.repo.copy.CopyBehaviourCallback; import org.alfresco.repo.copy.CopyDetails; @@ -68,11 +66,6 @@ public class RecordFolderType extends AbstractDisposableItem implements NodeServicePolicies.OnMoveNodePolicy, NodeServicePolicies.OnCreateChildAssociationPolicy { - /** record service */ - private RecordService recordService; - - /** record folder service */ - private RecordFolderService recordFolderService; /** vital record service */ protected VitalRecordService vitalRecordService; @@ -85,22 +78,6 @@ public class RecordFolderType extends AbstractDisposableItem private static final String MSG_CANNOT_CREATE_CHILDREN_IN_CLOSED_RECORD_FOLDER = "rm.service.add-children-to-closed-record-folder"; - /** - * @param recordService record service - */ - public void setRecordService(RecordService recordService) - { - this.recordService = recordService; - } - - /** - * @param recordFolderService record folder service - */ - public void setRecordFolderService(RecordFolderService recordFolderService) - { - this.recordFolderService = recordFolderService; - } - /** * @param vitalRecordService vital record service */ @@ -131,31 +108,7 @@ public class RecordFolderType extends AbstractDisposableItem { if (!oldChildAssocRef.getParentRef().equals(newChildAssocRef.getParentRef())) { - final NodeRef newNodeRef = newChildAssocRef.getChildRef(); - - AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() - { - public Object doWork() - { - // clean record folder - cleanDisposableItem(nodeService, newNodeRef); - - // re-initialise the record folder - recordFolderService.setupRecordFolder(newNodeRef); - - // sort out the child records - for (NodeRef record : recordService.getRecords(newNodeRef)) - { - // clean record - cleanDisposableItem(nodeService, record); - - // Re-initiate the records in the new folder. - recordService.file(record); - } - - return null; - } - }, AuthenticationUtil.getSystemUserName()); + reinitializeRecordFolder(newChildAssocRef); } } else diff --git a/amps/ags/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/disposition/DispositionScheduleInheritanceTest.java b/amps/ags/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/disposition/DispositionScheduleInheritanceTest.java index e55fe0e7b6..2427227972 100644 --- a/amps/ags/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/disposition/DispositionScheduleInheritanceTest.java +++ b/amps/ags/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/disposition/DispositionScheduleInheritanceTest.java @@ -28,6 +28,8 @@ package org.alfresco.module.org_alfresco_module_rm.test.integration.disposition; import static org.alfresco.module.org_alfresco_module_rm.test.util.CommonRMTestUtils.DEFAULT_DISPOSITION_DESCRIPTION; import static org.alfresco.module.org_alfresco_module_rm.test.util.CommonRMTestUtils.DEFAULT_DISPOSITION_INSTRUCTIONS; +import static org.alfresco.module.org_alfresco_module_rm.test.util.CommonRMTestUtils.DEFAULT_EVENT_NAME; +import static org.alfresco.module.org_alfresco_module_rm.test.util.CommonRMTestUtils.SEPARATION_EVENT_NAME; import static org.alfresco.util.GUID.generate; import java.io.Serializable; @@ -37,6 +39,7 @@ import java.util.List; import java.util.Map; import org.alfresco.module.org_alfresco_module_rm.action.impl.CutOffAction; +import org.alfresco.module.org_alfresco_module_rm.action.impl.DestroyAction; import org.alfresco.module.org_alfresco_module_rm.action.impl.RetainAction; import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionAction; import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionSchedule; @@ -190,6 +193,162 @@ public class DispositionScheduleInheritanceTest extends BaseRMTestCase }); } + /** + * Given a root record category A with a retention schedule set to retain and destroy after 1 day + * and another root record category B with a retention schedule set to cut off and destroy after 1 day containing a + * subcategory + * When moving the subcategory into the first root category + * Then records under the subcategory inherit the retention schedule of the parent record category + * The events list contain the retain event step inherited from the new parent category + *

+ * Please see https://alfresco.atlassian.net/browse/APPS-1004 + */ + public void testRetentionScheduleInheritance_APPS_1004() + { + doBehaviourDrivenTest(new BehaviourDrivenTest() + { + NodeRef category1; + NodeRef subcategory2; + NodeRef record; + Date asOfDateBeforeMove; + + @Override + public void given() + { + // create root category1 + category1 = filePlanService.createRecordCategory(filePlan, generate()); + + // create record level disposition schedule for category1 + createDispositionScheduleRetainAndCutOffOneDay(category1); + + // create root category2 + NodeRef category2 = filePlanService.createRecordCategory(filePlan, generate()); + + // create record level disposition schedule for category2 + createDispositionScheduleCutOffAndDestroyOneDay(category2); + + // create subcategory2 under category2 + subcategory2 = filePlanService.createRecordCategory(category2, generate()); + + // create folder under subcategory2 + folder = recordFolderService.createRecordFolder(subcategory2, generate()); + + // file record in folder and complete it + record = utils.createRecord(folder, generate(), generate()); + utils.completeRecord(record); + + //store the date to check if it was updated + asOfDateBeforeMove = dispositionService.getNextDispositionAction(record).getAsOfDate(); + } + + @Override + public void when() throws Exception + { + // move subcategory2 under category1 + fileFolderService.move(subcategory2, category1, null); + } + + @Override + public void then() throws Exception + { + dispositionService.getDispositionSchedule(record); + // check the next disposition action + DispositionAction dispositionActionAfterMove = dispositionService.getNextDispositionAction(record); + assertNotNull(dispositionActionAfterMove); + assertEquals(RetainAction.NAME, dispositionActionAfterMove.getName()); + assertNotNull(dispositionActionAfterMove.getAsOfDate()); + assertTrue(dispositionActionAfterMove.getAsOfDate().after(asOfDateBeforeMove)); + + // check the search aspect details + assertTrue(nodeService.hasAspect(record, ASPECT_RM_SEARCH)); + assertEquals(RetainAction.NAME, nodeService.getProperty(record, PROP_RS_DISPOSITION_ACTION_NAME)); + assertNotNull(nodeService.getProperty(record, PROP_RS_DISPOSITION_ACTION_AS_OF)); + assertNull((List) nodeService.getProperty(record, PROP_RS_DISPOSITION_EVENTS)); + assertNotNull(nodeService.getProperty(record, PROP_RS_DISPOITION_INSTRUCTIONS)); + assertNotNull(nodeService.getProperty(record, PROP_RS_DISPOITION_AUTHORITY)); + assertTrue((Boolean) nodeService.getProperty(record, PROP_RS_HAS_DISPOITION_SCHEDULE)); + } + }); + } + + /** + * Given a root record category A with a retention schedule set to cut off on event 'case closed' + * and another root record category B with a retention schedule set to cut off on event 'separation' + * When moving the subcategory into the first root category + * Then records under the subcategory inherit the retention schedule of the parent record category + * The events list contain the case closed event step inherited from the new parent category + *

+ * Please see https://alfresco.atlassian.net/browse/APPS-1005 + */ + public void testRetentionScheduleInheritance_APPS_1005() + { + doBehaviourDrivenTest(new BehaviourDrivenTest() + { + NodeRef category1; + NodeRef subcategory2; + NodeRef record; + Date asOfDateBeforeMove; + + @Override + public void given() + { + // create root category1 + category1 = filePlanService.createRecordCategory(filePlan, generate()); + + utils.createDispositionSchedule(category1, DEFAULT_DISPOSITION_INSTRUCTIONS, + DEFAULT_DISPOSITION_DESCRIPTION, true, true, false, DEFAULT_EVENT_NAME); + + // create root category2 + NodeRef category2 = filePlanService.createRecordCategory(filePlan, generate()); + + // create record level disposition schedule for category2 + utils.createDispositionSchedule(category2, DEFAULT_DISPOSITION_INSTRUCTIONS, + DEFAULT_DISPOSITION_DESCRIPTION, true, true, false, SEPARATION_EVENT_NAME); + + // create subcategory2 under category2 + subcategory2 = filePlanService.createRecordCategory(category2, generate()); + + // create folder under subcategory2 + folder = recordFolderService.createRecordFolder(subcategory2, generate()); + + // file record in folder and complete it + record = utils.createRecord(folder, generate(), generate()); + utils.completeRecord(record); + + //store the date to check if it was updated + asOfDateBeforeMove = dispositionService.getNextDispositionAction(record).getAsOfDate(); + } + + @Override + public void when() throws Exception + { + // move subcategory2 under category1 + fileFolderService.move(subcategory2, category1, null); + } + + @Override + public void then() throws Exception + { + // check the next disposition action + DispositionAction dispositionActionAfterMove = dispositionService.getNextDispositionAction(record); + assertNotNull(dispositionActionAfterMove); + assertEquals(CutOffAction.NAME, dispositionActionAfterMove.getName()); + + // check the search aspect details + assertTrue(nodeService.hasAspect(record, ASPECT_RM_SEARCH)); + assertEquals(CutOffAction.NAME, nodeService.getProperty(record, PROP_RS_DISPOSITION_ACTION_NAME)); + assertNotNull((List) nodeService.getProperty(record, PROP_RS_DISPOSITION_EVENTS)); + assertEquals(((List) ((List) nodeService.getProperty(record, + PROP_RS_DISPOSITION_EVENTS))).size(), 1); + assertEquals(DEFAULT_EVENT_NAME, ((List) ((List) nodeService.getProperty(record, + PROP_RS_DISPOSITION_EVENTS))).get(0)); + assertNotNull(nodeService.getProperty(record, PROP_RS_DISPOITION_INSTRUCTIONS)); + assertNotNull(nodeService.getProperty(record, PROP_RS_DISPOITION_AUTHORITY)); + assertTrue((Boolean) nodeService.getProperty(record, PROP_RS_HAS_DISPOITION_SCHEDULE)); + } + }); + } + private void createDispositionScheduleCutOff(NodeRef category, String action, String period) { DispositionSchedule ds = utils.createDispositionSchedule(category, DEFAULT_DISPOSITION_INSTRUCTIONS, DEFAULT_DISPOSITION_DESCRIPTION, true, false, false); @@ -205,6 +364,22 @@ public class DispositionScheduleInheritanceTest extends BaseRMTestCase createDispositionScheduleStep(ds, RetainAction.NAME, CommonRMTestUtils.PERIOD_IMMEDIATELY); } + private void createDispositionScheduleRetainAndCutOffOneDay(NodeRef category) + { + DispositionSchedule ds = utils.createDispositionSchedule(category, DEFAULT_DISPOSITION_INSTRUCTIONS, DEFAULT_DISPOSITION_DESCRIPTION, true, false, false); + + createDispositionScheduleStep(ds, RetainAction.NAME, CommonRMTestUtils.PERIOD_ONE_DAY); + createDispositionScheduleStep(ds, DestroyAction.NAME, CommonRMTestUtils.PERIOD_ONE_DAY); + } + + private void createDispositionScheduleCutOffAndDestroyOneDay(NodeRef category) + { + DispositionSchedule ds = utils.createDispositionSchedule(category, DEFAULT_DISPOSITION_INSTRUCTIONS, DEFAULT_DISPOSITION_DESCRIPTION, true, false, false); + + createDispositionScheduleStep(ds, CutOffAction.NAME, CommonRMTestUtils.PERIOD_ONE_DAY); + createDispositionScheduleStep(ds, DestroyAction.NAME, CommonRMTestUtils.PERIOD_ONE_DAY); + } + private void createDispositionScheduleStep(DispositionSchedule ds, String action, String period) { Map step = new HashMap(3); diff --git a/amps/ags/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/service/FreezeServiceImplTest.java b/amps/ags/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/service/FreezeServiceImplTest.java index 870cce7038..94e81e8e64 100644 --- a/amps/ags/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/service/FreezeServiceImplTest.java +++ b/amps/ags/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/service/FreezeServiceImplTest.java @@ -53,7 +53,7 @@ public class FreezeServiceImplTest extends BaseRMTestCase /** * Test freeze service methods. - * + * * @deprecated as of 2.2 */ public void testFreezeService() throws Exception @@ -219,7 +219,7 @@ public class FreezeServiceImplTest extends BaseRMTestCase // hold is not automatically removed holdAssocs = holdService.getHolds(filePlan); assertEquals(1, holdAssocs.size()); - + // delete hold holdService.deleteHold(holdNodeRef); @@ -265,8 +265,38 @@ public class FreezeServiceImplTest extends BaseRMTestCase assertFalse(freezeService.isFrozen(recordFour)); // assertFalse(freezeService.hasFrozenChildren(rmFolder)); - return null; - } - }); - } + return null; + } + }); + + doTestInTransaction(new Test() + { + @Override + public Void run() throws Exception + { + NodeRef hold101 = holdService.createHold(filePlan, "freezename 103", "FreezeReason", null); + // Freeze a record folder + assertNotNull(hold101); + holdService.addToHold(hold101, rmFolder); + assertTrue(recordFolderService.isRecordFolder(rmFolder)); + assertTrue(freezeService.isFrozenOrHasFrozenChildren(rmFolder)); + return null; + } + }); + + doTestInTransaction(new Test() + { + @Override + public Void run() throws Exception + { + NodeRef hold101 = holdService.createHold(filePlan, "freezename 104", "FreezeReason", null); + // Freeze a record inside a record folder + assertNotNull(hold101); + holdService.addToHold(hold101, recordThree); + assertTrue(recordService.isRecord(recordThree)); + assertTrue(freezeService.isFrozenOrHasFrozenChildren(rmFolder)); + return null; + } + }); + } } diff --git a/amps/ags/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/CommonRMTestUtils.java b/amps/ags/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/CommonRMTestUtils.java index 2543f63225..7d9dc3d0ae 100644 --- a/amps/ags/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/CommonRMTestUtils.java +++ b/amps/ags/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/CommonRMTestUtils.java @@ -82,8 +82,10 @@ public class CommonRMTestUtils implements RecordsManagementModel public static final String DEFAULT_DISPOSITION_INSTRUCTIONS = "disposition instructions"; public static final String DEFAULT_DISPOSITION_DESCRIPTION = "disposition action description"; public static final String DEFAULT_EVENT_NAME = "case_closed"; + public static final String SEPARATION_EVENT_NAME = "separation"; public static final String PERIOD_NONE = "none|0"; public static final String PERIOD_IMMEDIATELY = "immediately|0"; + public static final String PERIOD_ONE_DAY = "day|1"; public static final String PERIOD_FIVE_DAYS = "day|5"; public static final String PERIOD_TEN_DAYS = "day|10"; public static final String PERIOD_ONE_WEEK = "week|1"; diff --git a/amps/ags/rm-community/rm-community-rest-api-explorer/pom.xml b/amps/ags/rm-community/rm-community-rest-api-explorer/pom.xml index 368b61a8b8..fd35c10dfb 100644 --- a/amps/ags/rm-community/rm-community-rest-api-explorer/pom.xml +++ b/amps/ags/rm-community/rm-community-rest-api-explorer/pom.xml @@ -7,7 +7,7 @@ org.alfresco alfresco-governance-services-community-repo-parent - 11.94-SNAPSHOT + 14.7-SNAPSHOT diff --git a/amps/module-info.java b/amps/module-info.java new file mode 100644 index 0000000000..669eda1dd7 --- /dev/null +++ b/amps/module-info.java @@ -0,0 +1,5 @@ +module simple.lombok { + requires static lombok; + requires java.logging; + +} \ No newline at end of file diff --git a/amps/pom.xml b/amps/pom.xml index 53e47b8d7a..373353a597 100644 --- a/amps/pom.xml +++ b/amps/pom.xml @@ -7,7 +7,7 @@ org.alfresco alfresco-community-repo - 11.94-SNAPSHOT + 14.7-SNAPSHOT diff --git a/amps/share-services/pom.xml b/amps/share-services/pom.xml index cceeec982e..7a0cb14b1a 100644 --- a/amps/share-services/pom.xml +++ b/amps/share-services/pom.xml @@ -8,7 +8,7 @@ org.alfresco alfresco-community-repo-amps - 11.94-SNAPSHOT + 14.7-SNAPSHOT diff --git a/core/pom.xml b/core/pom.xml index 4daba9ed7a..be8a234a0d 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -7,7 +7,7 @@ org.alfresco alfresco-community-repo - 11.94-SNAPSHOT + 14.7-SNAPSHOT diff --git a/data-model/pom.xml b/data-model/pom.xml index d666931da7..762e7e12a2 100644 --- a/data-model/pom.xml +++ b/data-model/pom.xml @@ -7,7 +7,7 @@ org.alfresco alfresco-community-repo - 11.94-SNAPSHOT + 14.7-SNAPSHOT diff --git a/data-model/src/main/java/org/alfresco/repo/content/ContentStore.java b/data-model/src/main/java/org/alfresco/repo/content/ContentStore.java index 7e3c0230f6..93f35a4f27 100644 --- a/data-model/src/main/java/org/alfresco/repo/content/ContentStore.java +++ b/data-model/src/main/java/org/alfresco/repo/content/ContentStore.java @@ -2,7 +2,7 @@ * #%L * Alfresco Data model classes * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited + * 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 @@ -38,6 +38,7 @@ import org.alfresco.service.cmr.repository.ContentStreamListener; import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.DirectAccessUrl; + /** * Provides low-level retrieval of content * {@link org.alfresco.service.cmr.repository.ContentReader readers} and @@ -259,30 +260,58 @@ public interface ContentStore public boolean delete(String contentUrl); /** - * Gets a presigned URL to directly access a binary content. It is up to the actual store - * implementation if it can fulfil this request with an expiry time or not. + * Checks if the store supports the retrieving of direct access URLs. * - * @param contentUrl A content store URL - * @param expiresAt An optional expiry date, so the direct access url would become invalid when the expiry date is reached - * @return A direct access URL object for a binary content - * @throws UnsupportedOperationException if the store is unable to provide the information + * @return {@code true} if direct access URLs retrieving is supported, {@code false} otherwise */ - default DirectAccessUrl getDirectAccessUrl(String contentUrl, Date expiresAt) + default boolean isContentDirectUrlEnabled() { - throw new UnsupportedOperationException( - "Retrieving direct access URLs is not supported by this content store."); + return false; } /** - * Checks if the store supports the retrieving of direct access URLs. + * Checks if the store supports the retrieving of a direct access URL for the given node. * - * @return true if direct access URLs retrieving is supported, false otherwise + * @param contentUrl the {@code URL} of the content for which to request a direct access {@code URL} + * @return {@code true} if direct access URLs retrieving is supported for the node, {@code false} otherwise */ - default boolean isDirectAccessSupported() + default boolean isContentDirectUrlEnabled(String contentUrl) { return false; } + /** + * Gets a presigned URL to directly access the content. It is up to the actual store + * implementation if it can fulfil this request with an expiry time or not. + * + * @param contentUrl A content store {@code URL} + * @param attachment {@code true} if an attachment URL is requested, {@code false} for an embedded {@code URL}. + * @param fileName File name of the content + * @return A direct access {@code URL} object for the content + * @throws UnsupportedOperationException if the store is unable to provide the information + */ + default DirectAccessUrl requestContentDirectUrl(String contentUrl, boolean attachment, String fileName) + { + return requestContentDirectUrl(contentUrl, attachment, fileName, null); + } + + /** + * Gets a presigned URL to directly access the content. It is up to the actual store + * implementation if it can fulfil this request with an expiry time or not. + * + * @param contentUrl A content store {@code URL} + * @param attachment {@code true} if an attachment URL is requested, {@code false} for an embedded {@code URL}. + * @param fileName File name of the content + * @param validFor The time at which the direct access {@code URL} will expire. + * @return A direct access {@code URL} object for the content. + * @throws UnsupportedOperationException if the store is unable to provide the information + */ + default DirectAccessUrl requestContentDirectUrl(String contentUrl, boolean attachment, String fileName, Long validFor) + { + throw new UnsupportedOperationException( + "Retrieving direct access URLs is not supported by this content store."); + } + /** * Checks whether or not the current {@link ContentStore} supports the provided {@link Set} storage classes * @@ -292,8 +321,8 @@ public interface ContentStore default boolean isStorageClassesSupported(StorageClassSet storageClassSet) { return storageClassSet == null || - storageClassSet.isEmpty() || - (1 == storageClassSet.size() && storageClassSet.equals(SCS_DEFAULT)); + storageClassSet.isEmpty() || + (1 == storageClassSet.size() && storageClassSet.equals(SCS_DEFAULT)); } /** diff --git a/data-model/src/main/java/org/alfresco/repo/search/impl/querymodel/QueryEngine.java b/data-model/src/main/java/org/alfresco/repo/search/impl/querymodel/QueryEngine.java index e543104e5f..b39cbd572e 100644 --- a/data-model/src/main/java/org/alfresco/repo/search/impl/querymodel/QueryEngine.java +++ b/data-model/src/main/java/org/alfresco/repo/search/impl/querymodel/QueryEngine.java @@ -2,7 +2,7 @@ * #%L * Alfresco Data model classes * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited + * 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 diff --git a/data-model/src/main/java/org/alfresco/service/cmr/repository/DirectAccessUrl.java b/data-model/src/main/java/org/alfresco/service/cmr/repository/DirectAccessUrl.java index 0df9532f7a..365f441bb2 100644 --- a/data-model/src/main/java/org/alfresco/service/cmr/repository/DirectAccessUrl.java +++ b/data-model/src/main/java/org/alfresco/service/cmr/repository/DirectAccessUrl.java @@ -2,7 +2,7 @@ * #%L * Alfresco Data model classes * %% - * Copyright (C) 2005 - 2020 Alfresco Software Limited + * 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 @@ -27,6 +27,7 @@ package org.alfresco.service.cmr.repository; import java.io.Serializable; import java.util.Date; +import java.util.Objects; import org.alfresco.api.AlfrescoPublicApi; @@ -36,7 +37,8 @@ public class DirectAccessUrl implements Serializable private static final long serialVersionUID = -881676208224414139L; private String contentUrl; - private Date expiresAt; + private Date expiryTime; + private boolean attachment; public String getContentUrl() { @@ -48,13 +50,38 @@ public class DirectAccessUrl implements Serializable this.contentUrl = contentUrl; } - public Date getExpiresAt() + public Date getExpiryTime() { - return expiresAt; + return expiryTime; } - public void setExpiresAt(Date expiresAt) + public void setExpiryTime(Date expiryTime) { - this.expiresAt = expiresAt; + this.expiryTime = expiryTime; + } + + public boolean isAttachment() + { + return attachment; + } + + public void setAttachment(boolean attachment) + { + this.attachment = attachment; + } + + @Override public boolean equals(Object obj) + { + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + + DirectAccessUrl that = (DirectAccessUrl) obj; + return attachment == that.attachment && Objects.equals(contentUrl, + that.contentUrl) && Objects.equals(expiryTime, that.expiryTime); + } + + @Override public int hashCode() + { + return Objects.hash(contentUrl, expiryTime, attachment); } } diff --git a/packaging/distribution/pom.xml b/packaging/distribution/pom.xml index b14edf0d12..83264a1ac8 100644 --- a/packaging/distribution/pom.xml +++ b/packaging/distribution/pom.xml @@ -9,6 +9,6 @@ org.alfresco alfresco-community-repo-packaging - 11.94-SNAPSHOT + 14.7-SNAPSHOT diff --git a/packaging/distribution/src/main/resources/licenses/3rd-party/BSDlike-JDOM.txt b/packaging/distribution/src/main/resources/licenses/3rd-party/BSDlike-JDOM.txt new file mode 100644 index 0000000000..24746983ae --- /dev/null +++ b/packaging/distribution/src/main/resources/licenses/3rd-party/BSDlike-JDOM.txt @@ -0,0 +1,54 @@ +/*-- + + Copyright (C) 2000-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + diff --git a/packaging/distribution/src/main/resources/licenses/3rd-party/BSDlike-XPP3.txt b/packaging/distribution/src/main/resources/licenses/3rd-party/BSDlike-XPP3.txt new file mode 100644 index 0000000000..4cc7224e81 --- /dev/null +++ b/packaging/distribution/src/main/resources/licenses/3rd-party/BSDlike-XPP3.txt @@ -0,0 +1,46 @@ +Indiana University Extreme! Lab Software License + +Version 1.1.1 + +Copyright (c) 2002 Extreme! Lab, Indiana University. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + +3. The end-user documentation included with the redistribution, if any, + must include the following acknowledgment: + + "This product includes software developed by the Indiana University + Extreme! Lab (http://www.extreme.indiana.edu/)." + +Alternately, this acknowledgment may appear in the software itself, +if and wherever such third-party acknowledgments normally appear. + +4. The names "Indiana Univeristy" and "Indiana Univeristy Extreme! Lab" +must not be used to endorse or promote products derived from this +software without prior written permission. For written permission, +please contact http://www.extreme.indiana.edu/. + +5. Products derived from this software may not use "Indiana Univeristy" +name nor may "Indiana Univeristy" appear in their name, without prior +written permission of the Indiana University. + +THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHORS, COPYRIGHT HOLDERS OR ITS CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/packaging/docker-alfresco/Dockerfile b/packaging/docker-alfresco/Dockerfile index aea5dadad5..430d0a89e2 100644 --- a/packaging/docker-alfresco/Dockerfile +++ b/packaging/docker-alfresco/Dockerfile @@ -1,6 +1,6 @@ # Fetch image based on Tomcat 9.0, Java 11 and Centos 8 # More infos about this image: https://github.com/Alfresco/alfresco-docker-base-tomcat -FROM alfresco/alfresco-base-tomcat:9.0.45-java-11-centos-8 +FROM alfresco/alfresco-base-tomcat:9.0.52-java-11-centos-7 # Set default docker_context. ARG resource_path=target @@ -65,12 +65,12 @@ RUN sed -i -e "s_log4j.appender.File.File\=alfresco.log_log4j.appender.File.File # fontconfig is required by Activiti worflow diagram generator # installing pinned dependencies as well -RUN yum install -y fontconfig-2.13.1-3.el8 \ - dejavu-fonts-common-2.35-7.el8 \ - fontpackages-filesystem-1.44-22.el8 \ - freetype-2.9.1-4.el8_3.1 \ - libpng-1.6.34-5.el8 \ - dejavu-sans-fonts-2.35-7.el8 && \ +RUN yum install -y fontconfig-2.13.0-4.3.el7 \ + dejavu-fonts-common-2.33-6.el7 \ + fontpackages-filesystem-1.44-8.el7 \ + freetype-2.8-14.el7_9.1 \ + libpng-1.5.13-8.el7 \ + dejavu-sans-fonts-2.33-6.el7 && \ yum clean all # The standard configuration is to have all Tomcat files owned by root with group GROUPNAME and whilst owner has read/write privileges, diff --git a/packaging/docker-alfresco/pom.xml b/packaging/docker-alfresco/pom.xml index 26c836092a..bbb1ea31eb 100644 --- a/packaging/docker-alfresco/pom.xml +++ b/packaging/docker-alfresco/pom.xml @@ -7,7 +7,7 @@ org.alfresco alfresco-community-repo-packaging - 11.94-SNAPSHOT + 14.7-SNAPSHOT diff --git a/packaging/pom.xml b/packaging/pom.xml index 4836c1fa02..fac0b3ac8d 100644 --- a/packaging/pom.xml +++ b/packaging/pom.xml @@ -7,7 +7,7 @@ org.alfresco alfresco-community-repo - 11.94-SNAPSHOT + 14.7-SNAPSHOT diff --git a/packaging/tests/environment/.env b/packaging/tests/environment/.env index be995ae8fd..dcd9add520 100644 --- a/packaging/tests/environment/.env +++ b/packaging/tests/environment/.env @@ -1,4 +1,4 @@ -TRANSFORMERS_TAG=2.5.2 +TRANSFORMERS_TAG=2.5.3 SOLR6_TAG=2.0.2 POSTGRES_TAG=13.3 ACTIVEMQ_TAG=5.16.1 diff --git a/packaging/tests/pom.xml b/packaging/tests/pom.xml index b56d6ba620..e30f4d79b5 100644 --- a/packaging/tests/pom.xml +++ b/packaging/tests/pom.xml @@ -6,7 +6,7 @@ org.alfresco alfresco-community-repo-packaging - 11.94-SNAPSHOT + 14.7-SNAPSHOT diff --git a/packaging/tests/tas-cmis/pom.xml b/packaging/tests/tas-cmis/pom.xml index 10769257b8..b8b336d744 100644 --- a/packaging/tests/tas-cmis/pom.xml +++ b/packaging/tests/tas-cmis/pom.xml @@ -9,7 +9,7 @@ org.alfresco alfresco-community-repo-tests - 11.94-SNAPSHOT + 14.7-SNAPSHOT diff --git a/packaging/tests/tas-cmis/src/test/java/org/alfresco/cmis/CancelCheckOutTests.java b/packaging/tests/tas-cmis/src/test/java/org/alfresco/cmis/CancelCheckOutTests.java index 088955f88f..d5eaa2a79b 100644 --- a/packaging/tests/tas-cmis/src/test/java/org/alfresco/cmis/CancelCheckOutTests.java +++ b/packaging/tests/tas-cmis/src/test/java/org/alfresco/cmis/CancelCheckOutTests.java @@ -90,7 +90,7 @@ public class CancelCheckOutTests extends CmisTest .cancelCheckOut(); } - @Test(groups = { TestGroup.REGRESSION, TestGroup.CMIS}) + @Test(groups = { TestGroup.NOT_SUPPORTED_ON_SINGLE_PIPELINE, TestGroup.REGRESSION, TestGroup.CMIS}) @TestRail(section = {"cmis-api"}, executionType= ExecutionType.REGRESSION, description = "Verify that cancel check out on document created with Versioning State CHECKED OUT deletes the document") public void cancelCheckOutOnDocWithVersioningStateCheckedOut() throws Exception diff --git a/packaging/tests/tas-email/pom.xml b/packaging/tests/tas-email/pom.xml index 0270e12496..abe8d52236 100644 --- a/packaging/tests/tas-email/pom.xml +++ b/packaging/tests/tas-email/pom.xml @@ -9,7 +9,7 @@ org.alfresco alfresco-community-repo-tests - 11.94-SNAPSHOT + 14.7-SNAPSHOT diff --git a/packaging/tests/tas-integration/pom.xml b/packaging/tests/tas-integration/pom.xml index 077c0f1119..066cece1b9 100644 --- a/packaging/tests/tas-integration/pom.xml +++ b/packaging/tests/tas-integration/pom.xml @@ -9,7 +9,7 @@ org.alfresco alfresco-community-repo-tests - 11.94-SNAPSHOT + 14.7-SNAPSHOT diff --git a/packaging/tests/tas-restapi/pom.xml b/packaging/tests/tas-restapi/pom.xml index a0f5b8712c..45921ac0ba 100644 --- a/packaging/tests/tas-restapi/pom.xml +++ b/packaging/tests/tas-restapi/pom.xml @@ -9,7 +9,7 @@ org.alfresco alfresco-community-repo-tests - 11.94-SNAPSHOT + 14.7-SNAPSHOT diff --git a/packaging/tests/tas-webdav/pom.xml b/packaging/tests/tas-webdav/pom.xml index 8cfe163796..69f4b276be 100644 --- a/packaging/tests/tas-webdav/pom.xml +++ b/packaging/tests/tas-webdav/pom.xml @@ -9,7 +9,7 @@ org.alfresco alfresco-community-repo-tests - 11.94-SNAPSHOT + 14.7-SNAPSHOT diff --git a/packaging/war/pom.xml b/packaging/war/pom.xml index 4607c70e58..de38423a62 100644 --- a/packaging/war/pom.xml +++ b/packaging/war/pom.xml @@ -7,7 +7,7 @@ org.alfresco alfresco-community-repo-packaging - 11.94-SNAPSHOT + 14.7-SNAPSHOT diff --git a/pom.xml b/pom.xml index e228449046..f1a3959766 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 alfresco-community-repo - 11.94-SNAPSHOT + 14.7-SNAPSHOT pom Alfresco Community Repo Parent @@ -23,7 +23,7 @@ 7 - 1 + 2 0 ${acs.version.major}.0.0 @@ -51,23 +51,23 @@ 0.2 5.23.0 5.23.0 - 1.3.1 + 1.4.0 6.2 - 0.0.12 + 0.0.13 - 5.3.3 + 5.3.9 3.5.2 2.12.3 2.12.4 3.4.4 1.0.0 - 8.22 + 8.23 1.69 3.11.2 1.10.19 20210307 1.4-DBCP330 - 2.8.0 + 2.11.0 2.8.5 4.5.13 4.4.14 @@ -81,11 +81,11 @@ 7.7.10 4.1.2 1.4 - 13.0.1 + 15.0.2 3.4.2.Final 3.7.4 5.16.1 - 1.20 + 1.21 1.2.5 4.1.0 3.38.0 @@ -105,16 +105,16 @@ 3.2.1.3 1.4.0.1 - 7.0.0 + 7.1.0.1 2.2.0 42.2.20 8.0.25 8 2.7.2 - 3.0.44 + 3.0.45 3.3.0 - 1.61 + 1.64 1.30 1.8 1.6 @@ -638,7 +638,7 @@ org.jsoup jsoup - 1.13.1 + 1.14.2 @@ -649,7 +649,7 @@ org.apache.commons commons-csv - 1.8 + 1.9.0 @@ -851,6 +851,7 @@ org.projectlombok lombok 1.18.20 + provided diff --git a/remote-api/pom.xml b/remote-api/pom.xml index af173ee4d4..f7a68838c9 100644 --- a/remote-api/pom.xml +++ b/remote-api/pom.xml @@ -7,7 +7,7 @@ org.alfresco alfresco-community-repo - 11.94-SNAPSHOT + 14.7-SNAPSHOT diff --git a/remote-api/src/main/java/org/alfresco/opencmis/CMISServletDispatcher.java b/remote-api/src/main/java/org/alfresco/opencmis/CMISServletDispatcher.java index d803a91951..268390a642 100644 --- a/remote-api/src/main/java/org/alfresco/opencmis/CMISServletDispatcher.java +++ b/remote-api/src/main/java/org/alfresco/opencmis/CMISServletDispatcher.java @@ -27,17 +27,14 @@ package org.alfresco.opencmis; import java.io.IOException; import java.io.InputStream; -import java.io.PrintWriter; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.Enumeration; import java.util.EventListener; import java.util.Iterator; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.Set; @@ -48,19 +45,17 @@ import javax.servlet.Servlet; import javax.servlet.ServletConfig; import javax.servlet.ServletContext; import javax.servlet.ServletException; -import javax.servlet.ServletOutputStream; import javax.servlet.ServletRegistration; import javax.servlet.SessionCookieConfig; import javax.servlet.SessionTrackingMode; import javax.servlet.descriptor.JspConfigDescriptor; -import javax.servlet.http.Cookie; import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletResponse; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.opencmis.CMISDispatcherRegistry.Binding; import org.alfresco.opencmis.CMISDispatcherRegistry.Endpoint; import org.alfresco.repo.tenant.TenantAdminService; +import org.alfresco.rest.framework.core.exceptions.JsonpCallbackNotAllowedException; import org.alfresco.service.descriptor.Descriptor; import org.alfresco.service.descriptor.DescriptorService; import org.apache.chemistry.opencmis.commons.enums.CmisVersion; @@ -69,7 +64,6 @@ import org.apache.chemistry.opencmis.server.impl.CmisRepositoryContextListener; import org.apache.chemistry.opencmis.server.impl.atompub.CmisAtomPubServlet; import org.springframework.extensions.webscripts.WebScriptRequest; import org.springframework.extensions.webscripts.WebScriptResponse; -import org.springframework.extensions.webscripts.servlet.WebScriptServletRuntime; /** * Dispatches OpenCMIS requests to a servlet e.g. the OpenCMIS AtomPub servlet. @@ -90,6 +84,8 @@ public abstract class CMISServletDispatcher implements CMISDispatcher protected CmisVersion cmisVersion; protected TenantAdminService tenantAdminService; + private boolean allowUnsecureCallbackJSONP; + private Set nonAttachContentTypes = Collections.emptySet(); // pre-configured whitelist, eg. images & pdf public void setTenantAdminService(TenantAdminService tenantAdminService) @@ -151,7 +147,17 @@ public abstract class CMISServletDispatcher implements CMISDispatcher return this.currentDescriptor; } - + + public void setAllowUnsecureCallbackJSONP(boolean allowUnsecureCallbackJSONP) + { + this.allowUnsecureCallbackJSONP = allowUnsecureCallbackJSONP; + } + + public boolean isAllowUnsecureCallbackJSONP() + { + return allowUnsecureCallbackJSONP; + } + public void init() { Endpoint endpoint = new Endpoint(getBinding(), version); @@ -219,12 +225,22 @@ public abstract class CMISServletDispatcher implements CMISDispatcher CMISHttpServletResponse httpResWrapper = getHttpResponse(res); CMISHttpServletRequest httpReqWrapper = getHttpRequest(req); - servlet.service(httpReqWrapper, httpResWrapper); + // check for "callback" query param + if (!allowUnsecureCallbackJSONP && httpReqWrapper.getParameter("callback") != null) + { + throw new JsonpCallbackNotAllowedException(); + } + servlet.service(httpReqWrapper, httpResWrapper); } catch(ServletException e) { throw new AlfrescoRuntimeException("", e); } + catch (JsonpCallbackNotAllowedException e) + { + res.setStatus(403); + res.getWriter().append(e.getMessage()); + } } /** diff --git a/remote-api/src/main/java/org/alfresco/repo/web/scripts/BufferedRequest.java b/remote-api/src/main/java/org/alfresco/repo/web/scripts/BufferedRequest.java index a655e2b631..dec5f19b01 100644 --- a/remote-api/src/main/java/org/alfresco/repo/web/scripts/BufferedRequest.java +++ b/remote-api/src/main/java/org/alfresco/repo/web/scripts/BufferedRequest.java @@ -29,6 +29,7 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.util.function.Supplier; import org.springframework.extensions.surf.util.Content; import org.springframework.extensions.webscripts.Description.FormatStyle; @@ -38,15 +39,15 @@ import org.springframework.extensions.webscripts.WebScriptRequest; import org.springframework.extensions.webscripts.WrappingWebScriptRequest; import org.springframework.util.FileCopyUtils; -public class BufferedRequest implements WrappingWebScriptRequest +public class BufferedRequest implements WrappingWebScriptRequest, AutoCloseable { - private TempOutputStreamFactory streamFactory; - private WebScriptRequest req; + private final Supplier streamFactory; + private final WebScriptRequest req; private TempOutputStream bufferStream; private InputStream contentStream; private BufferedReader contentReader; - public BufferedRequest(WebScriptRequest req, TempOutputStreamFactory streamFactory) + public BufferedRequest(WebScriptRequest req, Supplier streamFactory) { this.req = req; this.streamFactory = streamFactory; @@ -56,7 +57,7 @@ public class BufferedRequest implements WrappingWebScriptRequest { if (bufferStream == null) { - bufferStream = streamFactory.createOutputStream(); + bufferStream = streamFactory.get(); try { @@ -81,7 +82,7 @@ public class BufferedRequest implements WrappingWebScriptRequest } if (contentStream == null) { - contentStream = getBufferedBodyAsTempStream().getInputStream(); + contentStream = getBufferedBodyAsTempStream().toNewInputStream(); } return contentStream; @@ -95,7 +96,7 @@ public class BufferedRequest implements WrappingWebScriptRequest { contentStream.close(); } - catch (Exception e) + catch (Exception ignore) { } contentStream = null; @@ -106,13 +107,14 @@ public class BufferedRequest implements WrappingWebScriptRequest { contentReader.close(); } - catch (Exception e) + catch (Exception ignore) { } contentReader = null; } } - + + @Override public void close() { reset(); @@ -122,7 +124,7 @@ public class BufferedRequest implements WrappingWebScriptRequest { bufferStream.destroy(); } - catch (Exception e) + catch (Exception ignore) { } bufferStream = null; diff --git a/remote-api/src/main/java/org/alfresco/repo/web/scripts/BufferedResponse.java b/remote-api/src/main/java/org/alfresco/repo/web/scripts/BufferedResponse.java index f5811b01ad..4e082052dd 100644 --- a/remote-api/src/main/java/org/alfresco/repo/web/scripts/BufferedResponse.java +++ b/remote-api/src/main/java/org/alfresco/repo/web/scripts/BufferedResponse.java @@ -28,6 +28,7 @@ package org.alfresco.repo.web.scripts; import java.io.IOException; import java.io.OutputStream; import java.io.Writer; +import java.util.function.Supplier; import org.alfresco.error.AlfrescoRuntimeException; import org.apache.commons.logging.Log; @@ -42,25 +43,24 @@ import org.springframework.util.FileCopyUtils; /** * Transactional Buffered Response */ -public class BufferedResponse implements WrappingWebScriptResponse +public class BufferedResponse implements WrappingWebScriptResponse, AutoCloseable { // Logger protected static final Log logger = LogFactory.getLog(BufferedResponse.class); - private TempOutputStreamFactory streamFactory; - private WebScriptResponse res; - private int bufferSize; - private TempOutputStream outputStream = null; - private StringBuilderWriter outputWriter = null; - + private final Supplier streamFactory; + private final WebScriptResponse res; + private final int bufferSize; + private TempOutputStream outputStream; + private StringBuilderWriter outputWriter; /** * Construct - * - * @param res WebScriptResponse + * + * @param res WebScriptResponse * @param bufferSize int */ - public BufferedResponse(WebScriptResponse res, int bufferSize, TempOutputStreamFactory streamFactory) + public BufferedResponse(WebScriptResponse res, int bufferSize, Supplier streamFactory) { this.res = res; this.bufferSize = bufferSize; @@ -71,6 +71,7 @@ public class BufferedResponse implements WrappingWebScriptResponse * (non-Javadoc) * @see org.alfresco.web.scripts.WrappingWebScriptResponse#getNext() */ + @Override public WebScriptResponse getNext() { return res; @@ -123,16 +124,18 @@ public class BufferedResponse implements WrappingWebScriptResponse * (non-Javadoc) * @see org.alfresco.web.scripts.WebScriptResponse#getOutputStream() */ + @Override public OutputStream getOutputStream() throws IOException { - if (outputStream == null) + if (outputStream != null) { - if (outputWriter != null) - { - throw new AlfrescoRuntimeException("Already buffering output writer"); - } - outputStream = streamFactory.createOutputStream(); + return outputStream; } + if (outputWriter != null) + { + throw new AlfrescoRuntimeException("Already buffering output writer"); + } + outputStream = streamFactory.get(); return outputStream; } @@ -151,14 +154,15 @@ public class BufferedResponse implements WrappingWebScriptResponse */ public Writer getWriter() throws IOException { - if (outputWriter == null) + if (outputWriter != null) { - if (outputStream != null) - { - throw new AlfrescoRuntimeException("Already buffering output stream"); - } - outputWriter = new StringBuilderWriter(bufferSize); + return outputWriter; } + if (outputStream != null) + { + throw new AlfrescoRuntimeException("Already buffering output stream"); + } + outputWriter = new StringBuilderWriter(bufferSize); return outputWriter; } @@ -262,15 +266,7 @@ public class BufferedResponse implements WrappingWebScriptResponse if (logger.isDebugEnabled()) logger.debug("Writing Transactional response: size=" + outputStream.getLength()); - try - { - outputStream.flush(); - FileCopyUtils.copy(outputStream.getInputStream(), res.getOutputStream()); - } - finally - { - outputStream.destroy(); - } + FileCopyUtils.copy(outputStream.toNewInputStream(), res.getOutputStream()); } } catch (IOException e) @@ -278,4 +274,20 @@ public class BufferedResponse implements WrappingWebScriptResponse throw new AlfrescoRuntimeException("Failed to commit buffered response", e); } } + + @Override + public void close() + { + if (outputStream != null) + { + try + { + outputStream.destroy(); + } + catch (Exception ignore) + { + } + outputStream = null; + } + } } diff --git a/remote-api/src/main/java/org/alfresco/repo/web/scripts/RepositoryContainer.java b/remote-api/src/main/java/org/alfresco/repo/web/scripts/RepositoryContainer.java index 7e1728be5c..dfc51fdfcc 100644 --- a/remote-api/src/main/java/org/alfresco/repo/web/scripts/RepositoryContainer.java +++ b/remote-api/src/main/java/org/alfresco/repo/web/scripts/RepositoryContainer.java @@ -2,7 +2,7 @@ * #%L * Alfresco Remote API * %% - * Copyright (C) 2005 - 2019 Alfresco Software Limited + * 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 @@ -25,12 +25,13 @@ */ package org.alfresco.repo.web.scripts; -import java.io.File; import java.io.IOException; import java.net.SocketException; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.function.Supplier; import javax.servlet.http.HttpServletResponse; import javax.transaction.Status; @@ -40,7 +41,6 @@ import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.error.ExceptionStackUtil; import org.alfresco.repo.model.Repository; import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; import org.alfresco.repo.transaction.RetryingTransactionHelper; @@ -95,8 +95,7 @@ public class RepositoryContainer extends AbstractRuntimeContainer private String tempDirectoryName = null; private int memoryThreshold = 4 * 1024 * 1024; // 4mb private long maxContentSize = (long) 4 * 1024 * 1024 * 1024; // 4gb - private TempOutputStreamFactory streamFactory = null; - private TempOutputStreamFactory responseStreamFactory = null; + private Supplier streamFactory = null; private String preserveHeadersPattern = null; private Class[] notPublicExceptions = new Class[] {}; @@ -107,17 +106,16 @@ public class RepositoryContainer extends AbstractRuntimeContainer */ public void setup() { - File tempDirectory = TempFileProvider.getTempDir(tempDirectoryName); - this.streamFactory = new TempOutputStreamFactory(tempDirectory, memoryThreshold, maxContentSize, encryptTempFiles, false); - this.responseStreamFactory = new TempOutputStreamFactory(tempDirectory, memoryThreshold, maxContentSize, - encryptTempFiles, false); + streamFactory = TempOutputStream.factory( + TempFileProvider.getTempDir(tempDirectoryName), + memoryThreshold, maxContentSize, encryptTempFiles); } public void setEncryptTempFiles(Boolean encryptTempFiles) { if(encryptTempFiles != null) { - this.encryptTempFiles = encryptTempFiles.booleanValue(); + this.encryptTempFiles = encryptTempFiles; } } @@ -130,7 +128,7 @@ public class RepositoryContainer extends AbstractRuntimeContainer { if(memoryThreshold != null) { - this.memoryThreshold = memoryThreshold.intValue(); + this.memoryThreshold = memoryThreshold; } } @@ -138,7 +136,7 @@ public class RepositoryContainer extends AbstractRuntimeContainer { if(maxContentSize != null) { - this.maxContentSize = maxContentSize.longValue(); + this.maxContentSize = maxContentSize; } } @@ -246,8 +244,7 @@ public class RepositoryContainer extends AbstractRuntimeContainer */ public Map getScriptParameters() { - Map params = new HashMap(); - params.putAll(super.getScriptParameters()); + Map params = new HashMap<>(super.getScriptParameters()); addRepoParameters(params); return params; } @@ -259,16 +256,11 @@ public class RepositoryContainer extends AbstractRuntimeContainer public Map getTemplateParameters() { // Ensure we have a transaction - we might be generating the status template after the main transaction failed - return fallbackTransactionHelper.doInTransaction(new RetryingTransactionCallback>() - { - public Map execute() throws Throwable - { - Map params = new HashMap(); - params.putAll(RepositoryContainer.super.getTemplateParameters()); - params.put(TemplateService.KEY_IMAGE_RESOLVER, imageResolver.getImageResolver()); - addRepoParameters(params); - return params; - } + return fallbackTransactionHelper.doInTransaction(() -> { + Map params = new HashMap<>(RepositoryContainer.super.getTemplateParameters()); + params.put(TemplateService.KEY_IMAGE_RESOLVER, imageResolver.getImageResolver()); + addRepoParameters(params); + return params; }, true); } @@ -321,7 +313,7 @@ public class RepositoryContainer extends AbstractRuntimeContainer Throwable displayCause = ExceptionStackUtil.getCause(e, publicExceptions); if (displayCause == null && hideCause != null) { - AlfrescoRuntimeException alf = null; + final AlfrescoRuntimeException alf; if (e instanceof AlfrescoRuntimeException) { alf = (AlfrescoRuntimeException) e; @@ -342,117 +334,154 @@ public class RepositoryContainer extends AbstractRuntimeContainer } } - protected void executeScriptInternal(WebScriptRequest scriptReq, WebScriptResponse scriptRes, final Authenticator auth) + protected void executeScriptInternal(final WebScriptRequest scriptReq, final WebScriptResponse scriptRes, final Authenticator auth) throws IOException { final WebScript script = scriptReq.getServiceMatch().getWebScript(); final Description desc = script.getDescription(); final boolean debug = logger.isDebugEnabled(); - + // Escalate the webscript declared level of authentication to the container required authentication // eg. must be guest if MT is enabled unless credentials are empty - RequiredAuthentication containerRequiredAuthentication = getRequiredAuthentication(); + final RequiredAuthentication containerRequiredAuthentication = getRequiredAuthentication(); final RequiredAuthentication required = (desc.getRequiredAuthentication().compareTo(containerRequiredAuthentication) < 0 && !auth.emptyCredentials() ? containerRequiredAuthentication : desc.getRequiredAuthentication()); final boolean isGuest = scriptReq.isGuest(); - + if (required == RequiredAuthentication.none) { // TODO revisit - cleared here, in-lieu of WebClient clear //AuthenticationUtil.clearCurrentSecurityContext(); - + transactionedExecuteAs(script, scriptReq, scriptRes); + return; } - else if ((required == RequiredAuthentication.user || required == RequiredAuthentication.admin) && isGuest) + + // if the required authentication is not equal to guest, then it should be one of the following: + // user | sysadmin | admin (the 'none' authentication is handled above) + // in this case the guest user should not be able to execute those scripts. + if (required != RequiredAuthentication.guest && isGuest) { throw new WebScriptException(HttpServletResponse.SC_UNAUTHORIZED, "Web Script " + desc.getId() + " requires user authentication; however, a guest has attempted access."); } - else + + try { - try + AuthenticationUtil.pushAuthentication(); + + // + // Determine if user already authenticated + // + if (debug) { - AuthenticationUtil.pushAuthentication(); - - // - // Determine if user already authenticated - // + String currentUser = AuthenticationUtil.getFullyAuthenticatedUser(); + logger.debug("Current authentication: " + (currentUser == null ? "unauthenticated" : "authenticated as " + currentUser)); + logger.debug("Authentication required: " + required); + logger.debug("Guest login requested: " + isGuest); + } + + // + // Apply appropriate authentication to Web Script invocation + // + final RetryingTransactionCallback authWork = () -> { + if (auth != null && !auth.authenticate(required, isGuest)) + { + return false; + } + // The user will now have been authenticated, based on HTTP Auth, Ticket, etc. + // Check that the user they authenticated as has appropriate access to the script + checkScriptAccess(required, desc.getId()); + if (debug) { String currentUser = AuthenticationUtil.getFullyAuthenticatedUser(); - logger.debug("Current authentication: " + (currentUser == null ? "unauthenticated" : "authenticated as " + currentUser)); - logger.debug("Authentication required: " + required); - logger.debug("Guest login requested: " + isGuest); + logger.debug("Authentication: " + (currentUser == null ? "unauthenticated" : "authenticated as " + currentUser)); } - - // - // Apply appropriate authentication to Web Script invocation - // - RetryingTransactionCallback authWork = new RetryingTransactionCallback() - { - public Boolean execute() throws Exception - { - if (auth == null || auth.authenticate(required, isGuest)) - { - // The user will now have been authenticated, based on HTTP Auth, Ticket etc - // Check that the user they authenticated as has appropriate access to the script - - // Check to see if they supplied HTTP Auth or Ticket as guest, on a script that needs more - if (required == RequiredAuthentication.user || required == RequiredAuthentication.admin) - { - String authenticatedUser = AuthenticationUtil.getFullyAuthenticatedUser(); - String runAsUser = AuthenticationUtil.getRunAsUser(); - - if ( (authenticatedUser == null) || - (authenticatedUser.equals(runAsUser) && authorityService.hasGuestAuthority()) || - (!authenticatedUser.equals(runAsUser) && authorityService.isGuestAuthority(authenticatedUser)) ) - { - throw new WebScriptException(HttpServletResponse.SC_UNAUTHORIZED, "Web Script " + desc.getId() + " requires user authentication; however, a guest has attempted access."); - } - } - - // Check to see if they're admin or system on an Admin only script - if (required == RequiredAuthentication.admin && !(authorityService.hasAdminAuthority() || AuthenticationUtil.getFullyAuthenticatedUser().equals(AuthenticationUtil.getSystemUserName()))) - { - throw new WebScriptException(HttpServletResponse.SC_UNAUTHORIZED, "Web Script " + desc.getId() + " requires admin authentication; however, a non-admin has attempted access."); - } - - if (debug) - { - String currentUser = AuthenticationUtil.getFullyAuthenticatedUser(); - logger.debug("Authentication: " + (currentUser == null ? "unauthenticated" : "authenticated as " + currentUser)); - } - - return true; - } - return false; - } - }; - - boolean readOnly = transactionService.isReadOnly(); - boolean requiresNew = !readOnly && AlfrescoTransactionSupport.getTransactionReadState() == TxnReadState.TXN_READ_ONLY; - if (transactionService.getRetryingTransactionHelper().doInTransaction(authWork, readOnly, requiresNew)) - { - // Execute Web Script if authentication passed - // The Web Script has its own txn management with potential runAs() user - transactionedExecuteAs(script, scriptReq, scriptRes); - } - else - { - throw new WebScriptException(HttpServletResponse.SC_UNAUTHORIZED, "Authentication failed for Web Script " + desc.getId()); - } - } - finally + + return true; + }; + + final boolean readOnly = transactionService.isReadOnly(); + final boolean requiresNew = !readOnly && AlfrescoTransactionSupport.getTransactionReadState() == TxnReadState.TXN_READ_ONLY; + if (!transactionService.getRetryingTransactionHelper().doInTransaction(authWork, readOnly, requiresNew)) { - // - // Reset authentication for current thread - // - AuthenticationUtil.popAuthentication(); - - if (debug) - { - String currentUser = AuthenticationUtil.getFullyAuthenticatedUser(); - logger.debug("Authentication reset: " + (currentUser == null ? "unauthenticated" : "authenticated as " + currentUser)); - } + throw new WebScriptException(HttpServletResponse.SC_UNAUTHORIZED, "Authentication failed for Web Script " + desc.getId()); } + + // Execute Web Script if authentication passed + // The Web Script has its own txn management with potential runAs() user + transactionedExecuteAs(script, scriptReq, scriptRes, required); + } + finally + { + // + // Reset authentication for current thread + // + AuthenticationUtil.popAuthentication(); + + if (debug) + { + String currentUser = AuthenticationUtil.getFullyAuthenticatedUser(); + logger.debug("Authentication reset: " + (currentUser == null ? "unauthenticated" : "authenticated as " + currentUser)); + } + } + } + + private boolean isSystemUser() + { + return Objects.equals(AuthenticationUtil.getFullyAuthenticatedUser(), AuthenticationUtil.getSystemUserName()); + } + + private boolean isSysAdminUser() + { + return authorityService.hasSysAdminAuthority(); + } + + private boolean isAdmin() + { + return authorityService.hasAdminAuthority(); + } + + public final boolean isAdminOrSystemUser() + { + return isAdmin() || isSystemUser(); + } + + /** + * Check to see if they supplied HTTP Auth or Ticket as guest, on a script that needs more + */ + private void checkGuestAccess(RequiredAuthentication required, String scriptDescriptorId) + { + if (required == RequiredAuthentication.user || required == RequiredAuthentication.admin + || required == RequiredAuthentication.sysadmin) + { + final String authenticatedUser = AuthenticationUtil.getFullyAuthenticatedUser(); + final String runAsUser = AuthenticationUtil.getRunAsUser(); + + if ((authenticatedUser == null) || (authenticatedUser.equals(runAsUser) + && authorityService.hasGuestAuthority()) || (!authenticatedUser.equals(runAsUser) + && authorityService.isGuestAuthority(authenticatedUser))) + { + throw new WebScriptException(HttpServletResponse.SC_UNAUTHORIZED, "Web Script " + scriptDescriptorId + + " requires user authentication; however, a guest has attempted access."); + } + } + } + + private void checkScriptAccess(RequiredAuthentication required, String scriptDescriptorId) + { + // first, check guest access + checkGuestAccess(required, scriptDescriptorId); + + // Check to see if the user is sysAdmin, admin or system on a sysadmin scripts + if (required == RequiredAuthentication.sysadmin && !(isSysAdminUser() || isAdminOrSystemUser())) + { + throw new WebScriptException(HttpServletResponse.SC_UNAUTHORIZED, "Web Script " + scriptDescriptorId + + " requires system-admin authentication; however, a non-system-admin has attempted access."); + } + else if (required == RequiredAuthentication.admin && !isAdminOrSystemUser()) + { + throw new WebScriptException(HttpServletResponse.SC_UNAUTHORIZED, "Web Script " + scriptDescriptorId + + " requires admin authentication; however, a non-admin has attempted access."); } } @@ -467,191 +496,160 @@ public class RepositoryContainer extends AbstractRuntimeContainer protected void transactionedExecute(final WebScript script, final WebScriptRequest scriptReq, final WebScriptResponse scriptRes) throws IOException { + final Description description = script.getDescription(); + try { - final Description description = script.getDescription(); if (description.getRequiredTransaction() == RequiredTransaction.none) { script.execute(scriptReq, scriptRes); + return; } - else + } + catch (IOException e) + { + handleIOException(e); + } + + final RequiredTransactionParameters trxParams = description.getRequiredTransactionParameters(); + + try (final BufferedRequest bufferedReq = newBufferedRequest(trxParams, scriptReq, streamFactory); + final BufferedResponse bufferedRes = newBufferedResponse(trxParams, scriptRes, streamFactory)) + { + boolean readonly = description.getRequiredTransactionParameters().getCapability() == TransactionCapability.readonly; + boolean requiresNew = description.getRequiredTransaction() == RequiredTransaction.requiresnew; + + // log a warning if we detect a GET webscript being run in a readwrite transaction, GET calls should + // NOT have any side effects so this scenario as a warning sign something maybe amiss, see ALF-10179. + if (logger.isDebugEnabled() && !readonly && "GET".equalsIgnoreCase( + description.getMethod())) { - final BufferedRequest bufferedReq; - final BufferedResponse bufferedRes; - RequiredTransactionParameters trxParams = description.getRequiredTransactionParameters(); - if (trxParams.getCapability() == TransactionCapability.readwrite) - { - if (trxParams.getBufferSize() > 0) - { - if (logger.isDebugEnabled()) - logger.debug("Creating Transactional Response for ReadWrite transaction; buffersize=" + trxParams.getBufferSize()); + logger.debug("Webscript with URL '" + scriptReq.getURL() + + "' is a GET request but it's descriptor has declared a readwrite transaction is required"); + } - // create buffered request and response that allow transaction retrying - bufferedReq = new BufferedRequest(scriptReq, streamFactory); - bufferedRes = new BufferedResponse(scriptRes, trxParams.getBufferSize(), responseStreamFactory); - } - else + try + { + final RetryingTransactionHelper transactionHelper = transactionService.getRetryingTransactionHelper(); + if (script instanceof LoginPost) + { + //login script requires read-write transaction because of authorization interceptor + transactionHelper.setForceWritable(true); + } + transactionHelper.doInTransaction(() -> { + try { if (logger.isDebugEnabled()) - logger.debug("Transactional Response bypassed for ReadWrite - buffersize=0"); - bufferedReq = null; - bufferedRes = null; - } - } - else - { - bufferedReq = null; - bufferedRes = null; - } - - // encapsulate script within transaction - RetryingTransactionCallback work = new RetryingTransactionCallback() - { - public Object execute() throws Exception - { - try + logger.debug("Begin retry transaction block: " + description.getRequiredTransaction() + "," + + description.getRequiredTransactionParameters().getCapability()); + + if (bufferedReq == null || bufferedRes == null) { - if (logger.isDebugEnabled()) - logger.debug("Begin retry transaction block: " + description.getRequiredTransaction() + "," - + description.getRequiredTransactionParameters().getCapability()); - - if (bufferedRes == null) - { - script.execute(scriptReq, scriptRes); - } - else - { - // Reset the request and response in case of a transaction retry - bufferedReq.reset(); - // REPO-4388 don't reset specified headers - bufferedRes.reset(preserveHeadersPattern); - script.execute(bufferedReq, bufferedRes); - } + script.execute(scriptReq, scriptRes); } - catch(Exception e) + else { - if (logger.isDebugEnabled()) - { - logger.debug("Transaction exception: " + description.getRequiredTransaction() + ": " + e.getMessage()); - // Note: user transaction shouldn't be null, but just in case inside this exception handler - UserTransaction userTrx = RetryingTransactionHelper.getActiveUserTransaction(); - if (userTrx != null) - { - logger.debug("Transaction status: " + userTrx.getStatus()); - } - } - + // Reset the request and response in case of a transaction retry + bufferedReq.reset(); + // REPO-4388 don't reset specified headers + bufferedRes.reset(preserveHeadersPattern); + script.execute(bufferedReq, bufferedRes); + } + } + catch (Exception e) + { + if (logger.isDebugEnabled()) + { + logger.debug("Transaction exception: " + description.getRequiredTransaction() + ": " + e.getMessage()); + // Note: user transaction shouldn't be null, but just in case inside this exception handler UserTransaction userTrx = RetryingTransactionHelper.getActiveUserTransaction(); if (userTrx != null) { - if (userTrx.getStatus() != Status.STATUS_MARKED_ROLLBACK) + logger.debug("Transaction status: " + userTrx.getStatus()); + } + } + + final UserTransaction userTrx = RetryingTransactionHelper.getActiveUserTransaction(); + if (userTrx != null) + { + if (userTrx.getStatus() != Status.STATUS_MARKED_ROLLBACK) + { + if (logger.isDebugEnabled()) + logger.debug("Marking web script transaction for rollback"); + try + { + userTrx.setRollbackOnly(); + } + catch (Throwable re) { if (logger.isDebugEnabled()) - logger.debug("Marking web script transaction for rollback"); - try - { - userTrx.setRollbackOnly(); - } - catch(Throwable re) - { - if (logger.isDebugEnabled()) - logger.debug("Caught and ignoring exception during marking for rollback: " + re.getMessage()); - } + logger.debug("Caught and ignoring exception during marking for rollback: " + re.getMessage()); } } - - // re-throw original exception for retry - throw e; } - finally - { - if (logger.isDebugEnabled()) - logger.debug("End retry transaction block: " + description.getRequiredTransaction() + "," - + description.getRequiredTransactionParameters().getCapability()); - } - - return null; - } - }; - - boolean readonly = description.getRequiredTransactionParameters().getCapability() == TransactionCapability.readonly; - boolean requiresNew = description.getRequiredTransaction() == RequiredTransaction.requiresnew; - - // log a warning if we detect a GET webscript being run in a readwrite transaction, GET calls should - // NOT have any side effects so this scenario as a warning sign something maybe amiss, see ALF-10179. - if (logger.isDebugEnabled() && !readonly && "GET".equalsIgnoreCase(description.getMethod())) - { - logger.debug("Webscript with URL '" + scriptReq.getURL() + - "' is a GET request but it's descriptor has declared a readwrite transaction is required"); - } - - try - { - RetryingTransactionHelper transactionHelper = transactionService.getRetryingTransactionHelper(); - if(script instanceof LoginPost) - { - //login script requires read-write transaction because of authorization intercepter - transactionHelper.setForceWritable(true); - } - transactionHelper.doInTransaction(work, readonly, requiresNew); - } - catch (TooBusyException e) - { - // Map TooBusyException to a 503 status code - throw new WebScriptException(HttpServletResponse.SC_SERVICE_UNAVAILABLE, e.getMessage(), e); - } - finally - { - // Get rid of any temporary files - if (bufferedReq != null) - { - bufferedReq.close(); - } - } - // Ensure a response is always flushed after successful execution - if (bufferedRes != null) - { - bufferedRes.writeResponse(); - } - + // re-throw original exception for retry + throw e; + } + finally + { + if (logger.isDebugEnabled()) + logger.debug("End retry transaction block: " + description.getRequiredTransaction() + "," + + description.getRequiredTransactionParameters().getCapability()); + } + + return null; + }, readonly, requiresNew); } - } - catch (IOException ioe) - { - Throwable socketException = ExceptionStackUtil.getCause(ioe, SocketException.class); - Class clientAbortException = null; - try + catch (TooBusyException e) { - clientAbortException = Class.forName("org.apache.catalina.connector.ClientAbortException"); + // Map TooBusyException to a 503 status code + throw new WebScriptException(HttpServletResponse.SC_SERVICE_UNAVAILABLE, e.getMessage(), e); } - catch (ClassNotFoundException e) + + // Ensure a response is always flushed after successful execution + if (bufferedRes != null) { - // do nothing - } - // Note: if you need to look for more exceptions in the stack, then create a static array and pass it in - if ((socketException != null && socketException.getMessage().contains("Broken pipe")) || (clientAbortException != null && ExceptionStackUtil.getCause(ioe, clientAbortException) != null)) - { - if (logger.isDebugEnabled()) - { - logger.warn("Client has cut off communication", ioe); - } - else - { - logger.info("Client has cut off communication"); - } - } - else - { - throw ioe; + bufferedRes.writeResponse(); } } } - + + private static void handleIOException(final IOException ioe) throws IOException + { + Throwable socketException = ExceptionStackUtil.getCause(ioe, SocketException.class); + Class clientAbortException = null; + try + { + clientAbortException = Class.forName("org.apache.catalina.connector.ClientAbortException"); + } + catch (ClassNotFoundException e) + { + // do nothing + } + // Note: if you need to look for more exceptions in the stack, then create a static array and pass it in + if ((socketException != null && socketException.getMessage().contains("Broken pipe")) || + (clientAbortException != null && ExceptionStackUtil.getCause(ioe, clientAbortException) != null)) + { + if (logger.isDebugEnabled()) + { + logger.warn("Client has cut off communication", ioe); + } + else + { + logger.info("Client has cut off communication"); + } + } + else + { + throw ioe; + } + } + /** * Execute script within required level of transaction as required effective user. - * - * @param script WebScript + * + * @param script WebScript * @param scriptReq WebScriptRequest * @param scriptRes WebScriptResponse * @throws IOException @@ -659,22 +657,46 @@ public class RepositoryContainer extends AbstractRuntimeContainer private void transactionedExecuteAs(final WebScript script, final WebScriptRequest scriptReq, final WebScriptResponse scriptRes) throws IOException { - String runAs = script.getDescription().getRunAs(); + final String runAs = script.getDescription().getRunAs(); if (runAs == null) { transactionedExecute(script, scriptReq, scriptRes); } else { - RunAsWork work = new RunAsWork() - { - public Object doWork() throws Exception - { - transactionedExecute(script, scriptReq, scriptRes); - return null; - } - }; - AuthenticationUtil.runAs(work, runAs); + AuthenticationUtil.runAs(() -> { + transactionedExecute(script, scriptReq, scriptRes); + return null; + }, runAs); + } + } + + /** + * Execute script within required level of transaction as required effective user. + * + * @param script WebScript + * @param scriptReq WebScriptRequest + * @param scriptRes WebScriptResponse + * @param requiredAuthentication Required authentication + * @throws IOException + */ + private void transactionedExecuteAs(final WebScript script, final WebScriptRequest scriptReq, + final WebScriptResponse scriptRes, RequiredAuthentication requiredAuthentication) throws IOException + { + // Execute as System if and only if, the current user is a member of System-Admin group, and he is not a super admin. + // E.g. if 'jdoe' is a member of ALFRESCO_SYSTEM_ADMINISTRATORS group, then the work should be executed as System to satisfy the ACL checks. + // But, if the current user is Admin (i.e. super admin, which by default he is a member fo the ALFRESCO_SYSTEM_ADMINISTRATORS group) + // then don't wrap the work as RunAs, since he can do anything! + if (requiredAuthentication == RequiredAuthentication.sysadmin && isSysAdminUser() && !isAdmin()) + { + AuthenticationUtil.runAs(() -> { + transactionedExecute(script, scriptReq, scriptRes); + return null; + }, AuthenticationUtil.SYSTEM_USER_NAME); + } + else + { + transactionedExecuteAs(script, scriptReq, scriptRes); } } @@ -688,17 +710,12 @@ public class RepositoryContainer extends AbstractRuntimeContainer { ContextRefreshedEvent refreshEvent = (ContextRefreshedEvent)event; ApplicationContext refreshContext = refreshEvent.getApplicationContext(); - if (refreshContext != null && refreshContext.equals(applicationContext)) + if (refreshContext.equals(applicationContext)) { - RunAsWork work = new RunAsWork() - { - public Object doWork() throws Exception - { - reset(); - return null; - } - }; - AuthenticationUtil.runAs(work, AuthenticationUtil.getSystemUserName()); + AuthenticationUtil.runAs(() -> { + reset(); + return null; + }, AuthenticationUtil.getSystemUserName()); } } } @@ -739,18 +756,54 @@ public class RepositoryContainer extends AbstractRuntimeContainer @Override public void reset() { - transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() - { - public Object execute() throws Exception - { - internalReset(); - return null; - } + transactionService.getRetryingTransactionHelper().doInTransaction(() -> { + internalReset(); + return null; }, true, false); } - + private void internalReset() { super.reset(); } + + private static BufferedRequest newBufferedRequest( + final RequiredTransactionParameters trxParams, + final WebScriptRequest scriptReq, + final Supplier streamFactory) + { + if (trxParams.getCapability() != TransactionCapability.readwrite) + { + return null; + } + if (trxParams.getBufferSize() <= 0) + { + return null; + } + + // create buffered request that allow transaction retrying + return new BufferedRequest(scriptReq, streamFactory); + } + + private static BufferedResponse newBufferedResponse( + final RequiredTransactionParameters trxParams, + final WebScriptResponse scriptRes, + final Supplier streamFactory) + { + if (trxParams.getCapability() != TransactionCapability.readwrite) + { + return null; + } + if (trxParams.getBufferSize() <= 0) + { + if (logger.isDebugEnabled()) + logger.debug("Transactional Response bypassed for ReadWrite - buffersize=0"); + return null; + } + if (logger.isDebugEnabled()) + logger.debug("Creating Transactional Response for ReadWrite transaction; buffersize=" + trxParams.getBufferSize()); + + // create buffered response that allow transaction retrying + return new BufferedResponse(scriptRes, trxParams.getBufferSize(), streamFactory); + } } diff --git a/remote-api/src/main/java/org/alfresco/repo/web/scripts/TempOutputStream.java b/remote-api/src/main/java/org/alfresco/repo/web/scripts/TempOutputStream.java index e9f6df704e..8ebef82486 100644 --- a/remote-api/src/main/java/org/alfresco/repo/web/scripts/TempOutputStream.java +++ b/remote-api/src/main/java/org/alfresco/repo/web/scripts/TempOutputStream.java @@ -36,6 +36,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.security.Key; +import java.util.function.Supplier; import javax.crypto.Cipher; import javax.crypto.CipherInputStream; @@ -88,13 +89,11 @@ public class TempOutputStream extends OutputStream private final File tempDir; private final int memoryThreshold; private final long maxContentSize; - private boolean encrypt; - private boolean deleteTempFileOnClose; + private final boolean encrypt; private long length = 0; private OutputStream outputStream; private File tempFile; - private TempByteArrayOutputStream tempStream; private Key symKey; private byte[] iv; @@ -112,58 +111,49 @@ public class TempOutputStream extends OutputStream * the max content size in B * @param encrypt * true if temp files should be encrypted - * @param deleteTempFileOnClose - * true if temp files should be deleted on output stream close - * (useful if we need to cache the content for further reads). If - * this is false then we need to make sure we call - * {@link TempOutputStream}.destroy to clean up properly. */ - public TempOutputStream(File tempDir, int memoryThreshold, long maxContentSize, boolean encrypt, boolean deleteTempFileOnClose) + public TempOutputStream(File tempDir, int memoryThreshold, long maxContentSize, boolean encrypt) { this.tempDir = tempDir; this.memoryThreshold = (memoryThreshold < 0) ? DEFAULT_MEMORY_THRESHOLD : memoryThreshold; this.maxContentSize = maxContentSize; this.encrypt = encrypt; - this.deleteTempFileOnClose = deleteTempFileOnClose; - this.tempStream = new TempByteArrayOutputStream(); - this.outputStream = this.tempStream; + this.outputStream = new ByteArrayOutputStream(); } /** * Returns the data as an InputStream */ - public InputStream getInputStream() throws IOException + public InputStream toNewInputStream() throws IOException { - if (tempFile != null) + closeOutputStream(); + + if (tempFile == null) + { + return new ByteArrayInputStream(((ByteArrayOutputStream) outputStream).toByteArray()); + } + if (!encrypt) { - if (encrypt) - { - final Cipher cipher; - try - { - cipher = Cipher.getInstance(TRANSFORMATION); - cipher.init(Cipher.DECRYPT_MODE, symKey, new IvParameterSpec(iv)); - } - catch (Exception e) - { - destroy(); - - if (logger.isErrorEnabled()) - { - logger.error("Cannot initialize decryption cipher", e); - } - - throw new IOException("Cannot initialize decryption cipher", e); - } - - return new BufferedInputStream(new CipherInputStream(new FileInputStream(tempFile), cipher)); - } return new BufferedInputStream(new FileInputStream(tempFile)); } - else + try { - return new ByteArrayInputStream(tempStream.getBuffer(), 0, tempStream.getCount()); + final Cipher cipher = Cipher.getInstance(TRANSFORMATION); + cipher.init(Cipher.DECRYPT_MODE, symKey, new IvParameterSpec(iv)); + + return new BufferedInputStream(new CipherInputStream(new FileInputStream(tempFile), cipher)); + } + catch (Exception e) + { + destroy(); + + if (logger.isErrorEnabled()) + { + logger.error("Cannot initialize decryption cipher", e); + } + + throw new IOException("Cannot initialize decryption cipher", e); } } @@ -190,7 +180,7 @@ public class TempOutputStream extends OutputStream @Override public void close() throws IOException { - close(deleteTempFileOnClose); + closeOutputStream(); } /** @@ -215,7 +205,9 @@ public class TempOutputStream extends OutputStream */ public void destroy() throws IOException { - close(true); + closeOutputStream(); + + deleteTempFile(); } public long getLength() @@ -282,102 +274,95 @@ public class TempOutputStream extends OutputStream } } - private void close(boolean deleteTempFileOnClose) + private BufferedOutputStream createFileOutputStream(final File file) throws IOException { - closeOutputStream(); - - if (deleteTempFileOnClose) + if (!encrypt) { - deleteTempFile(); + return new BufferedOutputStream(new FileOutputStream(file)); } - } - - private BufferedOutputStream createOutputStream(File file) throws IOException - { - BufferedOutputStream fileOutputStream; - if (encrypt) + try { - try + // Generate a symmetric key + final KeyGenerator keyGen = KeyGenerator.getInstance(ALGORITHM); + keyGen.init(KEY_SIZE); + symKey = keyGen.generateKey(); + + Cipher cipher = Cipher.getInstance(TRANSFORMATION); + cipher.init(Cipher.ENCRYPT_MODE, symKey); + + iv = cipher.getIV(); + + return new BufferedOutputStream(new CipherOutputStream(new FileOutputStream(file), cipher)); + } + catch (Exception e) + { + if (logger.isErrorEnabled()) { - // Generate a symmetric key - final KeyGenerator keyGen = KeyGenerator.getInstance(ALGORITHM); - keyGen.init(KEY_SIZE); - symKey = keyGen.generateKey(); - - Cipher cipher = Cipher.getInstance(TRANSFORMATION); - cipher.init(Cipher.ENCRYPT_MODE, symKey); - - iv = cipher.getIV(); - - fileOutputStream = new BufferedOutputStream(new CipherOutputStream(new FileOutputStream(file), cipher)); + logger.error("Cannot initialize encryption cipher", e); } - catch (Exception e) - { - if (logger.isErrorEnabled()) - { - logger.error("Cannot initialize encryption cipher", e); - } - throw new IOException("Cannot initialize encryption cipher", e); - } + throw new IOException("Cannot initialize encryption cipher", e); } - else - { - fileOutputStream = new BufferedOutputStream(new FileOutputStream(file)); - } - - return fileOutputStream; } private void update(int len) throws IOException { - if (maxContentSize > -1 && length + len > maxContentSize) + if (surpassesMaxContentSize(len)) { destroy(); throw new ContentLimitViolationException("Content size violation, limit = " + maxContentSize); } - if (tempFile == null && (tempStream.getCount() + len) > memoryThreshold) + if (surpassesThreshold(len)) { - File file = TempFileProvider.createTempFile(TEMP_FILE_PREFIX, ".bin", tempDir); + tempFile = TempFileProvider.createTempFile(TEMP_FILE_PREFIX, ".bin", tempDir); - BufferedOutputStream fileOutputStream = createOutputStream(file); - fileOutputStream.write(this.tempStream.getBuffer(), 0, this.tempStream.getCount()); + final BufferedOutputStream fileOutputStream = createFileOutputStream(tempFile); + fileOutputStream.write(((ByteArrayOutputStream) outputStream).toByteArray()); fileOutputStream.flush(); try { - tempStream.close(); + outputStream.close(); } - catch (IOException e) + catch (IOException ignore) { // Ignore exception } - tempStream = null; - tempFile = file; outputStream = fileOutputStream; } length += len; } - private static class TempByteArrayOutputStream extends ByteArrayOutputStream + private boolean surpassesMaxContentSize(final int len) { - /** - * @return The internal buffer where data is stored - */ - public byte[] getBuffer() - { - return buf; - } + return maxContentSize >= 0 && length + len > maxContentSize; + } - /** - * @return The number of valid bytes in the buffer. - */ - public int getCount() - { - return count; - } + private boolean surpassesThreshold(final int len) + { + return tempFile == null && length + len > memoryThreshold; + } + + /** + * Creates a {@link TempOutputStream} factory/supplier. + * + * @param tempDir + * the temporary directory, i.e. isDir == true, that + * will be used as * parent directory for creating temp file backed + * streams + * @param memoryThreshold + * the memory threshold in B + * @param maxContentSize + * the max content size in B + * @param encrypt + * true if temp files should be encrypted + */ + public static Supplier factory(final File tempDir, final int memoryThreshold, + final long maxContentSize, final boolean encrypt) + { + return () -> new TempOutputStream(tempDir, memoryThreshold, maxContentSize, encrypt); } } diff --git a/remote-api/src/main/java/org/alfresco/repo/web/scripts/TempOutputStreamFactory.java b/remote-api/src/main/java/org/alfresco/repo/web/scripts/TempOutputStreamFactory.java deleted file mode 100644 index 2300bb8b03..0000000000 --- a/remote-api/src/main/java/org/alfresco/repo/web/scripts/TempOutputStreamFactory.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * #%L - * Alfresco Remote API - * %% - * Copyright (C) 2005 - 2019 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 . - * #L% - */ -package org.alfresco.repo.web.scripts; - -import java.io.File; - -/** - * Factory for {@link TempOutputStream} - */ -public class TempOutputStreamFactory -{ - /** - * A temporary directory, i.e. isDir == true, that will be used as - * parent directory for creating temp file backed streams. - */ - private final File tempDir; - private int memoryThreshold; - private long maxContentSize; - private boolean encrypt; - private boolean deleteTempFileOnClose; - - /** - * Creates a {@link TempOutputStream} factory. - * - * @param tempDir - * the temporary directory, i.e. isDir == true, that - * will be used as * parent directory for creating temp file backed - * streams - * @param memoryThreshold - * the memory threshold in B - * @param maxContentSize - * the max content size in B - * @param encrypt - * true if temp files should be encrypted - * @param deleteTempFileOnClose - * true if temp files should be deleted on output stream close - * (useful if we need to cache the content for further reads). If - * this is false then we need to make sure we call - * {@link TempOutputStream}.destroy to clean up properly. - */ - public TempOutputStreamFactory(File tempDir, int memoryThreshold, long maxContentSize, boolean encrypt, boolean deleteTempFileOnClose) - { - this.tempDir = tempDir; - this.memoryThreshold = memoryThreshold; - this.maxContentSize = maxContentSize; - this.encrypt = encrypt; - this.deleteTempFileOnClose = deleteTempFileOnClose; - } - - /** - * Creates a new {@link TempOutputStream} object - */ - public TempOutputStream createOutputStream() - { - return new TempOutputStream(tempDir, memoryThreshold, maxContentSize, encrypt, deleteTempFileOnClose); - } - - public File getTempDir() - { - return tempDir; - } - - public int getMemoryThreshold() - { - return memoryThreshold; - } - - public long getMaxContentSize() - { - return maxContentSize; - } - - public boolean isEncrypt() - { - return encrypt; - } - - public boolean isDeleteTempFileOnClose() - { - return deleteTempFileOnClose; - } -} diff --git a/remote-api/src/main/java/org/alfresco/repo/web/scripts/bulkimport/AbstractBulkFileSystemImportWebScript.java b/remote-api/src/main/java/org/alfresco/repo/web/scripts/bulkimport/AbstractBulkFileSystemImportWebScript.java index 3e98a9d142..08eb24db23 100644 --- a/remote-api/src/main/java/org/alfresco/repo/web/scripts/bulkimport/AbstractBulkFileSystemImportWebScript.java +++ b/remote-api/src/main/java/org/alfresco/repo/web/scripts/bulkimport/AbstractBulkFileSystemImportWebScript.java @@ -30,8 +30,19 @@ import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Arrays; import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.OptionalInt; +import java.util.function.Function; +import java.util.function.Supplier; + +import com.google.common.primitives.Ints; import org.alfresco.repo.bulkimport.BulkFilesystemImporter; +import org.alfresco.repo.bulkimport.BulkImportParameters; +import org.alfresco.repo.bulkimport.NodeImporter; +import org.alfresco.repo.bulkimport.impl.MultiThreadedBulkFilesystemImporter; import org.alfresco.repo.model.Repository; import org.alfresco.service.cmr.model.FileFolderService; import org.alfresco.service.cmr.model.FileInfo; @@ -39,8 +50,12 @@ import org.alfresco.service.cmr.model.FileNotFoundException; import org.alfresco.service.cmr.repository.NodeRef; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.extensions.surf.util.I18NUtil; +import org.springframework.extensions.webscripts.Cache; import org.springframework.extensions.webscripts.DeclarativeWebScript; +import org.springframework.extensions.webscripts.Status; import org.springframework.extensions.webscripts.WebScriptException; +import org.springframework.extensions.webscripts.WebScriptRequest; /** * contains common fields and methods for the import web scripts. @@ -60,10 +75,10 @@ public class AbstractBulkFileSystemImportWebScript extends DeclarativeWebScript // Web scripts parameters (common) protected static final String PARAMETER_REPLACE_EXISTING = "replaceExisting"; protected static final String PARAMETER_EXISTING_FILE_MODE = "existingFileMode"; - protected static final String PARAMETER_VALUE_REPLACE_EXISTING = "replaceExisting"; + protected static final String PARAMETER_VALUE_REPLACE_EXISTING = "true"; protected static final String PARAMETER_SOURCE_DIRECTORY = "sourceDirectory"; protected static final String PARAMETER_DISABLE_RULES = "disableRules"; - protected static final String PARAMETER_VALUE_DISABLE_RULES = "disableRules"; + protected static final String PARAMETER_VALUE_DISABLE_RULES = "true"; protected static final String IMPORT_ALREADY_IN_PROGRESS_MODEL_KEY = "importInProgress"; protected static final String IMPORT_ALREADY_IN_PROGRESS_ERROR_KEY ="bfsit.error.importAlreadyInProgress"; @@ -75,7 +90,7 @@ public class AbstractBulkFileSystemImportWebScript extends DeclarativeWebScript protected Repository repository; protected volatile boolean importInProgress; - + protected NodeRef getTargetNodeRef(String targetNodeRefStr, String targetPath) throws FileNotFoundException { NodeRef targetNodeRef; @@ -219,4 +234,198 @@ public class AbstractBulkFileSystemImportWebScript extends DeclarativeWebScript this.repository = repository; } + protected class MultithreadedImportWebScriptLogic + { + private final MultiThreadedBulkFilesystemImporter bulkImporter; + private final Supplier nodeImporterFactory; + private final WebScriptRequest request; + private final Status status; + private final Cache cache; + + public MultithreadedImportWebScriptLogic(MultiThreadedBulkFilesystemImporter bulkImporter, Supplier nodeImporterFactory, WebScriptRequest request, Status status, Cache cache) + { + this.bulkImporter = Objects.requireNonNull(bulkImporter); + this.nodeImporterFactory = Objects.requireNonNull(nodeImporterFactory); + this.request = Objects.requireNonNull(request); + this.status = Objects.requireNonNull(status); + this.cache = Objects.requireNonNull(cache); + } + + public Map executeImport() + { + Map model = new HashMap<>(); + cache.setNeverCache(true); + String targetPath = null; + + try + { + targetPath = request.getParameter(PARAMETER_TARGET_PATH); + if (isRunning()) + { + model.put(IMPORT_ALREADY_IN_PROGRESS_MODEL_KEY, I18NUtil.getMessage(IMPORT_ALREADY_IN_PROGRESS_ERROR_KEY)); + return model; + } + + final BulkImportParameters bulkImportParameters = getBulkImportParameters(); + final NodeImporter nodeImporter = nodeImporterFactory.get(); + + bulkImporter.asyncBulkImport(bulkImportParameters, nodeImporter); + + waitForImportToBegin(); + + // redirect to the status Web Script + status.setCode(Status.STATUS_MOVED_TEMPORARILY); + status.setRedirect(true); + status.setLocation(request.getServiceContextPath() + WEB_SCRIPT_URI_BULK_FILESYSTEM_IMPORT_STATUS); + } + catch (WebScriptException | IllegalArgumentException e) + { + status.setCode(Status.STATUS_BAD_REQUEST, e.getMessage()); + status.setRedirect(true); + } + catch (FileNotFoundException fnfe) + { + status.setCode(Status.STATUS_BAD_REQUEST,"The repository path '" + targetPath + "' does not exist !"); + status.setRedirect(true); + } + catch (Throwable t) + { + throw new WebScriptException(Status.STATUS_INTERNAL_SERVER_ERROR, buildTextMessage(t), t); + } + + return model; + } + + private void waitForImportToBegin() throws InterruptedException + { + // ACE-3047 fix, since bulk import is started asynchronously there is a chance that client + // will get into the status page before import is actually started. + // In this case wrong information (for previous import) will be displayed. + // So lets ensure that import started before redirecting client to status page. + int i = 0; + while (!bulkImporter.getStatus().inProgress() && i < 10) + { + Thread.sleep(100); + i++; + } + } + + private BulkImportParameters getBulkImportParameters() throws FileNotFoundException + { + final BulkImportParametersExtractor extractor = new BulkImportParametersExtractor(request::getParameter, + AbstractBulkFileSystemImportWebScript.this::getTargetNodeRef, + bulkImporter.getDefaultBatchSize(), + bulkImporter.getDefaultNumThreads()); + return extractor.extract(); + } + + private boolean isRunning() + { + return bulkImporter.getStatus().inProgress(); + } + } + + protected static class BulkImportParametersExtractor + { + private final Function paramsProvider; + private final NodeRefCreator nodeRefCreator; + private final int defaultBatchSize; + private final int defaultNumThreads; + + public BulkImportParametersExtractor(final Function paramsProvider, final NodeRefCreator nodeRefCreator, + final int defaultBatchSize, final int defaultNumThreads) + { + this.paramsProvider = Objects.requireNonNull(paramsProvider); + this.nodeRefCreator = Objects.requireNonNull(nodeRefCreator); + this.defaultBatchSize = defaultBatchSize; + this.defaultNumThreads = defaultNumThreads; + } + + public BulkImportParameters extract() throws FileNotFoundException + { + BulkImportParameters result = new BulkImportParameters(); + + result.setTarget(getTargetNodeRef()); + setExistingFileMode(result); + result.setNumThreads(getOptionalPositiveInteger(PARAMETER_NUM_THREADS).orElse(defaultNumThreads)); + result.setBatchSize(getOptionalPositiveInteger(PARAMETER_BATCH_SIZE).orElse(defaultBatchSize)); + setDisableRules(result); + + return result; + } + + private void setExistingFileMode(BulkImportParameters params) + { + String replaceExistingStr = getParamStringValue(PARAMETER_REPLACE_EXISTING); + String existingFileModeStr = getParamStringValue(PARAMETER_EXISTING_FILE_MODE); + + if (!isNullOrEmpty(replaceExistingStr) && !isNullOrEmpty(existingFileModeStr)) + { + // Check that we haven't had both the deprecated and new (existingFileMode) + // parameters supplied. + throw new IllegalStateException( + String.format("Only one of these parameters may be used, not both: %s, %s", + PARAMETER_REPLACE_EXISTING, + PARAMETER_EXISTING_FILE_MODE)); + } + + if (!isNullOrEmpty(existingFileModeStr)) + { + params.setExistingFileMode(BulkImportParameters.ExistingFileMode.valueOf(existingFileModeStr)); + } + else + { + params.setReplaceExisting(PARAMETER_VALUE_REPLACE_EXISTING.equals(replaceExistingStr)); + } + } + + private void setDisableRules(final BulkImportParameters params) + { + final String disableRulesStr = getParamStringValue(PARAMETER_DISABLE_RULES); + params.setDisableRulesService(!isNullOrEmpty(disableRulesStr) && PARAMETER_VALUE_DISABLE_RULES.equals(disableRulesStr)); + } + + private NodeRef getTargetNodeRef() throws FileNotFoundException + { + String targetNodeRefStr = getParamStringValue(PARAMETER_TARGET_NODEREF); + String targetPath = getParamStringValue(PARAMETER_TARGET_PATH); + return nodeRefCreator.fromNodeRefAndPath(targetNodeRefStr, targetPath); + } + + private OptionalInt getOptionalPositiveInteger(final String paramName) + { + final String strValue = getParamStringValue(paramName); + if (isNullOrEmpty(strValue)) + { + return OptionalInt.empty(); + } + + final Integer asInt = Ints.tryParse(strValue); + if (asInt == null || asInt < 1) + { + throw new WebScriptException("Error: parameter '" + paramName + "' must be an integer > 0."); + } + + return OptionalInt.of(asInt); + } + + private String getParamStringValue(String paramName) + { + Objects.requireNonNull(paramName); + + return paramsProvider.apply(paramName); + } + + private boolean isNullOrEmpty(String str) + { + return str == null || str.trim().length() == 0; + } + + @FunctionalInterface + protected interface NodeRefCreator + { + NodeRef fromNodeRefAndPath(String nodeRef, String path) throws FileNotFoundException; + } + } + } \ No newline at end of file diff --git a/remote-api/src/main/java/org/alfresco/repo/web/scripts/bulkimport/copy/BulkFilesystemImportWebScript.java b/remote-api/src/main/java/org/alfresco/repo/web/scripts/bulkimport/copy/BulkFilesystemImportWebScript.java index ef7461193d..a0b692422a 100644 --- a/remote-api/src/main/java/org/alfresco/repo/web/scripts/bulkimport/copy/BulkFilesystemImportWebScript.java +++ b/remote-api/src/main/java/org/alfresco/repo/web/scripts/bulkimport/copy/BulkFilesystemImportWebScript.java @@ -27,17 +27,12 @@ package org.alfresco.repo.web.scripts.bulkimport.copy; import java.io.File; -import java.util.HashMap; import java.util.Map; -import org.alfresco.repo.bulkimport.BulkImportParameters; import org.alfresco.repo.bulkimport.NodeImporter; import org.alfresco.repo.bulkimport.impl.MultiThreadedBulkFilesystemImporter; import org.alfresco.repo.bulkimport.impl.StreamingNodeImporterFactory; import org.alfresco.repo.web.scripts.bulkimport.AbstractBulkFileSystemImportWebScript; -import org.alfresco.service.cmr.model.FileNotFoundException; -import org.alfresco.service.cmr.repository.NodeRef; -import org.springframework.extensions.surf.util.I18NUtil; import org.springframework.extensions.webscripts.Cache; import org.springframework.extensions.webscripts.Status; import org.springframework.extensions.webscripts.WebScriptException; @@ -69,170 +64,22 @@ public class BulkFilesystemImportWebScript extends AbstractBulkFileSystemImportW @Override protected Map executeImpl(final WebScriptRequest request, final Status status, final Cache cache) { - Map model = new HashMap(); - String targetNodeRefStr = null; - String targetPath = null; - String sourceDirectoryStr = null; - @Deprecated String replaceExistingStr = null; - String existingFileModeStr = null; - String batchSizeStr = null; - String numThreadsStr = null; - String disableRulesStr = null; + final MultithreadedImportWebScriptLogic importLogic = new MultithreadedImportWebScriptLogic(bulkImporter, + () -> createNodeImporter(request), request, status, cache); + return importLogic.executeImport(); + } - cache.setNeverCache(true); - - try + private NodeImporter createNodeImporter(WebScriptRequest request) + { + final String sourceDirectoryStr = request.getParameter(PARAMETER_SOURCE_DIRECTORY); + if (sourceDirectoryStr == null || sourceDirectoryStr.trim().length() == 0) { - if(!bulkImporter.getStatus().inProgress()) - { - NodeRef targetNodeRef = null; - File sourceDirectory = null; - boolean replaceExisting = false; - BulkImportParameters.ExistingFileMode existingFileMode = null; - int batchSize = bulkImporter.getDefaultBatchSize(); - int numThreads = bulkImporter.getDefaultNumThreads(); - boolean disableRules = false; - - // Retrieve, validate and convert parameters - targetNodeRefStr = request.getParameter(PARAMETER_TARGET_NODEREF); - targetPath = request.getParameter(PARAMETER_TARGET_PATH); - sourceDirectoryStr = request.getParameter(PARAMETER_SOURCE_DIRECTORY); - replaceExistingStr = request.getParameter(PARAMETER_REPLACE_EXISTING); - existingFileModeStr = request.getParameter(PARAMETER_EXISTING_FILE_MODE); - - batchSizeStr = request.getParameter(PARAMETER_BATCH_SIZE); - numThreadsStr = request.getParameter(PARAMETER_NUM_THREADS); - disableRulesStr = request.getParameter(PARAMETER_DISABLE_RULES); - - targetNodeRef = getTargetNodeRef(targetNodeRefStr, targetPath); - - if (sourceDirectoryStr == null || sourceDirectoryStr.trim().length() == 0) - { - throw new RuntimeException("Error: mandatory parameter '" + PARAMETER_SOURCE_DIRECTORY + "' was not provided."); - } - - sourceDirectory = new File(sourceDirectoryStr.trim()); - - if (replaceExistingStr != null && existingFileModeStr != null) - { - // Check that we haven't had both the deprecated and new (existingFileMode) - // parameters supplied. - throw new IllegalStateException( - String.format("Only one of these parameters may be used, not both: %s, %s", - PARAMETER_REPLACE_EXISTING, - PARAMETER_EXISTING_FILE_MODE)); - } - - if (replaceExistingStr != null && replaceExistingStr.trim().length() > 0) - { - replaceExisting = PARAMETER_VALUE_REPLACE_EXISTING.equals(replaceExistingStr); - } - - if (existingFileModeStr != null && existingFileModeStr.trim().length() > 0) - { - existingFileMode = BulkImportParameters.ExistingFileMode.valueOf(existingFileModeStr); - } - - if (disableRulesStr != null && disableRulesStr.trim().length() > 0) - { - disableRules = PARAMETER_VALUE_DISABLE_RULES.equals(disableRulesStr); - } - - // Initiate the import - NodeImporter nodeImporter = nodeImporterFactory.getNodeImporter(sourceDirectory); - BulkImportParameters bulkImportParameters = new BulkImportParameters(); - - if (numThreadsStr != null && numThreadsStr.trim().length() > 0) - { - try - { - numThreads = Integer.parseInt(numThreadsStr); - if(numThreads < 1) - { - throw new RuntimeException("Error: parameter '" + PARAMETER_NUM_THREADS + "' must be an integer > 0."); - } - bulkImportParameters.setNumThreads(numThreads); - } - catch(NumberFormatException e) - { - throw new RuntimeException("Error: parameter '" + PARAMETER_NUM_THREADS + "' must be an integer > 0."); - } - } - - if (batchSizeStr != null && batchSizeStr.trim().length() > 0) - { - try - { - batchSize = Integer.parseInt(batchSizeStr); - if(batchSize < 1) - { - throw new RuntimeException("Error: parameter '" + PARAMETER_BATCH_SIZE + "' must be an integer > 0."); - } - bulkImportParameters.setBatchSize(batchSize); - } - catch(NumberFormatException e) - { - throw new RuntimeException("Error: parameter '" + PARAMETER_BATCH_SIZE + "' must be an integer > 0."); - } - } - - if (existingFileMode != null) - { - bulkImportParameters.setExistingFileMode(existingFileMode); - } - else - { - // Fall back to the old/deprecated way. - bulkImportParameters.setReplaceExisting(replaceExisting); - } - - bulkImportParameters.setTarget(targetNodeRef); - bulkImportParameters.setDisableRulesService(disableRules); - - bulkImporter.asyncBulkImport(bulkImportParameters, nodeImporter); - - // ACE-3047 fix, since bulk import is started asynchronously there is a chance that client - // will get into the status page before import is actually started. - // In this case wrong information (for previous import) will be displayed. - // So lets ensure that import started before redirecting client to status page. - int i = 0; - while (!bulkImporter.getStatus().inProgress() && i < 10) - { - Thread.sleep(100); - i++; - } - - // redirect to the status Web Script - status.setCode(Status.STATUS_MOVED_TEMPORARILY); - status.setRedirect(true); - status.setLocation(request.getServiceContextPath() + WEB_SCRIPT_URI_BULK_FILESYSTEM_IMPORT_STATUS); - } - else - { - model.put(IMPORT_ALREADY_IN_PROGRESS_MODEL_KEY, I18NUtil.getMessage(IMPORT_ALREADY_IN_PROGRESS_ERROR_KEY)); - } + throw new WebScriptException("Error: mandatory parameter '" + PARAMETER_SOURCE_DIRECTORY + "' was not provided."); } - catch (WebScriptException wse) - { - status.setCode(Status.STATUS_BAD_REQUEST, wse.getMessage()); - status.setRedirect(true); - } - catch (FileNotFoundException fnfe) - { - status.setCode(Status.STATUS_BAD_REQUEST,"The repository path '" + targetPath + "' does not exist !"); - status.setRedirect(true); - } - catch(IllegalArgumentException iae) - { - status.setCode(Status.STATUS_BAD_REQUEST,iae.getMessage()); - status.setRedirect(true); - } - catch (Throwable t) - { - throw new WebScriptException(Status.STATUS_INTERNAL_SERVER_ERROR, buildTextMessage(t), t); - } - - return model; + + final File sourceDirectory = new File(sourceDirectoryStr.trim()); + + return nodeImporterFactory.getNodeImporter(sourceDirectory); } } diff --git a/remote-api/src/main/java/org/alfresco/repo/web/scripts/model/filefolder/FileFolderLoaderPost.java b/remote-api/src/main/java/org/alfresco/repo/web/scripts/model/filefolder/FileFolderLoaderPost.java index 188353b4c2..afd1e1278e 100644 --- a/remote-api/src/main/java/org/alfresco/repo/web/scripts/model/filefolder/FileFolderLoaderPost.java +++ b/remote-api/src/main/java/org/alfresco/repo/web/scripts/model/filefolder/FileFolderLoaderPost.java @@ -23,149 +23,144 @@ * along with Alfresco. If not, see . * #L% */ -package org.alfresco.repo.web.scripts.model.filefolder; - -import java.io.IOException; -import java.io.OutputStream; - -import org.alfresco.repo.model.filefolder.FileFolderLoader; -import org.alfresco.service.cmr.model.FileNotFoundException; -import org.json.JSONException; -import org.json.JSONObject; -import org.json.JSONTokener; -import org.springframework.beans.BeansException; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.springframework.extensions.webscripts.AbstractWebScript; -import org.springframework.extensions.webscripts.Status; -import org.springframework.extensions.webscripts.WebScriptException; -import org.springframework.extensions.webscripts.WebScriptRequest; -import org.springframework.extensions.webscripts.WebScriptResponse; - -/** - * Link to {@link FileFolderLoader} - */ -public class FileFolderLoaderPost extends AbstractWebScript implements ApplicationContextAware -{ - public static final String KEY_FOLDER_PATH = "folderPath"; - public static final String KEY_FILE_COUNT = "fileCount"; - public static final String KEY_FILES_PER_TXN = "filesPerTxn"; - public static final String KEY_MIN_FILE_SIZE = "minFileSize"; - public static final String KEY_MAX_FILE_SIZE = "maxFileSize"; - public static final String KEY_MAX_UNIQUE_DOCUMENTS = "maxUniqueDocuments"; - public static final String KEY_FORCE_BINARY_STORAGE = "forceBinaryStorage"; - public static final String KEY_DESCRIPTION_COUNT = "descriptionCount"; - public static final String KEY_DESCRIPTION_SIZE = "descriptionSize"; - public static final String KEY_COUNT = "count"; - - public static final int DEFAULT_FILE_COUNT = 100; - public static final int DEFAULT_FILES_PER_TXN = 100; - public static final long DEFAULT_MIN_FILE_SIZE = 80*1024L; - public static final long DEFAULT_MAX_FILE_SIZE = 120*1024L; - public static final long DEFAULT_MAX_UNIQUE_DOCUMENTS = Long.MAX_VALUE; - public static final int DEFAULT_DESCRIPTION_COUNT = 1; - public static final long DEFAULT_DESCRIPTION_SIZE = 128L; - public static final boolean DEFAULT_FORCE_BINARY_STORAGE = false; - - private ApplicationContext applicationContext; - - @Override - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException - { - this.applicationContext = applicationContext; - } - - public void execute(WebScriptRequest req, WebScriptResponse res) throws IOException - { - FileFolderLoader loader = (FileFolderLoader) applicationContext.getBean("fileFolderLoader"); - - int count = 0; - String folderPath = ""; - try - { - JSONObject json = new JSONObject(new JSONTokener(req.getContent().getContent())); - folderPath = json.getString(KEY_FOLDER_PATH); - if (folderPath == null) - { - throw new WebScriptException(Status.STATUS_BAD_REQUEST, KEY_FOLDER_PATH + " not supplied."); - } - int fileCount = 100; - if (json.has(KEY_FILE_COUNT)) - { - fileCount = json.getInt(KEY_FILE_COUNT); - } - int filesPerTxn = DEFAULT_FILES_PER_TXN; - if (json.has(KEY_FILES_PER_TXN)) - { - filesPerTxn = json.getInt(KEY_FILES_PER_TXN); - } - long minFileSize = DEFAULT_MIN_FILE_SIZE; - if (json.has(KEY_MIN_FILE_SIZE)) - { - minFileSize = json.getInt(KEY_MIN_FILE_SIZE); - } - long maxFileSize = DEFAULT_MAX_FILE_SIZE; - if (json.has(KEY_MAX_FILE_SIZE)) - { - maxFileSize = json.getInt(KEY_MAX_FILE_SIZE); - } - long maxUniqueDocuments = DEFAULT_MAX_UNIQUE_DOCUMENTS; - if (json.has(KEY_MAX_UNIQUE_DOCUMENTS)) - { - maxUniqueDocuments = json.getInt(KEY_MAX_UNIQUE_DOCUMENTS); - } - boolean forceBinaryStorage = DEFAULT_FORCE_BINARY_STORAGE; - if (json.has(KEY_FORCE_BINARY_STORAGE)) - { - forceBinaryStorage = json.getBoolean(KEY_FORCE_BINARY_STORAGE); - } - int descriptionCount = DEFAULT_DESCRIPTION_COUNT; - if (json.has(KEY_DESCRIPTION_COUNT)) - { - descriptionCount = json.getInt(KEY_DESCRIPTION_COUNT); - } - long descriptionSize = DEFAULT_DESCRIPTION_SIZE; - if (json.has(KEY_DESCRIPTION_SIZE)) - { - descriptionSize = json.getLong(KEY_DESCRIPTION_SIZE); - } - - // Perform the load - count = loader.createFiles( - folderPath, - fileCount, filesPerTxn, - minFileSize, maxFileSize, - maxUniqueDocuments, - forceBinaryStorage, - descriptionCount, descriptionSize); - } - catch (FileNotFoundException e) - { - throw new WebScriptException(Status.STATUS_NOT_FOUND, "Folder not found: ", folderPath); - } - catch (IOException iox) - { - throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Could not read content from req.", iox); - } - catch (JSONException je) - { - throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Could not parse JSON from req.", je); - } - // Write the response - OutputStream os = res.getOutputStream(); - try - { - JSONObject json = new JSONObject(); - json.put(KEY_COUNT, count); - os.write(json.toString().getBytes("UTF-8")); - } - catch (JSONException e) - { - throw new WebScriptException(Status.STATUS_INTERNAL_SERVER_ERROR, "Failed to write JSON", e); - } - finally - { - os.close(); - } - } -} +package org.alfresco.repo.web.scripts.model.filefolder; + +import java.io.IOException; +import java.io.OutputStream; + +import org.alfresco.repo.model.filefolder.FileFolderLoader; +import org.alfresco.service.cmr.model.FileNotFoundException; +import org.json.JSONException; +import org.json.JSONObject; +import org.json.JSONTokener; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.extensions.webscripts.AbstractWebScript; +import org.springframework.extensions.webscripts.Status; +import org.springframework.extensions.webscripts.WebScriptException; +import org.springframework.extensions.webscripts.WebScriptRequest; +import org.springframework.extensions.webscripts.WebScriptResponse; + +/** + * Link to {@link FileFolderLoader} + */ +public class FileFolderLoaderPost extends AbstractWebScript implements ApplicationContextAware +{ + public static final String KEY_FOLDER_PATH = "folderPath"; + public static final String KEY_FILE_COUNT = "fileCount"; + public static final String KEY_FILES_PER_TXN = "filesPerTxn"; + public static final String KEY_MIN_FILE_SIZE = "minFileSize"; + public static final String KEY_MAX_FILE_SIZE = "maxFileSize"; + public static final String KEY_MAX_UNIQUE_DOCUMENTS = "maxUniqueDocuments"; + public static final String KEY_FORCE_BINARY_STORAGE = "forceBinaryStorage"; + public static final String KEY_DESCRIPTION_COUNT = "descriptionCount"; + public static final String KEY_DESCRIPTION_SIZE = "descriptionSize"; + public static final String KEY_COUNT = "count"; + + public static final int DEFAULT_FILE_COUNT = 100; + public static final int DEFAULT_FILES_PER_TXN = 100; + public static final long DEFAULT_MIN_FILE_SIZE = 80*1024L; + public static final long DEFAULT_MAX_FILE_SIZE = 120*1024L; + public static final long DEFAULT_MAX_UNIQUE_DOCUMENTS = Long.MAX_VALUE; + public static final int DEFAULT_DESCRIPTION_COUNT = 1; + public static final long DEFAULT_DESCRIPTION_SIZE = 128L; + public static final boolean DEFAULT_FORCE_BINARY_STORAGE = false; + + private ApplicationContext applicationContext; + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException + { + this.applicationContext = applicationContext; + } + + public void execute(WebScriptRequest req, WebScriptResponse res) throws IOException + { + FileFolderLoader loader = (FileFolderLoader) applicationContext.getBean("fileFolderLoader"); + + int count = 0; + String folderPath = ""; + try + { + JSONObject json = new JSONObject(new JSONTokener(req.getContent().getContent())); + folderPath = json.getString(KEY_FOLDER_PATH); + if (folderPath == null) + { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, KEY_FOLDER_PATH + " not supplied."); + } + int fileCount = 100; + if (json.has(KEY_FILE_COUNT)) + { + fileCount = json.getInt(KEY_FILE_COUNT); + } + int filesPerTxn = DEFAULT_FILES_PER_TXN; + if (json.has(KEY_FILES_PER_TXN)) + { + filesPerTxn = json.getInt(KEY_FILES_PER_TXN); + } + long minFileSize = DEFAULT_MIN_FILE_SIZE; + if (json.has(KEY_MIN_FILE_SIZE)) + { + minFileSize = json.getInt(KEY_MIN_FILE_SIZE); + } + long maxFileSize = DEFAULT_MAX_FILE_SIZE; + if (json.has(KEY_MAX_FILE_SIZE)) + { + maxFileSize = json.getInt(KEY_MAX_FILE_SIZE); + } + long maxUniqueDocuments = DEFAULT_MAX_UNIQUE_DOCUMENTS; + if (json.has(KEY_MAX_UNIQUE_DOCUMENTS)) + { + maxUniqueDocuments = json.getInt(KEY_MAX_UNIQUE_DOCUMENTS); + } + boolean forceBinaryStorage = DEFAULT_FORCE_BINARY_STORAGE; + if (json.has(KEY_FORCE_BINARY_STORAGE)) + { + forceBinaryStorage = json.getBoolean(KEY_FORCE_BINARY_STORAGE); + } + int descriptionCount = DEFAULT_DESCRIPTION_COUNT; + if (json.has(KEY_DESCRIPTION_COUNT)) + { + descriptionCount = json.getInt(KEY_DESCRIPTION_COUNT); + } + long descriptionSize = DEFAULT_DESCRIPTION_SIZE; + if (json.has(KEY_DESCRIPTION_SIZE)) + { + descriptionSize = json.getLong(KEY_DESCRIPTION_SIZE); + } + + // Perform the load + count = loader.createFiles( + folderPath, + fileCount, filesPerTxn, + minFileSize, maxFileSize, + maxUniqueDocuments, + forceBinaryStorage, + descriptionCount, descriptionSize); + } + catch (FileNotFoundException e) + { + throw new WebScriptException(Status.STATUS_NOT_FOUND, "Folder not found: ", folderPath); + } + catch (IOException iox) + { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Could not read content from req.", iox); + } + catch (JSONException je) + { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Could not parse JSON from req.", je); + } + // Write the response + try (OutputStream os = res.getOutputStream()) + { + JSONObject json = new JSONObject(); + json.put(KEY_COUNT, count); + os.write(json.toString().getBytes("UTF-8")); + } + catch (JSONException e) + { + throw new WebScriptException(Status.STATUS_INTERNAL_SERVER_ERROR, "Failed to write JSON", e); + } + } +} diff --git a/remote-api/src/main/java/org/alfresco/repo/web/scripts/transfer/PostSnapshotCommandProcessor.java b/remote-api/src/main/java/org/alfresco/repo/web/scripts/transfer/PostSnapshotCommandProcessor.java index afe7a29b94..4b756df5d3 100644 --- a/remote-api/src/main/java/org/alfresco/repo/web/scripts/transfer/PostSnapshotCommandProcessor.java +++ b/remote-api/src/main/java/org/alfresco/repo/web/scripts/transfer/PostSnapshotCommandProcessor.java @@ -125,16 +125,15 @@ public class PostSnapshotCommandProcessor implements CommandProcessor logger.debug("success"); resp.setStatus(Status.STATUS_OK); - - OutputStream out = resp.getOutputStream(); - resp.setContentType("text/xml"); - resp.setContentEncoding("utf-8"); - - receiver.generateRequsite(transferId, out); - - out.close(); - - } + + try (OutputStream out = resp.getOutputStream()) + { + resp.setContentType("text/xml"); + resp.setContentEncoding("utf-8"); + + receiver.generateRequsite(transferId, out); + } + } catch (Exception ex) { logger.debug("exception caught", ex); diff --git a/remote-api/src/main/java/org/alfresco/rest/api/DeletedNodes.java b/remote-api/src/main/java/org/alfresco/rest/api/DeletedNodes.java index 1b2a568e4d..09609ae77d 100644 --- a/remote-api/src/main/java/org/alfresco/rest/api/DeletedNodes.java +++ b/remote-api/src/main/java/org/alfresco/rest/api/DeletedNodes.java @@ -2,7 +2,7 @@ * #%L * Alfresco Remote API * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited + * 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 @@ -34,6 +34,7 @@ import org.alfresco.rest.api.model.UserInfo; import org.alfresco.rest.framework.resource.content.BinaryResource; import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo; import org.alfresco.rest.framework.resource.parameters.Parameters; +import org.alfresco.service.cmr.repository.DirectAccessUrl; /** * Handles trashcan / deleted nodes @@ -99,4 +100,29 @@ public interface DeletedNodes * @return */ CollectionWithPagingInfo getRenditions(String archivedId, Parameters parameters); + + /** + * Gets a presigned URL to directly access content. + * + * @param archivedId The node id for which to obtain the direct access {@code URL} + * @param renditionId The rendition id for which to obtain the direct access {@code URL} + * @param attachment {@code true} if an attachment {@code URL} is requested, {@code false} for an embedded {@code URL}, {@code true} by default. + * @return A direct access {@code URL} object for the content. + */ + default DirectAccessUrl requestContentDirectUrl(String archivedId, String renditionId, boolean attachment) + { + return requestContentDirectUrl(archivedId, renditionId, attachment, null); + } + + /** + * Gets a presigned URL to directly access content. + * + * @param archivedId The node id for which to obtain the direct access {@code URL} + * @param renditionId The rendition id for which to obtain the direct access {@code URL} + * @param attachment {@code true} if an attachment {@code URL} is requested, {@code false} for an embedded {@code URL}, {@code true} by default. + * @param validFor The time at which the direct access {@code URL} will expire. + * @return A direct access {@code URL} object for the content. + */ + DirectAccessUrl requestContentDirectUrl(String archivedId, String renditionId, boolean attachment, Long validFor); + } diff --git a/remote-api/src/main/java/org/alfresco/rest/api/DirectAccessUrlHelper.java b/remote-api/src/main/java/org/alfresco/rest/api/DirectAccessUrlHelper.java new file mode 100644 index 0000000000..93d49d3c93 --- /dev/null +++ b/remote-api/src/main/java/org/alfresco/rest/api/DirectAccessUrlHelper.java @@ -0,0 +1,66 @@ +/* + * #%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 . + * #L% + */ +package org.alfresco.rest.api; + +import org.alfresco.rest.api.impl.directurl.RestApiDirectUrlConfig; +import org.alfresco.rest.api.model.DirectAccessUrlRequest; +import org.alfresco.rest.framework.core.exceptions.DisabledServiceException; +import org.apache.commons.lang3.BooleanUtils; + +/** + * Helper class for retrieving direct access URLs options. + * + * @author Sara Aspery + */ +public class DirectAccessUrlHelper +{ + private RestApiDirectUrlConfig restApiDirectUrlConfig; + + public void setRestApiDirectUrlConfig(RestApiDirectUrlConfig restApiDirectUrlConfig) + { + this.restApiDirectUrlConfig = restApiDirectUrlConfig; + } + + public Long getDefaultExpiryTimeInSec() + { + if (restApiDirectUrlConfig ==null || !restApiDirectUrlConfig.isEnabled()) + { + throw new DisabledServiceException("Direct access url isn't available."); + } + + return restApiDirectUrlConfig.getDefaultExpiryTimeInSec(); + } + + public boolean getAttachment(DirectAccessUrlRequest directAccessUrlRequest) + { + boolean attachment = true; + if (directAccessUrlRequest != null ) + { + attachment = BooleanUtils.toBooleanDefaultIfNull(directAccessUrlRequest.isAttachment(), true); + } + return attachment; + } +} diff --git a/remote-api/src/main/java/org/alfresco/rest/api/Nodes.java b/remote-api/src/main/java/org/alfresco/rest/api/Nodes.java index c0ae404000..5b9292514c 100644 --- a/remote-api/src/main/java/org/alfresco/rest/api/Nodes.java +++ b/remote-api/src/main/java/org/alfresco/rest/api/Nodes.java @@ -2,7 +2,7 @@ * #%L * Alfresco Remote API * %% - * Copyright (C) 2005 - 2017 Alfresco Software Limited + * 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 @@ -44,6 +44,7 @@ import org.alfresco.rest.framework.resource.content.BinaryResource; import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo; import org.alfresco.rest.framework.resource.parameters.Parameters; import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.DirectAccessUrl; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.namespace.QName; @@ -266,6 +267,49 @@ public interface Nodes */ Node unlock(String nodeId, Parameters parameters); + /** + * Gets a presigned URL to directly access content. + * @param nodeId The node id for which to obtain the direct access {@code URL} + * @param attachment {@code true} if an attachment {@code URL} is requested, {@code false} for an embedded {@code URL}. + * @return A direct access {@code URL} object for the content. + */ + default DirectAccessUrl requestContentDirectUrl(String nodeId, boolean attachment) + { + return requestContentDirectUrl(validateNode(nodeId), attachment); + } + + /** + * Gets a presigned URL to directly access content. + * @param nodeRef The node reference for which to obtain the direct access {@code URL} + * @param attachment {@code true} if an attachment {@code URL} is requested, {@code false} for an embedded {@code URL}. + * @return A direct access {@code URL} object for the content. + */ + default DirectAccessUrl requestContentDirectUrl(NodeRef nodeRef, boolean attachment) + { + return requestContentDirectUrl(nodeRef, attachment, null); + } + + /** + * Gets a presigned URL to directly access content. + * @param nodeId The node id for which to obtain the direct access {@code URL} + * @param attachment {@code true} if an attachment {@code URL} is requested, {@code false} for an embedded {@code URL}. + * @param validFor The time at which the direct access {@code URL} will expire. + * @return A direct access {@code URL} object for the content. + */ + default DirectAccessUrl requestContentDirectUrl(String nodeId, boolean attachment, Long validFor) + { + return requestContentDirectUrl(validateNode(nodeId), attachment, validFor); + } + + /** + * Gets a presigned URL to directly access content. + * @param nodeRef The node reference for which to obtain the direct access {@code URL} + * @param attachment {@code true} if an attachment {@code URL} is requested, {@code false} for an embedded {@code URL}. + * @param validFor The time at which the direct access {@code URL} will expire. + * @return A direct access {@code URL} object for the content. + */ + DirectAccessUrl requestContentDirectUrl(NodeRef nodeRef, boolean attachment, Long validFor); + /** * Convert from node properties (map of QName to Serializable) retrieved from * the respository to a map of String to Object that can be formatted/expressed diff --git a/remote-api/src/main/java/org/alfresco/rest/api/Renditions.java b/remote-api/src/main/java/org/alfresco/rest/api/Renditions.java index 3464bad52d..aa1e352798 100644 --- a/remote-api/src/main/java/org/alfresco/rest/api/Renditions.java +++ b/remote-api/src/main/java/org/alfresco/rest/api/Renditions.java @@ -2,7 +2,7 @@ * #%L * Alfresco Remote API * %% - * Copyright (C) 2005 - 2020 Alfresco Software Limited + * 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 @@ -32,7 +32,9 @@ import org.alfresco.rest.framework.core.exceptions.NotFoundException; import org.alfresco.rest.framework.resource.content.BinaryResource; import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo; import org.alfresco.rest.framework.resource.parameters.Parameters; +import org.alfresco.service.cmr.repository.DirectAccessUrl; import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.StoreRef; import java.util.List; @@ -186,5 +188,58 @@ public interface Renditions * @return the rendition stream */ BinaryResource getContentNoValidation(NodeRef nodeRef, String versionId, String renditionId, Parameters parameters); + + /** + * Gets a presigned URL to directly access content. + * @param nodeId the node id for which to obtain the direct access {@code URL} + * @param versionId the version id (aka version label) + * @param renditionId the rendition id + * @param attachment {@code true} if an attachment {@code URL} is requested, {@code false} for an embedded {@code URL} + * @return a direct access {@code URL} object for the content + */ + default DirectAccessUrl requestContentDirectUrl(String nodeId, String versionId, String renditionId, boolean attachment) + { + NodeRef nodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, nodeId); + return requestContentDirectUrl(nodeRef, versionId, renditionId, attachment); + } + + /** + * Gets a presigned URL to directly access content. + * @param nodeId the node id for which to obtain the direct access {@code URL} + * @param versionId the version id (aka version label) + * @param renditionId the rendition id + * @param attachment {@code true} if an attachment {@code URL} is requested, {@code false} for an embedded {@code URL} + * @param validFor the time at which the direct access {@code URL} will expire + * @return a direct access {@code URL} object for the content + */ + default DirectAccessUrl requestContentDirectUrl(String nodeId, String versionId, String renditionId, boolean attachment, Long validFor) + { + NodeRef nodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, nodeId); + return requestContentDirectUrl(nodeRef, versionId, renditionId, attachment, validFor); + } + + /** + * Gets a presigned URL to directly access content. + * @param nodeRef the node reference for which to obtain the direct access {@code URL} + * @param versionId the version id (aka version label) + * @param renditionId the rendition id + * @param attachment {@code true} if an attachment {@code URL} is requested, {@code false} for an embedded {@code URL} + * @return a direct access {@code URL} object for the content. + */ + default DirectAccessUrl requestContentDirectUrl(NodeRef nodeRef, String versionId, String renditionId, boolean attachment) + { + return requestContentDirectUrl(nodeRef, versionId, renditionId, attachment, null); + } + + /** + * Gets a presigned URL to directly access content. + * @param nodeRef the node reference for which to obtain the direct access {@code URL} + * @param versionId the version id (aka version label) + * @param renditionId the rendition id + * @param attachment {@code true} if an attachment {@code URL} is requested, {@code false} for an embedded {@code URL} + * @param validFor the time at which the direct access {@code URL} will expire + * @return a direct access {@code URL} object for the content. + */ + DirectAccessUrl requestContentDirectUrl(NodeRef nodeRef, String versionId, String renditionId, boolean attachment, Long validFor); } diff --git a/remote-api/src/main/java/org/alfresco/rest/api/discovery/DiscoveryApiWebscript.java b/remote-api/src/main/java/org/alfresco/rest/api/discovery/DiscoveryApiWebscript.java index c163447fd0..b4ee326711 100644 --- a/remote-api/src/main/java/org/alfresco/rest/api/discovery/DiscoveryApiWebscript.java +++ b/remote-api/src/main/java/org/alfresco/rest/api/discovery/DiscoveryApiWebscript.java @@ -2,7 +2,7 @@ * #%L * Alfresco Remote API * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited + * 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 @@ -25,6 +25,7 @@ */ package org.alfresco.rest.api.discovery; +import org.alfresco.rest.api.impl.directurl.RestApiDirectUrlConfig; import org.alfresco.rest.api.model.DiscoveryDetails; import org.alfresco.rest.api.model.ModulePackage; import org.alfresco.rest.api.model.RepositoryInfo; @@ -41,6 +42,7 @@ import org.alfresco.service.cmr.audit.AuditService; import org.alfresco.service.cmr.module.ModuleDetails; import org.alfresco.service.cmr.module.ModuleService; import org.alfresco.service.cmr.quickshare.QuickShareService; +import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.thumbnail.ThumbnailService; import org.alfresco.service.descriptor.Descriptor; import org.alfresco.service.descriptor.DescriptorService; @@ -67,6 +69,8 @@ public class DiscoveryApiWebscript extends AbstractWebScript implements Recogniz private ModuleService moduleService; private ApiAssistant assistant; private ThumbnailService thumbnailService; + private RestApiDirectUrlConfig restApiDirectUrlConfig; + private ContentService contentService; private boolean enabled = true; private final static String DISABLED = "Not Implemented"; @@ -106,6 +110,16 @@ public class DiscoveryApiWebscript extends AbstractWebScript implements Recogniz this.thumbnailService = thumbnailService; } + public void setRestApiDirectUrlConfig(RestApiDirectUrlConfig restApiDirectUrlConfig) + { + this.restApiDirectUrlConfig = restApiDirectUrlConfig; + } + + public void setContentService(ContentService contentService) + { + this.contentService = contentService; + } + @Override public void afterPropertiesSet() throws Exception { @@ -116,6 +130,8 @@ public class DiscoveryApiWebscript extends AbstractWebScript implements Recogniz PropertyCheck.mandatory(this, "moduleService", moduleService); PropertyCheck.mandatory(this, "assistant", assistant); PropertyCheck.mandatory(this, "thumbnailService", thumbnailService); + PropertyCheck.mandatory(this, "restApiDirectUrlConfig", restApiDirectUrlConfig); + PropertyCheck.mandatory(this, "contentService", contentService); } @Override @@ -154,7 +170,8 @@ public class DiscoveryApiWebscript extends AbstractWebScript implements Recogniz .setReadOnly(repoAdminService.getUsage().isReadOnly()) .setAuditEnabled(auditService.isAuditEnabled()) .setQuickShareEnabled(quickShareService.isQuickShareEnabled()) - .setThumbnailGenerationEnabled(thumbnailService.getThumbnailsEnabled())); + .setThumbnailGenerationEnabled(thumbnailService.getThumbnailsEnabled()) + .setDirectAccessUrlEnabled(isContentDirectUrlEnabled())); } private List getModules() @@ -194,4 +211,10 @@ public class DiscoveryApiWebscript extends AbstractWebScript implements Recogniz throw new DisabledServiceException(DISABLED); } } + + protected boolean isContentDirectUrlEnabled() + { + return (restApiDirectUrlConfig.isEnabled() && contentService.isContentDirectUrlEnabled()); + } + } diff --git a/remote-api/src/main/java/org/alfresco/rest/api/impl/DeletedNodesImpl.java b/remote-api/src/main/java/org/alfresco/rest/api/impl/DeletedNodesImpl.java index 2398a37067..f63de476e2 100644 --- a/remote-api/src/main/java/org/alfresco/rest/api/impl/DeletedNodesImpl.java +++ b/remote-api/src/main/java/org/alfresco/rest/api/impl/DeletedNodesImpl.java @@ -2,7 +2,7 @@ * #%L * Alfresco Remote API * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited + * 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 @@ -55,6 +55,7 @@ import org.alfresco.rest.framework.resource.content.BinaryResource; import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo; import org.alfresco.rest.framework.resource.parameters.Parameters; import org.alfresco.rest.framework.tools.RecognizedParamsExtractor; +import org.alfresco.service.cmr.repository.DirectAccessUrl; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.StoreRef; @@ -244,4 +245,23 @@ public class DeletedNodesImpl implements DeletedNodes, RecognizedParamsExtractor NodeRef nodeRef = new NodeRef(StoreRef.STORE_REF_ARCHIVE_SPACESSTORE, archivedId); return renditions.getRenditions(nodeRef, parameters); } + + /** + * {@inheritDoc} + */ + @Override + public DirectAccessUrl requestContentDirectUrl(String originalNodeId, String renditionId, boolean attachment, Long validFor) + { + //First check the node is valid and has been archived. + NodeRef validatedNodeRef = nodes.validateNode(StoreRef.STORE_REF_ARCHIVE_SPACESSTORE, originalNodeId); + + if (renditionId != null) + { + return renditions.requestContentDirectUrl(validatedNodeRef, null, renditionId, attachment, validFor); + } + else + { + return nodes.requestContentDirectUrl(validatedNodeRef, attachment, validFor); + } + } } diff --git a/remote-api/src/main/java/org/alfresco/rest/api/impl/NodesImpl.java b/remote-api/src/main/java/org/alfresco/rest/api/impl/NodesImpl.java index 93e28d4b18..4e0b7ed729 100644 --- a/remote-api/src/main/java/org/alfresco/rest/api/impl/NodesImpl.java +++ b/remote-api/src/main/java/org/alfresco/rest/api/impl/NodesImpl.java @@ -2,7 +2,7 @@ * #%L * Alfresco Remote API * %% - * Copyright (C) 2005 - 2019 Alfresco Software Limited + * 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 @@ -142,6 +142,7 @@ import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.ContentIOException; import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.DirectAccessUrl; import org.alfresco.service.cmr.repository.DuplicateChildNodeNameException; import org.alfresco.service.cmr.repository.InvalidNodeRefException; import org.alfresco.service.cmr.repository.MimetypeService; @@ -3478,6 +3479,20 @@ public class NodesImpl implements Nodes return getFolderOrDocument(nodeId, parameters); } + /** + * {@inheritDoc} + */ + @Override + public DirectAccessUrl requestContentDirectUrl(NodeRef nodeRef, boolean attachment, Long validFor) + { + DirectAccessUrl directAccessUrl = contentService.requestContentDirectUrl(nodeRef, attachment, validFor); + if (directAccessUrl == null) + { + throw new DisabledServiceException("Direct access url isn't available."); + } + return directAccessUrl; + } + /** * Checks if same permission is sent more than once * @param locallySetPermissions diff --git a/remote-api/src/main/java/org/alfresco/rest/api/impl/RenditionsImpl.java b/remote-api/src/main/java/org/alfresco/rest/api/impl/RenditionsImpl.java index 3c1b4f0353..d253addecf 100644 --- a/remote-api/src/main/java/org/alfresco/rest/api/impl/RenditionsImpl.java +++ b/remote-api/src/main/java/org/alfresco/rest/api/impl/RenditionsImpl.java @@ -2,7 +2,7 @@ * #%L * Alfresco Remote API * %% - * Copyright (C) 2005 - 2020 Alfresco Software LimitedP + * Copyright (C) 2005 - 2021 Alfresco Software LimitedP * %% * This file is part of the Alfresco software. * If the software was purchased under a paid Alfresco license, the terms of @@ -26,6 +26,19 @@ package org.alfresco.rest.api.impl; +import java.io.File; +import java.io.InputStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.StringJoiner; +import java.util.TreeMap; + import org.alfresco.heartbeat.RenditionsDataCollector; import org.alfresco.model.ContentModel; import org.alfresco.query.PagingResults; @@ -61,6 +74,7 @@ import org.alfresco.rest.workflow.api.impl.MapBasedQueryWalker; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.DirectAccessUrl; import org.alfresco.service.cmr.repository.MimetypeService; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; @@ -78,19 +92,6 @@ import org.apache.commons.logging.LogFactory; import org.springframework.context.ResourceLoaderAware; import org.springframework.core.io.ResourceLoader; -import java.io.File; -import java.io.InputStream; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Date; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.StringJoiner; -import java.util.TreeMap; - /** * @author Jamal Kaabi-Mofrad, janv */ @@ -483,6 +484,22 @@ public class RenditionsImpl implements Renditions, ResourceLoaderAware return getContentImpl(nodeRef, renditionId, parameters); } + /** + * {@inheritDoc} + */ + public DirectAccessUrl requestContentDirectUrl(NodeRef nodeRef, String versionId, String renditionId, boolean attachment, Long validFor) + { + final NodeRef validatedNodeRef = validateNode(nodeRef.getStoreRef(), nodeRef.getId(), versionId, null); + NodeRef renditionNodeRef = getRenditionByName(validatedNodeRef, renditionId, null); + + if (renditionNodeRef == null) + { + throw new NotFoundException("The rendition with id: " + renditionId + " was not found."); + } + + return nodes.requestContentDirectUrl(renditionNodeRef, attachment, validFor); + } + private BinaryResource getContentImpl(NodeRef nodeRef, String renditionId, Parameters parameters) { NodeRef renditionNodeRef = getRenditionByName(nodeRef, renditionId, parameters); diff --git a/remote-api/src/main/java/org/alfresco/rest/api/impl/directurl/RestApiDirectUrlConfig.java b/remote-api/src/main/java/org/alfresco/rest/api/impl/directurl/RestApiDirectUrlConfig.java new file mode 100644 index 0000000000..bfcace5dc3 --- /dev/null +++ b/remote-api/src/main/java/org/alfresco/rest/api/impl/directurl/RestApiDirectUrlConfig.java @@ -0,0 +1,94 @@ +/* + * #%L + * Alfresco Repository + * %% + * 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 . + * #L% + */ +package org.alfresco.rest.api.impl.directurl; + +import org.alfresco.repo.content.directurl.AbstractDirectUrlConfig; +import org.alfresco.repo.content.directurl.InvalidDirectAccessUrlConfigException; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * REST API direct access URL configuration settings. + * + * @author Sara Aspery + */ +public class RestApiDirectUrlConfig extends AbstractDirectUrlConfig +{ + private static final Log logger = LogFactory.getLog(RestApiDirectUrlConfig.class); + + /** + * Configuration initialise + */ + public void init() + { + validate(); + } + + /** + * {@inheritDoc} + */ + @Override + public void validate() + { + // Disable direct access URLs for the REST API if any error found in the REST API direct access URL config + try + { + validateDirectAccessUrlConfig(); + } + catch (InvalidDirectAccessUrlConfigException ex) + { + logger.error("Disabling REST API direct access URLs due to configuration error: " + ex.getMessage()); + setEnabled(false); + } + logger.info("REST API direct access URLs are " + (isEnabled() ? "enabled" : "disabled")); + } + + /* Helper method to validate the REST API direct access url configuration settings */ + private void validateDirectAccessUrlConfig() throws InvalidDirectAccessUrlConfigException + { + if (isEnabled()) + { + if (getDefaultExpiryTimeInSec() == null) + { + logger.warn(String.format("Default expiry time property is missing: setting to system-wide default [%s].", getSysWideDefaultExpiryTimeInSec())); + setDefaultExpiryTimeInSec(getSysWideDefaultExpiryTimeInSec()); + } + + if (getDefaultExpiryTimeInSec() < 1) + { + String errorMsg = String.format("REST API direct access URL default expiry time [%s] is invalid.", getDefaultExpiryTimeInSec()); + throw new InvalidDirectAccessUrlConfigException(errorMsg); + } + + if (getDefaultExpiryTimeInSec() > getSysWideMaxExpiryTimeInSec()) + { + String errorMsg = String.format("REST API direct access URL default expiry time [%s] exceeds system-wide maximum expiry time [%s].", + getDefaultExpiryTimeInSec(), getSysWideMaxExpiryTimeInSec()); + throw new InvalidDirectAccessUrlConfigException(errorMsg); + } + } + } +} diff --git a/remote-api/src/main/java/org/alfresco/rest/api/model/DirectAccessUrlRequest.java b/remote-api/src/main/java/org/alfresco/rest/api/model/DirectAccessUrlRequest.java new file mode 100644 index 0000000000..6dde68adb2 --- /dev/null +++ b/remote-api/src/main/java/org/alfresco/rest/api/model/DirectAccessUrlRequest.java @@ -0,0 +1,46 @@ +/* + * #%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 . + * #L% + */ +package org.alfresco.rest.api.model; + +/** + * Direct Access URL request. + * + * @author Sara Aspery + */ +public class DirectAccessUrlRequest +{ + private Boolean attachment; + + public Boolean isAttachment() + { + return attachment; + } + + public void setAttachment(Boolean attachment) + { + this.attachment = attachment; + } +} diff --git a/remote-api/src/main/java/org/alfresco/rest/api/model/RepositoryInfo.java b/remote-api/src/main/java/org/alfresco/rest/api/model/RepositoryInfo.java index f65aa8c5c7..60bbadd32a 100644 --- a/remote-api/src/main/java/org/alfresco/rest/api/model/RepositoryInfo.java +++ b/remote-api/src/main/java/org/alfresco/rest/api/model/RepositoryInfo.java @@ -2,7 +2,7 @@ * #%L * Alfresco Remote API * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited + * 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 @@ -416,6 +416,7 @@ public class RepositoryInfo private boolean isAuditEnabled; private boolean isQuickShareEnabled; private boolean isThumbnailGenerationEnabled; + private boolean isDirectAccessUrlEnabled; public StatusInfo() { @@ -465,6 +466,17 @@ public class RepositoryInfo return this; } + public boolean getIsDirectAccessUrlEnabled() + { + return isDirectAccessUrlEnabled; + } + + public StatusInfo setDirectAccessUrlEnabled(boolean isDirectAccessUrlEnabled) + { + this.isDirectAccessUrlEnabled = isDirectAccessUrlEnabled; + return this; + } + @Override public String toString() { @@ -473,6 +485,7 @@ public class RepositoryInfo .append(", isAuditEnabled=").append(isAuditEnabled) .append(", isQuickShareEnabled=").append(isQuickShareEnabled) .append(", isThumbnailGenerationEnabled=").append(isThumbnailGenerationEnabled) + .append(", isDirectAccessUrlEnabled=").append(isDirectAccessUrlEnabled) .append(']'); return sb.toString(); } diff --git a/remote-api/src/main/java/org/alfresco/rest/api/nodes/NodeRenditionsRelation.java b/remote-api/src/main/java/org/alfresco/rest/api/nodes/NodeRenditionsRelation.java index f9702f1c62..54cd41747b 100644 --- a/remote-api/src/main/java/org/alfresco/rest/api/nodes/NodeRenditionsRelation.java +++ b/remote-api/src/main/java/org/alfresco/rest/api/nodes/NodeRenditionsRelation.java @@ -2,7 +2,7 @@ * #%L * Alfresco Remote API * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited + * 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 @@ -26,16 +26,27 @@ package org.alfresco.rest.api.nodes; +import javax.servlet.http.HttpServletResponse; + +import org.alfresco.repo.content.directurl.DirectAccessUrlDisabledException; +import org.alfresco.rest.api.DirectAccessUrlHelper; import org.alfresco.rest.api.Renditions; +import org.alfresco.rest.api.model.DirectAccessUrlRequest; import org.alfresco.rest.api.model.Rendition; import org.alfresco.rest.framework.BinaryProperties; +import org.alfresco.rest.framework.Operation; import org.alfresco.rest.framework.WebApiDescription; +import org.alfresco.rest.framework.WebApiParam; +import org.alfresco.rest.framework.core.ResourceParameter; +import org.alfresco.rest.framework.core.exceptions.DisabledServiceException; import org.alfresco.rest.framework.resource.RelationshipResource; import org.alfresco.rest.framework.resource.actions.interfaces.RelationshipResourceAction; import org.alfresco.rest.framework.resource.actions.interfaces.RelationshipResourceBinaryAction; import org.alfresco.rest.framework.resource.content.BinaryResource; import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo; import org.alfresco.rest.framework.resource.parameters.Parameters; +import org.alfresco.rest.framework.webscripts.WithResponse; +import org.alfresco.service.cmr.repository.DirectAccessUrl; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.util.PropertyCheck; @@ -58,12 +69,18 @@ public class NodeRenditionsRelation implements RelationshipResourceAction.Read, InitializingBean { private Nodes nodes; + private DirectAccessUrlHelper directAccessUrlHelper; public void setNodes(Nodes nodes) { this.nodes = nodes; } - @Override + public void setDirectAccessUrlHelper(DirectAccessUrlHelper directAccessUrlHelper) + { + this.directAccessUrlHelper = directAccessUrlHelper; + } + + @Override public void afterPropertiesSet() { ParameterCheck.mandatory("nodes", this.nodes); @@ -189,5 +202,27 @@ public class NodesEntityResource implements return nodes.unlock(nodeId, parameters); } + @Operation("request-direct-access-url") + @WebApiParam(name = "directAccessUrlRequest", title = "Request direct access url", description = "Options for direct access url request", kind = ResourceParameter.KIND.HTTP_BODY_OBJECT) + @WebApiDescription(title = "Request content url", + description="Generates a direct access URL.", + successStatus = HttpServletResponse.SC_OK) + public DirectAccessUrl requestContentDirectUrl(String nodeId, DirectAccessUrlRequest directAccessUrlRequest, Parameters parameters, WithResponse withResponse) + { + boolean attachment = directAccessUrlHelper.getAttachment(directAccessUrlRequest); + Long validFor = directAccessUrlHelper.getDefaultExpiryTimeInSec(); + NodeRef nodeRef = nodes.validateNode(nodeId); + + DirectAccessUrl directAccessUrl; + try + { + directAccessUrl = nodes.requestContentDirectUrl(nodeRef, attachment, validFor); + } + catch (DirectAccessUrlDisabledException ex) + { + throw new DisabledServiceException(ex.getMessage()); + } + return directAccessUrl; + } } diff --git a/remote-api/src/main/java/org/alfresco/rest/api/trashcan/TrashcanEntityResource.java b/remote-api/src/main/java/org/alfresco/rest/api/trashcan/TrashcanEntityResource.java index 1a831ef320..94cf99bdf0 100644 --- a/remote-api/src/main/java/org/alfresco/rest/api/trashcan/TrashcanEntityResource.java +++ b/remote-api/src/main/java/org/alfresco/rest/api/trashcan/TrashcanEntityResource.java @@ -2,7 +2,7 @@ * #%L * Alfresco Remote API * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited + * 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 @@ -28,7 +28,10 @@ package org.alfresco.rest.api.trashcan; import javax.servlet.http.HttpServletResponse; +import org.alfresco.repo.content.directurl.DirectAccessUrlDisabledException; import org.alfresco.rest.api.DeletedNodes; +import org.alfresco.rest.api.DirectAccessUrlHelper; +import org.alfresco.rest.api.model.DirectAccessUrlRequest; import org.alfresco.rest.api.model.Node; import org.alfresco.rest.api.model.NodeTargetAssoc; import org.alfresco.rest.framework.BinaryProperties; @@ -36,6 +39,7 @@ import org.alfresco.rest.framework.Operation; import org.alfresco.rest.framework.WebApiDescription; import org.alfresco.rest.framework.WebApiParam; import org.alfresco.rest.framework.core.ResourceParameter; +import org.alfresco.rest.framework.core.exceptions.DisabledServiceException; import org.alfresco.rest.framework.core.exceptions.EntityNotFoundException; import org.alfresco.rest.framework.resource.EntityResource; import org.alfresco.rest.framework.resource.actions.interfaces.BinaryResourceAction; @@ -44,6 +48,7 @@ import org.alfresco.rest.framework.resource.content.BinaryResource; import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo; import org.alfresco.rest.framework.resource.parameters.Parameters; import org.alfresco.rest.framework.webscripts.WithResponse; +import org.alfresco.service.cmr.repository.DirectAccessUrl; /** * An implementation of an Entity Resource for handling archived content @@ -55,12 +60,18 @@ public class TrashcanEntityResource implements EntityResourceAction.ReadById, EntityResourceAction.Read, EntityResourceAction.Delete, BinaryResourceAction.Read { private DeletedNodes deletedNodes; + private DirectAccessUrlHelper directAccessUrlHelper; public void setDeletedNodes(DeletedNodes deletedNodes) { this.deletedNodes = deletedNodes; } + public void setDirectAccessUrlHelper(DirectAccessUrlHelper directAccessUrlHelper) + { + this.directAccessUrlHelper = directAccessUrlHelper; + } + @Override public CollectionWithPagingInfo readAll(Parameters params) { @@ -89,6 +100,27 @@ public class TrashcanEntityResource implements return deletedNodes.getContent(nodeId, null, parameters); } + @Operation("request-direct-access-url") + @WebApiParam(name = "directAccessUrlRequest", title = "Request direct access url", description = "Options for direct access url request", kind = ResourceParameter.KIND.HTTP_BODY_OBJECT) + @WebApiDescription(title = "Request content url", + description="Generates a direct access URL.", + successStatus = HttpServletResponse.SC_OK) + public DirectAccessUrl requestContentDirectUrl(String originalNodeId, DirectAccessUrlRequest directAccessUrlRequest, Parameters parameters, WithResponse withResponse) + { + boolean attachment = directAccessUrlHelper.getAttachment(directAccessUrlRequest); + Long validFor = directAccessUrlHelper.getDefaultExpiryTimeInSec(); + DirectAccessUrl directAccessUrl; + try + { + directAccessUrl = deletedNodes.requestContentDirectUrl(originalNodeId, null, attachment, validFor); + } + catch (DirectAccessUrlDisabledException ex) + { + throw new DisabledServiceException(ex.getMessage()); + } + return directAccessUrl; + } + @Override public void delete(String nodeId, Parameters parameters) { diff --git a/remote-api/src/main/java/org/alfresco/rest/api/trashcan/TrashcanRenditionsRelation.java b/remote-api/src/main/java/org/alfresco/rest/api/trashcan/TrashcanRenditionsRelation.java index 72d427c3ae..5b8587eb89 100644 --- a/remote-api/src/main/java/org/alfresco/rest/api/trashcan/TrashcanRenditionsRelation.java +++ b/remote-api/src/main/java/org/alfresco/rest/api/trashcan/TrashcanRenditionsRelation.java @@ -2,7 +2,7 @@ * #%L * Alfresco Remote API * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited + * 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 @@ -25,18 +25,27 @@ */ package org.alfresco.rest.api.trashcan; +import javax.servlet.http.HttpServletResponse; + +import org.alfresco.repo.content.directurl.DirectAccessUrlDisabledException; import org.alfresco.rest.api.DeletedNodes; +import org.alfresco.rest.api.DirectAccessUrlHelper; +import org.alfresco.rest.api.model.DirectAccessUrlRequest; import org.alfresco.rest.api.model.Rendition; import org.alfresco.rest.framework.BinaryProperties; +import org.alfresco.rest.framework.Operation; import org.alfresco.rest.framework.WebApiDescription; -import org.alfresco.rest.framework.core.exceptions.EntityNotFoundException; -import org.alfresco.rest.framework.core.exceptions.RelationshipResourceNotFoundException; +import org.alfresco.rest.framework.WebApiParam; +import org.alfresco.rest.framework.core.ResourceParameter; +import org.alfresco.rest.framework.core.exceptions.DisabledServiceException; import org.alfresco.rest.framework.resource.RelationshipResource; import org.alfresco.rest.framework.resource.actions.interfaces.RelationshipResourceAction; import org.alfresco.rest.framework.resource.actions.interfaces.RelationshipResourceBinaryAction; import org.alfresco.rest.framework.resource.content.BinaryResource; import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo; import org.alfresco.rest.framework.resource.parameters.Parameters; +import org.alfresco.rest.framework.webscripts.WithResponse; +import org.alfresco.service.cmr.repository.DirectAccessUrl; import org.alfresco.util.ParameterCheck; import org.springframework.beans.factory.InitializingBean; @@ -46,12 +55,18 @@ public class TrashcanRenditionsRelation { private DeletedNodes deletedNodes; + private DirectAccessUrlHelper directAccessUrlHelper; public void setDeletedNodes(DeletedNodes deletedNodes) { this.deletedNodes = deletedNodes; } + public void setDirectAccessUrlHelper(DirectAccessUrlHelper directAccessUrlHelper) + { + this.directAccessUrlHelper = directAccessUrlHelper; + } + @WebApiDescription(title = "List renditions", description = "List available (created) renditions") @Override public CollectionWithPagingInfo readAll(String nodeId, Parameters parameters) @@ -74,6 +89,27 @@ public class TrashcanRenditionsRelation return deletedNodes.getContent(nodeId, renditionId, parameters); } + @Operation ("request-direct-access-url") + @WebApiParam (name = "directAccessUrlRequest", title = "Request direct access url", description = "Options for direct access url request", kind = ResourceParameter.KIND.HTTP_BODY_OBJECT) + @WebApiDescription(title = "Request content url", + description="Generates a direct access URL.", + successStatus = HttpServletResponse.SC_OK) + public DirectAccessUrl requestContentDirectUrl(String originalNodeId, String renditionId, DirectAccessUrlRequest directAccessUrlRequest, Parameters parameters, WithResponse withResponse) + { + boolean attachment = directAccessUrlHelper.getAttachment(directAccessUrlRequest); + Long validFor = directAccessUrlHelper.getDefaultExpiryTimeInSec(); + DirectAccessUrl directAccessUrl; + try + { + directAccessUrl = deletedNodes.requestContentDirectUrl(originalNodeId, renditionId, attachment, validFor); + } + catch (DirectAccessUrlDisabledException ex) + { + throw new DisabledServiceException(ex.getMessage()); + } + return directAccessUrl; + } + @Override public void afterPropertiesSet() throws Exception { diff --git a/remote-api/src/main/java/org/alfresco/rest/framework/core/exceptions/JsonpCallbackNotAllowedException.java b/remote-api/src/main/java/org/alfresco/rest/framework/core/exceptions/JsonpCallbackNotAllowedException.java new file mode 100644 index 0000000000..6408aabce8 --- /dev/null +++ b/remote-api/src/main/java/org/alfresco/rest/framework/core/exceptions/JsonpCallbackNotAllowedException.java @@ -0,0 +1,49 @@ +/* + * #%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 . + * #L% + */ +package org.alfresco.rest.framework.core.exceptions; + +/** + * JSONP callback not allowed + * + * @author Vitor Moreira + */ +public class JsonpCallbackNotAllowedException extends ApiException +{ + private static final long serialVersionUID = 7198491358180044895L; + + public static String DEFAULT_MESSAGE_ID = "framework.exception.JsonpCallbackNotAllowed"; + + public JsonpCallbackNotAllowedException() + { + super(DEFAULT_MESSAGE_ID); + } + + public JsonpCallbackNotAllowedException(String msgId) + { + super(msgId); + } + +} diff --git a/remote-api/src/main/java/org/alfresco/rest/framework/webscripts/ApiWebScript.java b/remote-api/src/main/java/org/alfresco/rest/framework/webscripts/ApiWebScript.java index c848eb05e7..606cf3cf07 100644 --- a/remote-api/src/main/java/org/alfresco/rest/framework/webscripts/ApiWebScript.java +++ b/remote-api/src/main/java/org/alfresco/rest/framework/webscripts/ApiWebScript.java @@ -28,10 +28,11 @@ package org.alfresco.rest.framework.webscripts; import java.io.File; import java.io.IOException; import java.util.Map; +import java.util.function.Supplier; import org.alfresco.repo.web.scripts.BufferedRequest; import org.alfresco.repo.web.scripts.BufferedResponse; -import org.alfresco.repo.web.scripts.TempOutputStreamFactory; +import org.alfresco.repo.web.scripts.TempOutputStream; import org.alfresco.rest.framework.Api; import org.alfresco.rest.framework.tools.ApiAssistant; import org.alfresco.service.transaction.TransactionService; @@ -56,7 +57,7 @@ public abstract class ApiWebScript extends AbstractWebScript protected String tempDirectoryName = null; protected int memoryThreshold = 4 * 1024 * 1024; // 4mb protected long maxContentSize = (long) 4 * 1024 * 1024 * 1024; // 4gb - protected TempOutputStreamFactory streamFactory = null; + protected Supplier streamFactory = null; protected TransactionService transactionService; public void setTransactionService(TransactionService transactionService) @@ -88,7 +89,7 @@ public abstract class ApiWebScript extends AbstractWebScript this.maxContentSize = maxContentSize; } - public void setStreamFactory(TempOutputStreamFactory streamFactory) + public void setStreamFactory(Supplier streamFactory) { this.streamFactory = streamFactory; } @@ -96,50 +97,38 @@ public abstract class ApiWebScript extends AbstractWebScript public void init() { File tempDirectory = TempFileProvider.getTempDir(tempDirectoryName); - this.streamFactory = new TempOutputStreamFactory(tempDirectory, memoryThreshold, maxContentSize, false, false); + streamFactory = TempOutputStream.factory(tempDirectory, memoryThreshold, maxContentSize, false); } @Override public void execute(final WebScriptRequest req, final WebScriptResponse res) throws IOException { - Map templateVars = req.getServiceMatch().getTemplateVars(); - Api api = assistant.determineApi(templateVars); - - final BufferedRequest bufferedReq = getRequest(req); - final BufferedResponse bufferedRes = getResponse(res); + final Map templateVars = req.getServiceMatch().getTemplateVars(); + final Api api = ApiAssistant.determineApi(templateVars); - try - { - execute(api, bufferedReq, bufferedRes); - } - finally - { - // Get rid of any temporary files - if (bufferedReq != null) - { - bufferedReq.close(); - } - } - - // Ensure a response is always flushed after successful execution - if (bufferedRes != null) + try (final BufferedRequest bufferedReq = getRequest(req); + final BufferedResponse bufferedRes = getResponse(res)) { - bufferedRes.writeResponse(); + execute(api, bufferedReq, bufferedRes); + + // Ensure a response is always flushed after successful execution + if (bufferedRes != null) + { + bufferedRes.writeResponse(); + } } } protected BufferedRequest getRequest(final WebScriptRequest req) { // create buffered request and response that allow transaction retrying - final BufferedRequest bufferedReq = new BufferedRequest(req, streamFactory); - return bufferedReq; + return new BufferedRequest(req, streamFactory); } protected BufferedResponse getResponse(final WebScriptResponse resp) { // create buffered request and response that allow transaction retrying - final BufferedResponse bufferedRes = new BufferedResponse(resp, memoryThreshold, streamFactory); - return bufferedRes; + return new BufferedResponse(resp, memoryThreshold, streamFactory); } public abstract void execute(final Api api, WebScriptRequest req, WebScriptResponse res) throws IOException; diff --git a/remote-api/src/main/java/org/alfresco/web/scripts/WebScriptUtils.java b/remote-api/src/main/java/org/alfresco/web/scripts/WebScriptUtils.java index ee2a7dbe2a..917b5fe81e 100644 --- a/remote-api/src/main/java/org/alfresco/web/scripts/WebScriptUtils.java +++ b/remote-api/src/main/java/org/alfresco/web/scripts/WebScriptUtils.java @@ -2,7 +2,7 @@ * #%L * Alfresco Remote API * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited + * 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 @@ -23,110 +23,145 @@ * along with Alfresco. If not, see . * #L% */ -package org.alfresco.web.scripts; - -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; - -import org.alfresco.repo.jscript.ScriptUtils; -import org.alfresco.repo.web.scripts.RepositoryContainer; -import org.alfresco.service.cmr.admin.RepoUsage; -import org.alfresco.service.cmr.repository.StoreRef; -import org.springframework.extensions.webscripts.WebScript; - -/** - * Override of the JavaScript API ScriptUtils bean "utilsScript" to provide additional - * Remote API methods using objects not available to base Repository project. - *

- * See "web-scripts-application-context.xml" for bean definition. - * - * @since 4.2.0 - * @since 5.1 (Moved to Remote API project) - * @author Kevin Roast - */ -public class WebScriptUtils extends ScriptUtils -{ - protected RepositoryContainer repositoryContainer; - - public void setRepositoryContainer(RepositoryContainer repositoryContainer) - { - this.repositoryContainer = repositoryContainer; - } - - /** - * Searches for webscript components with the given family name. - * - * @param family the family - * - * @return An array of webscripts that match the given family name - */ - public Object[] findWebScripts(String family) - { - List values = new ArrayList(); - - for (WebScript webscript : this.repositoryContainer.getRegistry().getWebScripts()) - { - if (family != null) - { - Set familys = webscript.getDescription().getFamilys(); - if (familys != null && familys.contains(family)) - { - values.add(webscript.getDescription()); - } - } - else - { - values.add(webscript.getDescription()); - } - } - - return values.toArray(new Object[values.size()]); - } - - public String getHostAddress() - { - try - { - return InetAddress.getLocalHost().getHostAddress(); - } - catch (UnknownHostException e) - { - return "Unknown"; - } - } - - public String getHostName() - { - try - { - return InetAddress.getLocalHost().getHostName(); - } - catch (UnknownHostException e) - { - return "Unknown"; - } - } - - public RepoUsage getRestrictions() - { - return this.services.getRepoAdminService().getRestrictions(); - } - - public RepoUsage getUsage() - { - return this.services.getRepoAdminService().getUsage(); - } - - /** - * Gets the list of repository stores - * - * @return stores - */ - public List getStores() - { - return this.services.getNodeService().getStores(); - } -} +package org.alfresco.web.scripts; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import org.alfresco.repo.jscript.ScriptUtils; +import org.alfresco.repo.web.scripts.RepositoryContainer; +import org.alfresco.service.cmr.admin.RepoUsage; +import org.alfresco.service.cmr.repository.StoreRef; +import org.springframework.extensions.webscripts.Description.RequiredAuthentication; +import org.springframework.extensions.webscripts.WebScript; + +/** + * Override of the JavaScript API ScriptUtils bean "utilsScript" to provide additional + * Remote API methods using objects not available to base Repository project. + *

+ * See "web-scripts-application-context.xml" for bean definition. + * + * @since 4.2.0 + * @since 5.1 (Moved to Remote API project) + * @author Kevin Roast + */ +public class WebScriptUtils extends ScriptUtils +{ + protected RepositoryContainer repositoryContainer; + + public void setRepositoryContainer(RepositoryContainer repositoryContainer) + { + this.repositoryContainer = repositoryContainer; + } + + /** + * Searches for webscript components with the given family name. + * + * @param family the family + * + * @return An array of webscripts that match the given family name + */ + public Object[] findWebScripts(String family) + { + List values = new ArrayList<>(); + + for (WebScript webscript : this.repositoryContainer.getRegistry().getWebScripts()) + { + addScriptDescription(family, values, webscript); + } + + return values.toArray(new Object[0]); + } + + /** + * Searches for webscript components with the given family name accessible to the current user. + * + * @param family the family + * + * @return An array of webscripts that match the given family name accessible to the current user + * + * @since 7.1 + */ + public Object[] findWebScriptsForCurrentUser(String family) + { + List values = new ArrayList<>(); + + final boolean isAdminOrSystemUser = repositoryContainer.isAdminOrSystemUser(); + for (WebScript webscript : this.repositoryContainer.getRegistry().getWebScripts()) + { + final RequiredAuthentication required = webscript.getDescription().getRequiredAuthentication(); + // Ignore admin webscripts if the current user is not an Admin or System + if (RequiredAuthentication.admin == required && !isAdminOrSystemUser) + { + continue; + } + + addScriptDescription(family, values, webscript); + } + + return values.toArray(new Object[0]); + } + + private void addScriptDescription(String family, List values, WebScript webscript) + { + if (family != null) + { + Set families = webscript.getDescription().getFamilys(); + if (families != null && families.contains(family)) + { + values.add(webscript.getDescription()); + } + } + else + { + values.add(webscript.getDescription()); + } + } + + public String getHostAddress() + { + try + { + return InetAddress.getLocalHost().getHostAddress(); + } + catch (UnknownHostException e) + { + return "Unknown"; + } + } + + public String getHostName() + { + try + { + return InetAddress.getLocalHost().getHostName(); + } + catch (UnknownHostException e) + { + return "Unknown"; + } + } + + public RepoUsage getRestrictions() + { + return this.services.getRepoAdminService().getRestrictions(); + } + + public RepoUsage getUsage() + { + return this.services.getRepoAdminService().getUsage(); + } + + /** + * Gets the list of repository stores + * + * @return stores + */ + public List getStores() + { + return this.services.getNodeService().getStores(); + } +} diff --git a/remote-api/src/main/resources/alfresco/messages/rest-framework-messages.properties b/remote-api/src/main/resources/alfresco/messages/rest-framework-messages.properties index 08562a979a..8f23a8784c 100644 --- a/remote-api/src/main/resources/alfresco/messages/rest-framework-messages.properties +++ b/remote-api/src/main/resources/alfresco/messages/rest-framework-messages.properties @@ -14,4 +14,6 @@ framework.exception.UnsupportedResourceOperation=The operation is unsupported framework.exception.DeletedResource=In this version of the REST API resource {0} has been deleted framework.exception.RequestEntityTooLarge=The file can't be uploaded because it's larger than the maximum upload size framework.exception.InsufficientStorage=The file upload exceeds the content storage allowance +framework.exception.JsonpCallbackNotAllowed=For security reasons the callback parameter is not allowed framework.no.stacktrace=For security reasons the stack trace is no longer displayed, but the property is kept for previous versions + diff --git a/remote-api/src/main/resources/alfresco/public-rest-context.xml b/remote-api/src/main/resources/alfresco/public-rest-context.xml index b939c00ba8..5a5e7b47b1 100644 --- a/remote-api/src/main/resources/alfresco/public-rest-context.xml +++ b/remote-api/src/main/resources/alfresco/public-rest-context.xml @@ -960,8 +960,13 @@ + + + + + @@ -987,6 +992,7 @@ + @@ -1011,9 +1017,9 @@ + - @@ -1085,12 +1091,21 @@ + + + + + + + + + @@ -1145,6 +1160,7 @@ + + @@ -1443,6 +1460,7 @@ + diff --git a/remote-api/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/admin/admin-common.lib.js b/remote-api/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/admin/admin-common.lib.js index c7610cae61..c9312b48c2 100644 --- a/remote-api/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/admin/admin-common.lib.js +++ b/remote-api/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/admin/admin-common.lib.js @@ -40,7 +40,7 @@ var Admin = Admin || {}; var toolInfo = {}; // collect the tools required for the Admin Console - var tools = utils.findWebScripts("AdminConsole"); + var tools = utils.findWebScriptsForCurrentUser("AdminConsole"); // process each tool and generate the data so that a label+link can // be output by the component template for each tool required diff --git a/remote-api/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/admin/admin-communitysummary.get.desc.xml b/remote-api/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/admin/admin-communitysummary.get.desc.xml index 55445b39d4..016f57de71 100644 --- a/remote-api/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/admin/admin-communitysummary.get.desc.xml +++ b/remote-api/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/admin/admin-communitysummary.get.desc.xml @@ -7,7 +7,7 @@ AdminConsole:Edition:Community argument - admin + sysadmin internal required - \ No newline at end of file + diff --git a/remote-api/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/admin/admin-root.get.desc.xml b/remote-api/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/admin/admin-root.get.desc.xml index 26816eeb20..2b7ae51945 100644 --- a/remote-api/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/admin/admin-root.get.desc.xml +++ b/remote-api/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/admin/admin-root.get.desc.xml @@ -5,7 +5,7 @@ /admin/ AdminConsoleHelper argument - admin + sysadmin internal required - \ No newline at end of file + diff --git a/remote-api/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/admin/jmxdump.get.desc.xml b/remote-api/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/admin/jmxdump.get.desc.xml index 39eceeb809..b1179e5ebe 100644 --- a/remote-api/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/admin/jmxdump.get.desc.xml +++ b/remote-api/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/admin/jmxdump.get.desc.xml @@ -5,7 +5,7 @@ /api/admin/jmxdump AdminConsoleHelper - admin + sysadmin internal - \ No newline at end of file + diff --git a/remote-api/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/admin/usage.post.desc.xml b/remote-api/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/admin/usage.post.desc.xml index e852951f6c..fb3fea15ae 100644 --- a/remote-api/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/admin/usage.post.desc.xml +++ b/remote-api/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/admin/usage.post.desc.xml @@ -3,8 +3,8 @@ Update and retrieve repository usage /api/admin/usage - admin + sysadmin required Admin internal - \ No newline at end of file + diff --git a/remote-api/src/test/java/org/alfresco/AppContext01TestSuite.java b/remote-api/src/test/java/org/alfresco/AppContext01TestSuite.java index 699b999070..5a857d0e9d 100644 --- a/remote-api/src/test/java/org/alfresco/AppContext01TestSuite.java +++ b/remote-api/src/test/java/org/alfresco/AppContext01TestSuite.java @@ -42,6 +42,7 @@ import org.junit.runners.Suite; org.alfresco.rest.api.tests.TestPublicApiAtomPub10TCK.class, org.alfresco.rest.api.tests.TestPublicApiAtomPub11TCK.class, org.alfresco.rest.api.tests.TestPublicApiBrowser11TCK.class, + org.alfresco.repo.web.scripts.bulkimport.BulkImportParametersExtractorTest.class }) public class AppContext01TestSuite { diff --git a/remote-api/src/test/java/org/alfresco/AppContext02TestSuite.java b/remote-api/src/test/java/org/alfresco/AppContext02TestSuite.java index dfde2647c1..0ed1f3bf22 100644 --- a/remote-api/src/test/java/org/alfresco/AppContext02TestSuite.java +++ b/remote-api/src/test/java/org/alfresco/AppContext02TestSuite.java @@ -2,7 +2,7 @@ * #%L * Alfresco Repository * %% - * Copyright (C) 2005 - 2017 Alfresco Software Limited + * 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 @@ -47,6 +47,7 @@ import org.junit.runners.Suite; org.alfresco.rest.api.tests.ActivitiesPostingTest.class, org.alfresco.rest.api.tests.AuthenticationsTest.class, org.alfresco.rest.api.tests.DiscoveryApiTest.class, + org.alfresco.rest.api.discovery.DiscoveryApiWebscriptUnitTest.class, org.alfresco.rest.api.tests.GroupsTest.class, org.alfresco.rest.api.tests.ModulePackagesApiTest.class, org.alfresco.rest.api.tests.NodeApiTest.class, diff --git a/remote-api/src/test/java/org/alfresco/AppContext04TestSuite.java b/remote-api/src/test/java/org/alfresco/AppContext04TestSuite.java index 9545280e76..4548e38d4c 100644 --- a/remote-api/src/test/java/org/alfresco/AppContext04TestSuite.java +++ b/remote-api/src/test/java/org/alfresco/AppContext04TestSuite.java @@ -2,7 +2,7 @@ * #%L * Alfresco Repository * %% - * Copyright (C) 2005 - 2017 Alfresco Software Limited + * 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 @@ -76,6 +76,7 @@ import org.junit.runners.Suite; org.alfresco.repo.web.scripts.site.SurfConfigTest.class, org.alfresco.repo.web.scripts.node.NodeWebScripTest.class, org.alfresco.rest.api.impl.CommentsImplUnitTest.class, + org.alfresco.rest.api.impl.RestApiDirectUrlConfigUnitTest.class }) public class AppContext04TestSuite { diff --git a/remote-api/src/test/java/org/alfresco/repo/web/scripts/admin/AdminWebScriptTest.java b/remote-api/src/test/java/org/alfresco/repo/web/scripts/admin/AdminWebScriptTest.java index c0610d25e8..4a9d6fe556 100644 --- a/remote-api/src/test/java/org/alfresco/repo/web/scripts/admin/AdminWebScriptTest.java +++ b/remote-api/src/test/java/org/alfresco/repo/web/scripts/admin/AdminWebScriptTest.java @@ -2,7 +2,7 @@ * #%L * Alfresco Remote API * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited + * 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 @@ -31,10 +31,12 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import org.alfresco.model.ContentModel; import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.dictionary.Facetable; import org.alfresco.repo.dictionary.IndexTokenisationMode; import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authority.AuthorityServiceImpl; import org.alfresco.repo.web.scripts.BaseWebScriptTest; import org.alfresco.service.cmr.admin.RepoAdminService; import org.alfresco.service.cmr.admin.RepoUsage; @@ -48,11 +50,18 @@ import org.alfresco.service.cmr.dictionary.ModelDefinition; import org.alfresco.service.cmr.dictionary.PropertyDefinition; import org.alfresco.service.cmr.i18n.MessageLookup; import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.security.AuthorityService; +import org.alfresco.service.cmr.security.MutableAuthenticationService; +import org.alfresco.service.cmr.security.PersonService; import org.alfresco.service.descriptor.DescriptorService; import org.alfresco.service.license.LicenseDescriptor; import org.alfresco.service.namespace.QName; import org.alfresco.test_category.OwnJVMTestsCategory; +import org.alfresco.util.PropertyMap; +import org.apache.commons.lang3.RandomStringUtils; import org.json.JSONObject; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; import org.junit.Test; import org.junit.experimental.categories.Category; import org.springframework.context.ApplicationContext; @@ -74,29 +83,46 @@ import static org.mockito.Mockito.when; @Category(OwnJVMTestsCategory.class) public class AdminWebScriptTest extends BaseWebScriptTest { - private ApplicationContext ctx; - private RepoAdminService repoAdminService; - private DescriptorService descriptorService; + private RepoAdminService repoAdminService; + private DescriptorService descriptorService; + private PersonService personService; + private MutableAuthenticationService authenticationService; + private String admin; private String guest; + private String user1_sysAdmin; + private String user2; @Override protected void setUp() throws Exception { super.setUp(); - ctx = getServer().getApplicationContext(); - repoAdminService = (RepoAdminService) ctx.getBean("RepoAdminService"); - descriptorService = (DescriptorService) ctx.getBean("DescriptorService"); + ApplicationContext ctx = getServer().getApplicationContext(); + repoAdminService = ctx.getBean("RepoAdminService", RepoAdminService.class); + descriptorService = ctx.getBean("DescriptorService", DescriptorService.class); + personService = ctx.getBean("PersonService", PersonService.class); + authenticationService = ctx.getBean("AuthenticationService", MutableAuthenticationService.class); + AuthorityService authorityService = ctx.getBean("AuthorityService", AuthorityService.class); admin = AuthenticationUtil.getAdminUserName(); guest = AuthenticationUtil.getGuestUserName(); AuthenticationUtil.setFullyAuthenticatedUser(admin); + + user1_sysAdmin = RandomStringUtils.randomAlphabetic(10); + String user1_password = RandomStringUtils.randomAlphabetic(10); + createUser(user1_sysAdmin, user1_password); + authorityService.addAuthority(AuthorityServiceImpl.GROUP_ALFRESCO_SYSTEM_ADMINISTRATORS_AUTHORITY, user1_sysAdmin); + + user2 = RandomStringUtils.randomAlphabetic(10); + String user2_password = RandomStringUtils.randomAlphabetic(10); + createUser(user2, user2_password); } @Override protected void tearDown() throws Exception { super.tearDown(); + AuthenticationUtil.clearCurrentSecurityContext(); } public void testGetRestrictions() throws Exception @@ -227,6 +253,129 @@ public class AdminWebScriptTest extends BaseWebScriptTest assertTrue(property.getResidual()); } + public void testSysAdminAccess() throws Exception + { + AuthenticationUtil.clearCurrentSecurityContext(); + + String url = "/admin/admin-communitysummary"; + TestWebScriptServer.GetRequest req = new TestWebScriptServer.GetRequest(url); + + Response response = sendRequest(req, Status.STATUS_OK, user1_sysAdmin); + Document doc = Jsoup.parse(response.getContentAsString()); + assertNotNull(doc.title()); + assertTrue(doc.title().contains("System Summary")); + + // Super Admin should still have access to all the scripts + response = sendRequest(req, Status.STATUS_OK, admin); + doc = Jsoup.parse(response.getContentAsString()); + assertNotNull(doc.title()); + assertTrue(doc.title().contains("System Summary")); + } + + public void testSysAdminAccess_nodeBrowser() throws Exception + { + AuthenticationUtil.clearCurrentSecurityContext(); + + String nodeBrowserUrl = "/admin/admin-nodebrowser"; + + // test the get webscript of the node browser + TestWebScriptServer.GetRequest getReq = new TestWebScriptServer.GetRequest(nodeBrowserUrl); + // The node browser is only accessible to admins, not sysAdmins + sendRequest(getReq, Status.STATUS_UNAUTHORIZED, user1_sysAdmin); + + // test the post webscript of the node browser too + TestWebScriptServer.PostRequest postReq = new TestWebScriptServer.PostRequest(nodeBrowserUrl, "", + "multipart/form-data; boundary=----WebKitFormBoundaryjacWCXfJ3KjtRenA"); + // The node browser is only accessible to admins, not sysAdmins + sendRequest(postReq, Status.STATUS_UNAUTHORIZED, user1_sysAdmin); + + // Normal user shouldn't have access either + sendRequest(getReq, Status.STATUS_UNAUTHORIZED, user2); + + // Admin should have access to everything + Response response = sendRequest(getReq, Status.STATUS_OK, admin); + Document doc = Jsoup.parse(response.getContentAsString()); + assertNotNull(doc.title()); + assertTrue(doc.title().contains("Node Browser")); + } + + public void testSysAdminAccess_repoConsole() throws Exception + { + String repoConsoleUrl = "/admin/admin-repoconsole"; + + // test the get webscript of the repo console + TestWebScriptServer.GetRequest getReq = new TestWebScriptServer.GetRequest(repoConsoleUrl); + sendRequest(getReq, Status.STATUS_UNAUTHORIZED, user1_sysAdmin); + + // test the post webscript of the repo console too + TestWebScriptServer.PostRequest postReq = new TestWebScriptServer.PostRequest(repoConsoleUrl, "", + "multipart/form-data; boundary=----WebKitFormBoundaryjacWCXfJ3KjtRenA"); + sendRequest(postReq, Status.STATUS_UNAUTHORIZED, user1_sysAdmin); + + // Normal user shouldn't have access either + sendRequest(getReq, Status.STATUS_UNAUTHORIZED, user2); + + // Admin should have access to everything + Response response = sendRequest(getReq, Status.STATUS_OK, admin); + Document doc = Jsoup.parse(response.getContentAsString()); + assertNotNull(doc.title()); + assertTrue(doc.title().contains("Model and Messages Console")); + } + + public void testSysAdminAccess_tenantConsole() throws Exception + { + String tenantConsoleUrl = "/admin/admin-tenantconsole"; + // test the get webscript of the tenant console + TestWebScriptServer.GetRequest getReq = new TestWebScriptServer.GetRequest(tenantConsoleUrl); + sendRequest(getReq, Status.STATUS_UNAUTHORIZED, user1_sysAdmin); + + // test the post webscript of the tenant console too + TestWebScriptServer.PostRequest postReq = new TestWebScriptServer.PostRequest(tenantConsoleUrl, "", + "multipart/form-data; boundary=----WebKitFormBoundaryjacWCXfJ3KjtRenA"); + sendRequest(postReq, Status.STATUS_UNAUTHORIZED, user1_sysAdmin); + + // Normal user shouldn't have access either + sendRequest(getReq, Status.STATUS_UNAUTHORIZED, user2); + + // Admin should have access to everything + Response response = sendRequest(getReq, Status.STATUS_OK, admin); + Document doc = Jsoup.parse(response.getContentAsString()); + assertNotNull(doc.title()); + assertTrue(doc.title().contains("Tenant Admin Console")); + } + + public void testSysAdminAccess_workflowConsole() throws Exception + { + String workflowConsoleUrl = "/admin/admin-workflowconsole"; + // test the get webscript of the workflow console + TestWebScriptServer.GetRequest getReq = new TestWebScriptServer.GetRequest(workflowConsoleUrl); + sendRequest(getReq, Status.STATUS_UNAUTHORIZED, user1_sysAdmin); + + // test the post webscript of the workflow console too + TestWebScriptServer.PostRequest postReq = new TestWebScriptServer.PostRequest(workflowConsoleUrl, "", + "multipart/form-data; boundary=----WebKitFormBoundaryjacWCXfJ3KjtRenA"); + sendRequest(postReq, Status.STATUS_UNAUTHORIZED, user1_sysAdmin); + + // Normal user shouldn't have access either + sendRequest(getReq, Status.STATUS_UNAUTHORIZED, user2); + + // Admin should have access to everything + Response response = sendRequest(getReq, Status.STATUS_OK, admin); + Document doc = Jsoup.parse(response.getContentAsString()); + assertNotNull(doc.title()); + assertTrue(doc.title().contains("Workflow Admin Console")); + } + + public void testNonSysAdminAccess() throws Exception + { + AuthenticationUtil.clearCurrentSecurityContext(); + + String url = "/admin/admin-communitysummary"; + TestWebScriptServer.GetRequest req = new TestWebScriptServer.GetRequest(url); + + sendRequest(req, Status.STATUS_UNAUTHORIZED, user2); + } + private class SimplePropertyDefinition implements PropertyDefinition { private boolean isAspect; @@ -350,4 +499,19 @@ public class AdminWebScriptTest extends BaseWebScriptTest return null; } } + + private void createUser(String username, String password) + { + if (!personService.personExists(username)) + { + this.authenticationService.createAuthentication(username, password.toCharArray()); + + PropertyMap personProps = new PropertyMap(); + personProps.put(ContentModel.PROP_USERNAME, username); + personProps.put(ContentModel.PROP_FIRSTNAME, "testFirstName"); + personProps.put(ContentModel.PROP_LASTNAME, "testLastName"); + personProps.put(ContentModel.PROP_EMAIL, username + "@email.com"); + this.personService.createPerson(personProps); + } + } } diff --git a/remote-api/src/test/java/org/alfresco/repo/web/scripts/bulkimport/BulkImportParametersExtractorTest.java b/remote-api/src/test/java/org/alfresco/repo/web/scripts/bulkimport/BulkImportParametersExtractorTest.java new file mode 100644 index 0000000000..c01089cf1c --- /dev/null +++ b/remote-api/src/test/java/org/alfresco/repo/web/scripts/bulkimport/BulkImportParametersExtractorTest.java @@ -0,0 +1,252 @@ +/* + * #%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 . + * #L% + */ +package org.alfresco.repo.web.scripts.bulkimport; + +import static org.alfresco.repo.web.scripts.bulkimport.AbstractBulkFileSystemImportWebScript.PARAMETER_BATCH_SIZE; +import static org.alfresco.repo.web.scripts.bulkimport.AbstractBulkFileSystemImportWebScript.PARAMETER_DISABLE_RULES; +import static org.alfresco.repo.web.scripts.bulkimport.AbstractBulkFileSystemImportWebScript.PARAMETER_NUM_THREADS; +import static org.alfresco.repo.web.scripts.bulkimport.AbstractBulkFileSystemImportWebScript.PARAMETER_TARGET_NODEREF; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.util.Map; + +import org.alfresco.repo.bulkimport.BulkImportParameters; +import org.alfresco.repo.bulkimport.BulkImportParameters.ExistingFileMode; +import org.alfresco.repo.web.scripts.bulkimport.AbstractBulkFileSystemImportWebScript.BulkImportParametersExtractor; +import org.alfresco.service.cmr.model.FileNotFoundException; +import org.alfresco.service.cmr.repository.NodeRef; +import org.junit.Test; +import org.springframework.extensions.webscripts.WebScriptException; + +public class BulkImportParametersExtractorTest +{ + private static final String TEST_NODE_REF = "workspace://SpacesStore/this-is-just-a-test-ref"; + private static final String TEST_MISSING_NODE_REF = "workspace://SpacesStore/this-is-just-a-not-existing-test-ref"; + private static final Integer DEFAULT_BATCH_SIZE = 1234; + private static final Integer DEFAULT_NUMBER_OF_THREADS = 4321; + + @Test + public void shouldExtractTargetRef() throws FileNotFoundException + { + final BulkImportParametersExtractor extractor = givenExtractor(Map.of( + PARAMETER_TARGET_NODEREF, TEST_NODE_REF)); + + final BulkImportParameters params = extractor.extract(); + + assertNotNull(params); + assertNotNull(params.getTarget()); + assertEquals(TEST_NODE_REF, params.getTarget().toString()); + } + + @Test + public void shouldFallbackToDefaultValues() throws FileNotFoundException + { + final BulkImportParametersExtractor extractor = givenExtractor(Map.of( + PARAMETER_TARGET_NODEREF, TEST_NODE_REF)); + + final BulkImportParameters params = extractor.extract(); + + assertEquals(DEFAULT_BATCH_SIZE, params.getBatchSize()); + assertEquals(DEFAULT_NUMBER_OF_THREADS, params.getNumThreads()); + assertFalse(params.isDisableRulesService()); + assertEquals(ExistingFileMode.SKIP, params.getExistingFileMode()); + assertNull(params.getLoggingInterval()); + } + + @Test + public void shouldExtractDisableFolderRulesFlagWhenSetToTrue() throws FileNotFoundException + { + final BulkImportParametersExtractor extractor = givenExtractor(Map.of( + PARAMETER_TARGET_NODEREF, TEST_NODE_REF, + PARAMETER_DISABLE_RULES, "true" + )); + + final BulkImportParameters params = extractor.extract(); + + assertTrue(params.isDisableRulesService()); + } + + @Test + public void shouldExtractDisableFolderRulesFlagWhenSetToFalse() throws FileNotFoundException + { + final BulkImportParametersExtractor extractor = givenExtractor(Map.of( + PARAMETER_TARGET_NODEREF, TEST_NODE_REF, + PARAMETER_DISABLE_RULES, "false" + )); + + final BulkImportParameters params = extractor.extract(); + + assertFalse(params.isDisableRulesService()); + } + + @Test + public void shouldExtractDisableFolderRulesFlagWhenSetToNotBooleanValue() throws FileNotFoundException + { + final BulkImportParametersExtractor extractor = givenExtractor(Map.of( + PARAMETER_TARGET_NODEREF, TEST_NODE_REF, + PARAMETER_DISABLE_RULES, "unknown" + )); + + final BulkImportParameters params = extractor.extract(); + + assertFalse(params.isDisableRulesService()); + } + + @Test + public void shouldPropagateFileNotFoundExceptionWhenTargetIsNotFound() + { + final BulkImportParametersExtractor extractor = givenExtractor(Map.of( + PARAMETER_TARGET_NODEREF, TEST_MISSING_NODE_REF)); + + assertThrows(FileNotFoundException.class, extractor::extract); + } + + @Test + public void shouldExtractValidBatchSize() throws FileNotFoundException + { + final BulkImportParametersExtractor extractor = givenExtractor(Map.of( + PARAMETER_TARGET_NODEREF, TEST_NODE_REF, + PARAMETER_BATCH_SIZE, "1")); + + final BulkImportParameters params = extractor.extract(); + + assertEquals(Integer.valueOf(1), params.getBatchSize()); + } + + @Test + public void shouldFailWithWebScriptExceptionWhenInvalidBatchSizeIsRequested() throws FileNotFoundException + { + final BulkImportParametersExtractor extractor = givenExtractor(Map.of( + PARAMETER_TARGET_NODEREF, TEST_NODE_REF, + PARAMETER_BATCH_SIZE, "not-a-number")); + + try + { + extractor.extract(); + } catch (WebScriptException e) + { + assertNotNull(e.getMessage()); + assertTrue(e.getMessage().contains(PARAMETER_BATCH_SIZE)); + return; + } + + fail("Expected exception to be thrown."); + } + + @Test + public void shouldFailWithWebScriptExceptionWhenNegativeBatchSizeIsRequested() throws FileNotFoundException + { + final BulkImportParametersExtractor extractor = givenExtractor(Map.of( + PARAMETER_TARGET_NODEREF, TEST_NODE_REF, + PARAMETER_BATCH_SIZE, "-1")); + + try + { + extractor.extract(); + } catch (WebScriptException e) + { + assertNotNull(e.getMessage()); + assertTrue(e.getMessage().contains(PARAMETER_BATCH_SIZE)); + return; + } + + fail("Expected exception to be thrown."); + } + + @Test + public void shouldExtractValidNumberOfThreads() throws FileNotFoundException + { + final BulkImportParametersExtractor extractor = givenExtractor(Map.of( + PARAMETER_TARGET_NODEREF, TEST_NODE_REF, + PARAMETER_NUM_THREADS, "1")); + + final BulkImportParameters params = extractor.extract(); + + assertEquals(Integer.valueOf(1), params.getNumThreads()); + } + + @Test + public void shouldFailWithWebScriptExceptionWhenInvalidNumberOfThreadsIsRequested() throws FileNotFoundException + { + final BulkImportParametersExtractor extractor = givenExtractor(Map.of( + PARAMETER_TARGET_NODEREF, TEST_NODE_REF, + PARAMETER_NUM_THREADS, "not-a-number")); + + try + { + extractor.extract(); + } catch (WebScriptException e) + { + assertNotNull(e.getMessage()); + assertTrue(e.getMessage().contains(PARAMETER_NUM_THREADS)); + return; + } + + fail("Expected exception to be thrown."); + } + + @Test + public void shouldFailWithWebScriptExceptionWhenNegativeNumberOfThreadsIsRequested() throws FileNotFoundException + { + final BulkImportParametersExtractor extractor = givenExtractor(Map.of( + PARAMETER_TARGET_NODEREF, TEST_NODE_REF, + PARAMETER_NUM_THREADS, "-1")); + + try + { + extractor.extract(); + } catch (WebScriptException e) + { + assertNotNull(e.getMessage()); + assertTrue(e.getMessage().contains(PARAMETER_NUM_THREADS)); + return; + } + + fail("Expected exception to be thrown."); + } + + private BulkImportParametersExtractor givenExtractor(Map params) + { + + return new BulkImportParametersExtractor(params::get, this::testRefCreator, DEFAULT_BATCH_SIZE, DEFAULT_NUMBER_OF_THREADS); + } + + private NodeRef testRefCreator(String nodeRef, String path) throws FileNotFoundException + { + if (TEST_MISSING_NODE_REF.equals(nodeRef)) + { + throw new FileNotFoundException(new NodeRef(nodeRef)); + } + return new NodeRef(nodeRef); + } + +} \ No newline at end of file diff --git a/remote-api/src/test/java/org/alfresco/rest/DeletedNodesTest.java b/remote-api/src/test/java/org/alfresco/rest/DeletedNodesTest.java index fcbe066ffc..b1d2f36d44 100644 --- a/remote-api/src/test/java/org/alfresco/rest/DeletedNodesTest.java +++ b/remote-api/src/test/java/org/alfresco/rest/DeletedNodesTest.java @@ -2,7 +2,7 @@ * #%L * Alfresco Remote API * %% - * Copyright (C) 2005 - 2020 Alfresco Software Limited + * 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 @@ -78,8 +78,6 @@ public class DeletedNodesTest extends AbstractSingleNetworkSiteTest protected static final String URL_DELETED_NODES = "deleted-nodes"; private static final String URL_RENDITIONS = "renditions"; - - private final static long DELAY_IN_MS = 500; @Override public void setup() throws Exception @@ -704,6 +702,73 @@ public class DeletedNodesTest extends AbstractSingleNetworkSiteTest assertNull("We don't show the parent id for a deleted node",aNode.getParentId()); } + @Test + public void testRequestArchivedContentDirectUrl() throws Exception + { + setRequestContext(user1); + + String myNodeId = getMyNodeId(); + + String fileName = "TestDocumentToArchive.txt"; + Document testDocumentToArchive = new Document(); + testDocumentToArchive.setName(fileName); + testDocumentToArchive.setNodeType(TYPE_CM_CONTENT); + + // create *empty* text file + HttpResponse response = post(getNodeChildrenUrl(myNodeId), toJsonAsStringNonNull(testDocumentToArchive), 201); + Document document = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class); + + final String contentNodeId = document.getId(); + deleteNode(contentNodeId); + + // Check the upload response + assertEquals(fileName, document.getName()); + ContentInfo contentInfo = document.getContent(); + assertNotNull(contentInfo); + assertEquals(MimetypeMap.MIMETYPE_TEXT_PLAIN, contentInfo.getMimeType()); + + HttpResponse dauResponse = post(getRequestArchivedContentDirectUrl(contentNodeId), null, null, null, null, 501); + } + + @Test + public void testRequestArchivedRenditionDirectUrl() throws Exception + { + setRequestContext(user1); + + // Create a folder within the site document's library + Date now = new Date(); + String folder1 = "folder" + now.getTime() + "_1"; + Folder createdFolder = createFolder(tDocLibNodeId, folder1, null); + assertNotNull(createdFolder); + String f1Id = createdFolder.getId(); + + // Create multipart request using an existing file + String fileName = "quick.pdf"; + File file = getResourceFile(fileName); + MultiPartBuilder multiPartBuilder = MultiPartBuilder.create().setFileData(new MultiPartBuilder.FileData(fileName, file)); + MultiPartBuilder.MultiPartRequest reqBody = multiPartBuilder.build(); + + // Upload quick.pdf file into 'folder' + HttpResponse response = post(getNodeChildrenUrl(f1Id), reqBody.getBody(), null, reqBody.getContentType(), 201); + Document document = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class); + String contentNodeId = document.getId(); + + Rendition rendition = createAndGetRendition(contentNodeId, "doclib"); + assertNotNull(rendition); + String renditionID = rendition.getId(); + assertEquals(Rendition.RenditionStatus.CREATED, rendition.getStatus()); + + deleteNode(contentNodeId); + + HttpResponse dauResponse = post(getRequestArchivedRenditonContentDirectUrl(contentNodeId, renditionID), null, null, null, null, 501); + } + + private String addToDocumentLibrary(String name, String nodeType, String userId) throws Exception + { + String parentId = getSiteContainerNodeId(Nodes.PATH_MY, "documentLibrary"); + return createNode(parentId, name, nodeType, null).getId(); + } + private String getDeletedNodeRenditionsUrl(String nodeId) { return URL_DELETED_NODES + "/" + nodeId + "/" + URL_RENDITIONS; diff --git a/remote-api/src/test/java/org/alfresco/rest/api/discovery/DiscoveryApiWebscriptUnitTest.java b/remote-api/src/test/java/org/alfresco/rest/api/discovery/DiscoveryApiWebscriptUnitTest.java new file mode 100644 index 0000000000..394c13a8f1 --- /dev/null +++ b/remote-api/src/test/java/org/alfresco/rest/api/discovery/DiscoveryApiWebscriptUnitTest.java @@ -0,0 +1,87 @@ +/* + * #%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 . + * #L% + */ +package org.alfresco.rest.api.discovery; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertEquals; + +import org.alfresco.rest.api.impl.directurl.RestApiDirectUrlConfig; +import org.alfresco.service.cmr.repository.ContentService; +import org.junit.Test; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +/** + * @author Mikołaj Brzeziński + */ +public class DiscoveryApiWebscriptUnitTest +{ + private static final Boolean ENABLED = Boolean.TRUE; + private static final Boolean DISABLED = Boolean.FALSE; + + private DiscoveryApiWebscript discoveryApiWebscript = mock(DiscoveryApiWebscript.class); + private RestApiDirectUrlConfig restApiDirectUrlConfig = mock(RestApiDirectUrlConfig.class); + private ContentService contentService = mock(ContentService.class); + + public void mockedAsserts(boolean restEnabled, boolean systemwideEnabled) + { + when(contentService.isContentDirectUrlEnabled()).thenReturn(systemwideEnabled); + when(restApiDirectUrlConfig.isEnabled()).thenReturn(restEnabled); + assertEquals(systemwideEnabled, contentService.isContentDirectUrlEnabled()); + assertEquals(restEnabled, restApiDirectUrlConfig.isEnabled()); + when(discoveryApiWebscript.isContentDirectUrlEnabled()).thenReturn(restEnabled && systemwideEnabled); + } + + @Test + public void testEnabledConfig_RestEnabledAndSystemwideEnabled() + { + mockedAsserts(ENABLED,ENABLED); + assertTrue("Direct Acess URLs are enabled",discoveryApiWebscript.isContentDirectUrlEnabled()); + } + + @Test + public void testDisabledConfig_RestEnabledAndSystemwideDisabled() + { + mockedAsserts(ENABLED,DISABLED); + assertFalse("Direct Access URLs are disabled system-wide",discoveryApiWebscript.isContentDirectUrlEnabled()); + } + + @Test + public void testDisabledConfig_RestDisabledAndSystemwideDisabled() + { + mockedAsserts(DISABLED,DISABLED); + assertFalse("REST API Direct Access URLs are disabled and Direct Access URLs are disabled system-wide ",discoveryApiWebscript.isContentDirectUrlEnabled()); + } + + @Test + public void testDisabledConfig_RestDisabledAndSystemwideEnabled() + { + mockedAsserts(DISABLED,ENABLED); + assertFalse("REST API direct access URLs are disabled",discoveryApiWebscript.isContentDirectUrlEnabled()); + } + +} diff --git a/remote-api/src/test/java/org/alfresco/rest/api/impl/RestApiDirectUrlConfigUnitTest.java b/remote-api/src/test/java/org/alfresco/rest/api/impl/RestApiDirectUrlConfigUnitTest.java new file mode 100644 index 0000000000..e04c07338f --- /dev/null +++ b/remote-api/src/test/java/org/alfresco/rest/api/impl/RestApiDirectUrlConfigUnitTest.java @@ -0,0 +1,132 @@ +/* + * #%L + * Alfresco Repository + * %% + * 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 . + * #L% + */ +package org.alfresco.rest.api.impl; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import org.alfresco.repo.content.directurl.SystemWideDirectUrlConfig; +import org.alfresco.rest.api.impl.directurl.RestApiDirectUrlConfig; +import org.junit.Before; +import org.junit.Test; + +/** + * Tests for REST API direct access URL configuration settings. + * + * @author Sara Aspery + */ +public class RestApiDirectUrlConfigUnitTest +{ + private static final Boolean ENABLED = Boolean.TRUE; + private static final Boolean DISABLED = Boolean.FALSE; + + private static final Long DEFAULT_EXPIRY_TIME_IN_SECS = 20L; + + private RestApiDirectUrlConfig restApiDirectUrlConfig; + + @Before + public void setup() + { + this.restApiDirectUrlConfig = new RestApiDirectUrlConfig(); + SystemWideDirectUrlConfig sysConfig = new SystemWideDirectUrlConfig(); + sysConfig.setEnabled(ENABLED); + sysConfig.setDefaultExpiryTimeInSec(30L); + sysConfig.setMaxExpiryTimeInSec(300L); + restApiDirectUrlConfig.setSystemWideDirectUrlConfig(sysConfig); + } + + @Test + public void testValidConfig_RemainsEnabled() + { + setupDirectAccessConfig(ENABLED, DEFAULT_EXPIRY_TIME_IN_SECS); + + assertTrue("Expected REST API direct URLs to be enabled", restApiDirectUrlConfig.isEnabled()); + restApiDirectUrlConfig.validate(); + assertTrue("Expected REST API direct URLs to be enabled", restApiDirectUrlConfig.isEnabled()); + } + + @Test + public void testValidConfig_RemainsDisabled() + { + setupDirectAccessConfig(DISABLED, DEFAULT_EXPIRY_TIME_IN_SECS); + + assertFalse("Expected REST API direct URLs to be disabled", restApiDirectUrlConfig.isEnabled()); + restApiDirectUrlConfig.validate(); + assertFalse("Expected REST API direct URLs to be disabled", restApiDirectUrlConfig.isEnabled()); + } + + @Test + public void testValidConfig_DefaultExpiryTimeMissing() + { + setupDirectAccessConfig(ENABLED, null); + + assertNull("Expected REST API default expiry time to be null", restApiDirectUrlConfig.getDefaultExpiryTimeInSec()); + restApiDirectUrlConfig.validate(); + Long expectedDefaultExpiryTime = restApiDirectUrlConfig.getSysWideDefaultExpiryTimeInSec(); + assertEquals("Expected REST API default expiry time to be set to the system-wide default", expectedDefaultExpiryTime, restApiDirectUrlConfig.getDefaultExpiryTimeInSec()); + assertTrue("Expected REST API direct URLs to be enabled", restApiDirectUrlConfig.isEnabled()); + } + + @Test + public void testInvalidConfig_DefaultExpiryTimeZero() + { + setupDirectAccessConfig(ENABLED, 0L); + + assertTrue("Expected REST API direct URLs to be enabled", restApiDirectUrlConfig.isEnabled()); + restApiDirectUrlConfig.validate(); + assertFalse("Expected REST API direct URLs to be disabled", restApiDirectUrlConfig.isEnabled()); + } + + @Test + public void testInvalidConfig_DefaultExpiryTimeNegative() + { + setupDirectAccessConfig(ENABLED, -1L); + + assertTrue("Expected REST API direct URLs to be enabled", restApiDirectUrlConfig.isEnabled()); + restApiDirectUrlConfig.validate(); + assertFalse("Expected REST API direct URLs to be disabled", restApiDirectUrlConfig.isEnabled()); + } + + @Test + public void testInvalidConfig_DefaultExpiryTimeExceedsSystemMax() + { + Long systemMax = restApiDirectUrlConfig.getSysWideMaxExpiryTimeInSec(); + setupDirectAccessConfig(ENABLED, systemMax + 1); + + assertTrue("Expected REST API direct URLs to be enabled", restApiDirectUrlConfig.isEnabled()); + restApiDirectUrlConfig.validate(); + assertFalse("Expected REST API direct URLs to be disabled", restApiDirectUrlConfig.isEnabled()); + } + + /* Helper method to set system-wide direct access url configuration settings */ + private void setupDirectAccessConfig(Boolean isEnabled, Long defaultExpiryTime) + { + restApiDirectUrlConfig.setEnabled(isEnabled); + restApiDirectUrlConfig.setDefaultExpiryTimeInSec(defaultExpiryTime); + } +} diff --git a/remote-api/src/test/java/org/alfresco/rest/api/tests/AbstractBaseApiTest.java b/remote-api/src/test/java/org/alfresco/rest/api/tests/AbstractBaseApiTest.java index 4938ecf42e..c6f0d16d77 100644 --- a/remote-api/src/test/java/org/alfresco/rest/api/tests/AbstractBaseApiTest.java +++ b/remote-api/src/test/java/org/alfresco/rest/api/tests/AbstractBaseApiTest.java @@ -2,7 +2,7 @@ * #%L * Alfresco Remote API * %% - * Copyright (C) 2005 - 2020 Alfresco Software Limited + * 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 @@ -96,6 +96,7 @@ public abstract class AbstractBaseApiTest extends EnterpriseTestApi private static final String RESOURCE_PREFIX = "publicapi/upload/"; protected static final String URL_NODES = "nodes"; + protected static final String URL_DELETED_NODES = "deleted-nodes"; protected static final String URL_RENDITIONS = "renditions"; protected static final String URL_VERSIONS = "versions"; @@ -136,6 +137,8 @@ public abstract class AbstractBaseApiTest extends EnterpriseTestApi protected static PersonService personService; protected final String RUNID = System.currentTimeMillis()+""; + + private static final String REQUEST_DIRECT_ACCESS_URL = "request-direct-access-url"; @Override @Before @@ -211,6 +214,21 @@ public abstract class AbstractBaseApiTest extends EnterpriseTestApi setRequestContext(null); } + protected String getRequestContentDirectUrl(String nodeId) + { + return URL_NODES + "/" + nodeId + "/" + REQUEST_DIRECT_ACCESS_URL; + } + + protected String getRequestArchivedContentDirectUrl(String nodeId) + { + return URL_DELETED_NODES + "/" + nodeId + "/" + REQUEST_DIRECT_ACCESS_URL; + } + + protected String getRequestArchivedRenditonContentDirectUrl(String nodeId, String renditionID) + { + return URL_DELETED_NODES + "/" + nodeId + "/" + URL_RENDITIONS + "/" + renditionID + "/" + REQUEST_DIRECT_ACCESS_URL; + } + /** * The api scope. either public or private * diff --git a/remote-api/src/test/java/org/alfresco/rest/api/tests/BufferedResponseTest.java b/remote-api/src/test/java/org/alfresco/rest/api/tests/BufferedResponseTest.java index 39c86ed916..2eb0f15df9 100644 --- a/remote-api/src/test/java/org/alfresco/rest/api/tests/BufferedResponseTest.java +++ b/remote-api/src/test/java/org/alfresco/rest/api/tests/BufferedResponseTest.java @@ -26,23 +26,23 @@ */ package org.alfresco.rest.api.tests; -import org.alfresco.repo.web.scripts.BufferedResponse; -import org.alfresco.repo.web.scripts.TempOutputStream; -import org.alfresco.repo.web.scripts.TempOutputStreamFactory; -import org.alfresco.util.TempFileProvider; -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; - import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.io.RandomAccessFile; import java.nio.file.Files; import java.util.Arrays; +import java.util.function.Supplier; import java.util.stream.Stream; +import org.alfresco.repo.web.scripts.BufferedResponse; +import org.alfresco.repo.web.scripts.TempOutputStream; +import org.alfresco.util.TempFileProvider; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + /** * Test that BufferedResponse uses a temp file instead of buffering the entire output stream in memory * @@ -82,17 +82,25 @@ public class BufferedResponseTest public void testOutputStream() throws IOException { File bufferTempDirectory = TempFileProvider.getTempDir(TEMP_DIRECTORY_NAME); - TempOutputStreamFactory streamFactory = new TempOutputStreamFactory(bufferTempDirectory, MEMORY_THRESHOLD, MAX_CONTENT_SIZE, false,true); - BufferedResponse response = new BufferedResponse(null, 0, streamFactory); + Supplier streamFactory = TempOutputStream.factory(bufferTempDirectory, + MEMORY_THRESHOLD, MAX_CONTENT_SIZE, false); - long countBefore = countFilesInDirectoryWithPrefix(bufferTempDirectory, FILE_PREFIX ); - copyFileToOutputStream(response); - long countAfter = countFilesInDirectoryWithPrefix(bufferTempDirectory, FILE_PREFIX); - - response.getOutputStream().close(); + final long countBefore = countFilesInDirectoryWithPrefix(bufferTempDirectory, FILE_PREFIX); - Assert.assertEquals(countBefore + 1, countAfter); + try (BufferedResponse response = new BufferedResponse(null, 0, streamFactory)) + { + copyFileToOutputStream(response); + final long countBeforeClose = countFilesInDirectoryWithPrefix(bufferTempDirectory, FILE_PREFIX); + response.getOutputStream().close(); + final long countAfterClose = countFilesInDirectoryWithPrefix(bufferTempDirectory, FILE_PREFIX); + + Assert.assertEquals(countBefore + 1, countBeforeClose); + Assert.assertEquals(countBefore + 1, countAfterClose); + } + + final long countAfterDestroy = countFilesInDirectoryWithPrefix(bufferTempDirectory, FILE_PREFIX); + Assert.assertEquals(countBefore, countAfterDestroy); } private void copyFileToOutputStream(BufferedResponse response) throws IOException diff --git a/remote-api/src/test/java/org/alfresco/rest/api/tests/DiscoveryApiTest.java b/remote-api/src/test/java/org/alfresco/rest/api/tests/DiscoveryApiTest.java index 6cfbdb9885..f244401350 100644 --- a/remote-api/src/test/java/org/alfresco/rest/api/tests/DiscoveryApiTest.java +++ b/remote-api/src/test/java/org/alfresco/rest/api/tests/DiscoveryApiTest.java @@ -2,7 +2,7 @@ * #%L * Alfresco Remote API * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited + * 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 @@ -185,6 +185,7 @@ public class DiscoveryApiTest extends AbstractSingleNetworkSiteTest assertTrue(statusInfo.getIsAuditEnabled()); assertTrue(statusInfo.getIsQuickShareEnabled()); assertTrue(statusInfo.getIsThumbnailGenerationEnabled()); + assertFalse(statusInfo.getIsDirectAccessUrlEnabled()); // Check modules List modulePackageList = repositoryInfo.getModules(); diff --git a/remote-api/src/test/java/org/alfresco/rest/api/tests/GroupsTest.java b/remote-api/src/test/java/org/alfresco/rest/api/tests/GroupsTest.java index e84ee463e1..99c57462dd 100644 --- a/remote-api/src/test/java/org/alfresco/rest/api/tests/GroupsTest.java +++ b/remote-api/src/test/java/org/alfresco/rest/api/tests/GroupsTest.java @@ -836,13 +836,13 @@ public class GroupsTest extends AbstractSingleNetworkSiteTest // Get network admin's groups by explicit ID. { ListResponse groups = groupsProxy.getGroupsByPersonId(networkAdmin, null, "Couldn't get user's groups", 200); - assertEquals(6L, (long) groups.getPaging().getTotalItems()); + assertEquals(7L, (long) groups.getPaging().getTotalItems()); } // test -me- alias (as network admin) { ListResponse groups = groupsProxy.getGroupsByPersonId("-me-", null, "Couldn't get user's groups", 200); - assertEquals(6L, (long) groups.getPaging().getCount()); + assertEquals(7L, (long) groups.getPaging().getCount()); Iterator it = groups.getList().iterator(); assertEquals("GROUP_ALFRESCO_ADMINISTRATORS", it.next().getId()); } diff --git a/remote-api/src/test/java/org/alfresco/rest/api/tests/NodeApiTest.java b/remote-api/src/test/java/org/alfresco/rest/api/tests/NodeApiTest.java index 94762872c0..8acf9683f2 100644 --- a/remote-api/src/test/java/org/alfresco/rest/api/tests/NodeApiTest.java +++ b/remote-api/src/test/java/org/alfresco/rest/api/tests/NodeApiTest.java @@ -2,7 +2,7 @@ * #%L * Alfresco Remote API * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited + * 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 @@ -82,6 +82,7 @@ import org.alfresco.rest.api.tests.client.data.Folder; import org.alfresco.rest.api.tests.client.data.Node; import org.alfresco.rest.api.tests.client.data.PathInfo; import org.alfresco.rest.api.tests.client.data.PathInfo.ElementInfo; +import org.alfresco.rest.api.tests.client.data.Rendition; import org.alfresco.rest.api.tests.client.data.SiteRole; import org.alfresco.rest.api.tests.client.data.UserInfo; import org.alfresco.rest.api.tests.util.MultiPartBuilder; @@ -6355,5 +6356,120 @@ public class NodeApiTest extends AbstractSingleNetworkSiteTest assertFalse((Boolean) constraintParameters.get("requiresMatch")); } + @Test + public void testRequestContentDirectUrl() throws Exception + { + setRequestContext(user1); + + // Use existing test file + String fileName = "quick-1.txt"; + File file = getResourceFile(fileName); + + MultiPartBuilder multiPartBuilder = MultiPartBuilder.create().setFileData(new MultiPartBuilder.FileData(fileName, file)); + MultiPartBuilder.MultiPartRequest reqBody = multiPartBuilder.build(); + + // Upload text content + HttpResponse response = post(getNodeChildrenUrl(Nodes.PATH_MY), reqBody.getBody(), null, reqBody.getContentType(), 201); + Document document = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class); + + final String contentNodeId = document.getId(); + + // Check the upload response + assertEquals(fileName, document.getName()); + ContentInfo contentInfo = document.getContent(); + assertNotNull(contentInfo); + assertEquals(MimetypeMap.MIMETYPE_TEXT_PLAIN, contentInfo.getMimeType()); + + HttpResponse dauResponse = post(getRequestContentDirectUrl(contentNodeId), null, null, null, null, 501); + } + + @Test + public void testRequestVersionsContentDirectUrl() throws Exception + { + setRequestContext(user1); + + String myNodeId = getMyNodeId(); + + Document d1 = new Document(); + d1.setName("d1.txt"); + d1.setNodeType(TYPE_CM_CONTENT); + + // create *empty* text file - as of now, versioning is not enabled by default + HttpResponse response = post(getNodeChildrenUrl(myNodeId), toJsonAsStringNonNull(d1), 201); + Document documentResp = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class); + + String docId = documentResp.getId(); + assertFalse(documentResp.getAspectNames().contains("cm:versionable")); + assertNull(documentResp.getProperties()); // no properties (ie. no "cm:versionLabel") + + int majorVersion = 1; + int minorVersion = 0; + + String content = "The quick brown fox jumps over the lazy dog "; + + Map params = new HashMap<>(); + params.put("comment", "my version "); + + documentResp = updateTextFile(docId, content, params); + assertTrue(documentResp.getAspectNames().contains("cm:versionable")); + assertNotNull(documentResp.getProperties()); + assertEquals(majorVersion+"."+minorVersion, documentResp.getProperties().get("cm:versionLabel")); + + final String contentNodeId = documentResp.getId(); + + // Check the upload response + assertNotNull(documentResp.getProperties()); + assertTrue(documentResp.getAspectNames().contains("cm:versionable")); + ContentInfo contentInfo = documentResp.getContent(); + assertNotNull(contentInfo); + assertEquals(MimetypeMap.MIMETYPE_TEXT_PLAIN, contentInfo.getMimeType()); + + HttpResponse dauResponse = post(getRequestContentDirectUrl(contentNodeId), null, null, null, null, 501); + } + + @Test + public void testRequestRenditionContentDirectUrl() throws Exception + { + setRequestContext(user1); + + RepoService.TestNetwork networkN1; + RepoService.TestPerson userOneN1; + Site userOneN1Site; + + networkN1 = repoService.createNetworkWithAlias("ping", true); + networkN1.create(); + userOneN1 = networkN1.createUser(); + + setRequestContext(networkN1.getId(), userOneN1.getId(), null); + + String siteTitle = "RandomSite" + System.currentTimeMillis(); + userOneN1Site = createSite(siteTitle, SiteVisibility.PRIVATE); + + // Create a folder within the site document's library + String folderName = "folder" + System.currentTimeMillis(); + String parentId = getSiteContainerNodeId(userOneN1Site.getId(), "documentLibrary"); + String folder_Id = createNode(parentId, folderName, TYPE_CM_FOLDER, null).getId(); + + // Create multipart request - pdf file + String renditionName = "doclib"; + String fileName = "quick.pdf"; + File file = getResourceFile(fileName); + MultiPartRequest reqBody = MultiPartBuilder.create() + .setFileData(new FileData(fileName, file)) + .setRenditions(Collections.singletonList(renditionName)) + .build(); + + // Upload quick.pdf file into 'folder' - including request to create 'doclib' thumbnail + HttpResponse response = post(getNodeChildrenUrl(folder_Id), reqBody.getBody(), null, reqBody.getContentType(), 201); + Document document = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class); + String contentNodeId = document.getId(); + + // wait and check that rendition is created ... + Rendition rendition = waitAndGetRendition(contentNodeId, null, renditionName); + assertNotNull(rendition); + assertEquals(Rendition.RenditionStatus.CREATED, rendition.getStatus()); + + HttpResponse dauResponse = post(getRequestContentDirectUrl(contentNodeId), null, null, null, null, 501); + } } diff --git a/remote-api/src/test/java/org/alfresco/rest/api/tests/TempOutputStreamTest.java b/remote-api/src/test/java/org/alfresco/rest/api/tests/TempOutputStreamTest.java index ae7b475f99..653e9256d8 100644 --- a/remote-api/src/test/java/org/alfresco/rest/api/tests/TempOutputStreamTest.java +++ b/remote-api/src/test/java/org/alfresco/rest/api/tests/TempOutputStreamTest.java @@ -33,11 +33,11 @@ import java.io.IOException; import java.io.RandomAccessFile; import java.nio.charset.Charset; import java.util.Arrays; +import java.util.function.Supplier; import java.util.stream.Stream; import org.alfresco.repo.content.ContentLimitViolationException; import org.alfresco.repo.web.scripts.TempOutputStream; -import org.alfresco.repo.web.scripts.TempOutputStreamFactory; import org.alfresco.util.TempFileProvider; import org.junit.Assert; import org.junit.Test; @@ -57,11 +57,12 @@ public class TempOutputStreamTest @Test public void testInMemoryStream() throws IOException { - TempOutputStreamFactory streamFactory = new TempOutputStreamFactory(bufferTempDirectory, MEMORY_THRESHOLD, MAX_CONTENT_SIZE, false, false); + Supplier streamFactory = TempOutputStream.factory(bufferTempDirectory, + MEMORY_THRESHOLD, MAX_CONTENT_SIZE, false); File file = createTextFileWithRandomContent(MEMORY_THRESHOLD - 1024L); { - TempOutputStream outputStream = streamFactory.createOutputStream(); + TempOutputStream outputStream = streamFactory.get(); long countBefore = countFilesInDirectoryWithPrefix(bufferTempDirectory); @@ -83,8 +84,8 @@ public class TempOutputStreamTest { // Create stream factory that doesn't delete temp file on stream close - TempOutputStreamFactory streamFactory = new TempOutputStreamFactory(bufferTempDirectory, MEMORY_THRESHOLD, MAX_CONTENT_SIZE, false, false); - TempOutputStream outputStream = streamFactory.createOutputStream(); + Supplier streamFactory = TempOutputStream.factory(bufferTempDirectory, MEMORY_THRESHOLD, MAX_CONTENT_SIZE, false); + TempOutputStream outputStream = streamFactory.get(); long countBefore = countFilesInDirectoryWithPrefix(bufferTempDirectory); @@ -107,26 +108,6 @@ public class TempOutputStreamTest Assert.assertEquals(countBefore, countAfter); } - { - // Create stream factory that deletes temp file on stream close - TempOutputStreamFactory streamFactory = new TempOutputStreamFactory(bufferTempDirectory, MEMORY_THRESHOLD, MAX_CONTENT_SIZE, false, true); - TempOutputStream outputStream = streamFactory.createOutputStream(); - - long countBefore = countFilesInDirectoryWithPrefix(bufferTempDirectory); - - StreamUtils.copy(new BufferedInputStream(new FileInputStream(file)), outputStream); - - // Check that temp file was created - long countAfter = countFilesInDirectoryWithPrefix(bufferTempDirectory); - Assert.assertEquals(countBefore + 1, countAfter); - - outputStream.close(); - - // Check that file was deleted on close - countAfter = countFilesInDirectoryWithPrefix(bufferTempDirectory); - Assert.assertEquals(countBefore, countAfter); - } - file.delete(); } @@ -140,9 +121,9 @@ public class TempOutputStreamTest File file = createTextFileWithRandomContent(contentSize); - // Create stream factory that deletes temp file on stream close - TempOutputStreamFactory streamFactory = new TempOutputStreamFactory(bufferTempDirectory, MEMORY_THRESHOLD, maxContentSize, false, true); - TempOutputStream outputStream = streamFactory.createOutputStream(); + // Create stream factory that deletes the temp file when the max Size is reached + Supplier streamFactory = TempOutputStream.factory(bufferTempDirectory, MEMORY_THRESHOLD, maxContentSize, false); + TempOutputStream outputStream = streamFactory.get(); long countBefore = countFilesInDirectoryWithPrefix(bufferTempDirectory); @@ -156,7 +137,7 @@ public class TempOutputStreamTest // Expected } - // Check that file was already deleted on close + // Check that file was already deleted on error long countAfter = countFilesInDirectoryWithPrefix(bufferTempDirectory); Assert.assertEquals(countBefore, countAfter); @@ -170,9 +151,9 @@ public class TempOutputStreamTest File file = createTextFileWithRandomContent(contentSize); - // Create stream factory that deletes temp file on stream close - TempOutputStreamFactory streamFactory = new TempOutputStreamFactory(bufferTempDirectory, MEMORY_THRESHOLD, maxContentSize, false, true); - TempOutputStream outputStream = streamFactory.createOutputStream(); + // Create stream factory that deletes the temp file when the max Size is reached + Supplier streamFactory = TempOutputStream.factory(bufferTempDirectory, MEMORY_THRESHOLD, maxContentSize, false); + TempOutputStream outputStream = streamFactory.get(); long countBefore = countFilesInDirectoryWithPrefix(bufferTempDirectory); @@ -186,7 +167,7 @@ public class TempOutputStreamTest // Expected } - // Check that file was already deleted on close + // Check that file was already deleted on error long countAfter = countFilesInDirectoryWithPrefix(bufferTempDirectory); Assert.assertEquals(countBefore, countAfter); @@ -200,9 +181,9 @@ public class TempOutputStreamTest File file = createTextFileWithRandomContent(MEMORY_THRESHOLD + 1024L); // Create stream factory that doesn't delete temp file on stream close - TempOutputStreamFactory streamFactory = new TempOutputStreamFactory(bufferTempDirectory, MEMORY_THRESHOLD, MAX_CONTENT_SIZE, true, false); + Supplier streamFactory = TempOutputStream.factory(bufferTempDirectory, MEMORY_THRESHOLD, MAX_CONTENT_SIZE, true); - TempOutputStream outputStream = streamFactory.createOutputStream(); + TempOutputStream outputStream = streamFactory.get(); long countBefore = countFilesInDirectoryWithPrefix(bufferTempDirectory); @@ -220,7 +201,7 @@ public class TempOutputStreamTest // Compare content String contentWriten = StreamUtils.copyToString(new BufferedInputStream(new FileInputStream(file)), Charset.defaultCharset()); - String contentRead = StreamUtils.copyToString(outputStream.getInputStream(), Charset.defaultCharset()); + String contentRead = StreamUtils.copyToString(outputStream.toNewInputStream(), Charset.defaultCharset()); Assert.assertEquals(contentWriten, contentRead); outputStream.destroy(); diff --git a/remote-api/src/test/java/org/alfresco/rest/api/tests/TestCMIS.java b/remote-api/src/test/java/org/alfresco/rest/api/tests/TestCMIS.java index 8c3ec87f43..94ebd5b135 100644 --- a/remote-api/src/test/java/org/alfresco/rest/api/tests/TestCMIS.java +++ b/remote-api/src/test/java/org/alfresco/rest/api/tests/TestCMIS.java @@ -60,8 +60,11 @@ import org.alfresco.cmis.client.impl.AlfrescoObjectFactoryImpl; import org.alfresco.cmis.client.type.AlfrescoType; import org.alfresco.model.ContentModel; import org.alfresco.model.RenditionModel; +import org.alfresco.opencmis.CMISDispatcher; import org.alfresco.opencmis.CMISDispatcherRegistry.Binding; +import org.alfresco.opencmis.CMISServletDispatcher; import org.alfresco.opencmis.PublicApiAlfrescoCmisServiceFactory; +import org.alfresco.opencmis.PublicApiBrowserCMISDispatcher; import org.alfresco.opencmis.dictionary.CMISStrictDictionaryService; import org.alfresco.opencmis.dictionary.QNameFilter; import org.alfresco.opencmis.dictionary.QNameFilterImpl; @@ -99,6 +102,7 @@ import org.alfresco.rest.api.tests.client.data.NodeRating.Aggregate; import org.alfresco.rest.api.tests.client.data.Person; import org.alfresco.rest.api.tests.client.data.SiteRole; import org.alfresco.rest.api.tests.client.data.Tag; +import org.alfresco.rest.framework.core.exceptions.JsonpCallbackNotAllowedException; import org.alfresco.service.cmr.lock.LockService; import org.alfresco.service.cmr.lock.LockType; import org.alfresco.service.cmr.model.FileFolderService; @@ -153,6 +157,7 @@ import org.junit.Ignore; import org.junit.Test; import org.junit.experimental.categories.Category; import org.springframework.context.ApplicationContext; +import org.springframework.extensions.surf.util.I18NUtil; import org.springframework.extensions.surf.util.URLEncoder; public class TestCMIS extends EnterpriseTestApi @@ -517,6 +522,73 @@ public class TestCMIS extends EnterpriseTestApi assertEquals(200, response.getStatusCode()); } + /** + * MNT-22428 Check the return from http://localhost:8080/alfresco/api/-default-/public/cmis/versions/1.1/browser/root&callback= when jsonp callback is disabled + * + * @throws Exception + */ + @Test + public void testBrowserDisabledJSONPCallback() throws Exception + { + // disables unsecure callback jsonp + final PublicApiBrowserCMISDispatcher dispatcher = ctx.getBean(PublicApiBrowserCMISDispatcher.class); + dispatcher.setAllowUnsecureCallbackJSONP(false); + + final TestNetwork network1 = getTestFixture().getRandomNetwork(); + Iterator personIt = network1.getPersonIds().iterator(); + final String personId = personIt.next(); + assertNotNull(personId); + Person person = repoService.getPerson(personId); + assertNotNull(person); + + publicApiClient.setRequestContext(new RequestContext(network1.getId(), personId)); + + // request with a callback parameter + HttpResponse response; + final Map params = Map.of("callback", ""); + response = publicApiClient.get(network1.getId() + "/public/cmis/versions/1.1/browser/root", params); + assertEquals(403, response.getStatusCode()); + + String exceptionMessage = I18NUtil.getMessage(JsonpCallbackNotAllowedException.DEFAULT_MESSAGE_ID, params); + assertTrue(response.getResponse().endsWith(exceptionMessage)); + + // request without a callback parameter + response = publicApiClient.get(network1.getId() + "/public/cmis/versions/1.1/browser/root", null); + assertEquals(200, response.getStatusCode()); + } + + /* + * MNT-22428 Check the return from http://localhost:8080/alfresco/api/-default-/public/cmis/versions/1.1/browser/root&callback= when jsonp callback is enabled + * + * @throws Exception + */ + @Test + public void testBrowserEnabledJSONPCallback() throws Exception + { + // enables unsecure callback jsonp + final PublicApiBrowserCMISDispatcher dispatcher = ctx.getBean(PublicApiBrowserCMISDispatcher.class); + dispatcher.setAllowUnsecureCallbackJSONP(true); + + final TestNetwork network1 = getTestFixture().getRandomNetwork(); + Iterator personIt = network1.getPersonIds().iterator(); + final String personId = personIt.next(); + assertNotNull(personId); + Person person = repoService.getPerson(personId); + assertNotNull(person); + + publicApiClient.setRequestContext(new RequestContext(network1.getId(), personId)); + + // request with a callback parameter + HttpResponse response; + final Map params = Map.of("callback", "someFunction"); + response = publicApiClient.get(network1.getId() + "/public/cmis/versions/1.1/browser/root", params); + assertEquals(200, response.getStatusCode()); + + // request without a callback parameter + response = publicApiClient.get(network1.getId() + "/public/cmis/versions/1.1/browser/root", null); + assertEquals(200, response.getStatusCode()); + } + /** * REPO-2041 / MNT-16236 Upload via cmis binding atom and browser files with different maxContentSize */ diff --git a/repository/pom.xml b/repository/pom.xml index 3ad0b97b31..54bc8aaf71 100644 --- a/repository/pom.xml +++ b/repository/pom.xml @@ -7,7 +7,7 @@ org.alfresco alfresco-community-repo - 11.94-SNAPSHOT + 14.7-SNAPSHOT @@ -718,7 +718,7 @@ org.alfresco alfresco-transform-model - ${dependency.transform.model.version} + ${dependency.alfresco-transform-model.version} @@ -740,7 +740,7 @@ org.alfresco alfresco-transform-model - ${dependency.transform.model.version} + ${dependency.alfresco-transform-model.version} tests test diff --git a/repository/src/main/java/org/alfresco/repo/content/ContentServiceImpl.java b/repository/src/main/java/org/alfresco/repo/content/ContentServiceImpl.java index 2ce6f1e241..eb97162334 100644 --- a/repository/src/main/java/org/alfresco/repo/content/ContentServiceImpl.java +++ b/repository/src/main/java/org/alfresco/repo/content/ContentServiceImpl.java @@ -2,7 +2,7 @@ * #%L * Alfresco Repository * %% - * Copyright (C) 2005 - 2019 Alfresco Software Limited + * 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 @@ -25,6 +25,12 @@ */ package org.alfresco.repo.content; +import java.io.Serializable; +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + import java.io.Serializable; import java.util.Collection; import java.util.Date; @@ -38,6 +44,8 @@ import org.alfresco.repo.content.ContentServicePolicies.OnContentPropertyUpdateP import org.alfresco.repo.content.ContentServicePolicies.OnContentReadPolicy; import org.alfresco.repo.content.ContentServicePolicies.OnContentUpdatePolicy; import org.alfresco.repo.content.cleanup.EagerContentStoreCleaner; +import org.alfresco.repo.content.directurl.DirectAccessUrlDisabledException; +import org.alfresco.repo.content.directurl.SystemWideDirectUrlConfig; import org.alfresco.repo.content.filestore.FileContentStore; import org.alfresco.repo.node.NodeServicePolicies; import org.alfresco.repo.policy.ClassPolicyDelegate; @@ -54,6 +62,7 @@ import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.DirectAccessUrl; +import org.alfresco.service.cmr.repository.InvalidNodeRefException; import org.alfresco.service.cmr.repository.MimetypeService; import org.alfresco.service.cmr.repository.MimetypeServiceAware; import org.alfresco.service.cmr.repository.NodeRef; @@ -82,7 +91,7 @@ import org.springframework.extensions.surf.util.I18NUtil; */ public class ContentServiceImpl implements ContentService, ApplicationContextAware { - private static Log logger = LogFactory.getLog(ContentServiceImpl.class); + private static final Log logger = LogFactory.getLog(ContentServiceImpl.class); private DictionaryService dictionaryService; private NodeService nodeService; @@ -99,6 +108,8 @@ public class ContentServiceImpl implements ContentService, ApplicationContextAwa /** Should we consider zero byte content to be the same as no content? */ private boolean ignoreEmptyContent; + private SystemWideDirectUrlConfig systemWideDirectUrlConfig; + /** * The policy component */ @@ -140,7 +151,12 @@ public class ContentServiceImpl implements ContentService, ApplicationContextAwa { this.store = store; } - + + public void setSystemWideDirectUrlConfig(SystemWideDirectUrlConfig systemWideDirectUrlConfig) + { + this.systemWideDirectUrlConfig = systemWideDirectUrlConfig; + } + public void setPolicyComponent(PolicyComponent policyComponent) { this.policyComponent = policyComponent; @@ -451,16 +467,15 @@ public class ContentServiceImpl implements ContentService, ApplicationContextAwa { return getWriter(nodeRef,propertyQName, update, null); } - + public ContentWriter getWriter(NodeRef nodeRef, QName propertyQName, boolean update, - StorageClassSet storageClassSet) + StorageClassSet storageClassSet) { if (!isStorageClassesSupported(storageClassSet)) { throw new UnsupportedStorageClassException(store, storageClassSet, - "The supplied storage classes are not supported"); + "The supplied storage classes are not supported"); } - if (nodeRef == null) { ContentContext ctx = new ContentContext(null, null, storageClassSet); @@ -477,18 +492,18 @@ public class ContentServiceImpl implements ContentService, ApplicationContextAwa if (storageClassSet != null) { - if (existingContentReader != null && - existingContentReader.getContentData() != null && + if (existingContentReader != null && + existingContentReader.getContentData() != null && existingContentReader.getContentData().getContentUrl() != null) { Set currentStorageClasses = findStorageClasses(nodeRef); - if (currentStorageClasses != null && + if (currentStorageClasses != null && !currentStorageClasses.equals(storageClassSet)) { Set possibleTransitions = findStorageClassesTransitions(nodeRef) .get(currentStorageClasses); - if (possibleTransitions == null || + if (possibleTransitions == null || !possibleTransitions.contains(storageClassSet)) { throw new UnsupportedStorageClassException(store, storageClassSet, @@ -501,11 +516,10 @@ public class ContentServiceImpl implements ContentService, ApplicationContextAwa } } } - + // get the content using the (potentially) existing content - the new content // can be wherever the store decides. - ContentContext ctx = new NodeContentContext(existingContentReader, null, nodeRef, - propertyQName, storageClassSet); + ContentContext ctx = new NodeContentContext(existingContentReader, null, nodeRef, propertyQName); ContentWriter writer = store.getWriter(ctx); // Register the new URL for rollback cleanup eagerContentStoreCleaner.registerNewContentUrl(writer.getContentUrl()); @@ -550,25 +564,6 @@ public class ContentServiceImpl implements ContentService, ApplicationContextAwa return tempStore.getWriter(ContentContext.NULL_CONTEXT); } - @Override - public DirectAccessUrl getDirectAccessUrl(NodeRef nodeRef, Date expiresAt) - { - ContentData contentData = getContentData(nodeRef, ContentModel.PROP_CONTENT); - - // check that the URL is available - if (contentData == null || contentData.getContentUrl() == null) - { - throw new IllegalArgumentException("The supplied nodeRef " + nodeRef + " has no content."); - } - - if (store.isDirectAccessSupported()) - { - return store.getDirectAccessUrl(contentData.getContentUrl(), expiresAt); - } - - return null; - } - /** * Ensures that, upon closure of the output stream, the node is updated with * the latest URL of the content to which it refers. @@ -627,6 +622,106 @@ public class ContentServiceImpl implements ContentService, ApplicationContextAwa } } + /** + * {@inheritDoc} + */ + @Override + public boolean isContentDirectUrlEnabled() + { + return systemWideDirectUrlConfig.isEnabled() && store.isContentDirectUrlEnabled(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isContentDirectUrlEnabled(NodeRef nodeRef) + { + boolean contentDirectUrlEnabled = false; + + // TODO: update this + if (systemWideDirectUrlConfig.isEnabled()) + { + ContentData contentData = getContentData(nodeRef, ContentModel.PROP_CONTENT); + + // check that the URL is available + if (contentData == null || contentData.getContentUrl() == null) + { + throw new IllegalArgumentException("The supplied nodeRef " + nodeRef + " has no content."); + } + + contentDirectUrlEnabled = (store.isContentDirectUrlEnabled(getContentUrl(nodeRef))); + } + + return contentDirectUrlEnabled; + } + + /** + * {@inheritDoc} + */ + public DirectAccessUrl requestContentDirectUrl(NodeRef nodeRef, boolean attachment, Long validFor) + { + if (!systemWideDirectUrlConfig.isEnabled()) + { + throw new DirectAccessUrlDisabledException("Direct access url isn't available."); + } + + String contentUrl = getContentUrl(nodeRef); + String fileName = getFileName(nodeRef); + validFor = adjustValidFor(validFor); + + DirectAccessUrl directAccessUrl = null; + if (store.isContentDirectUrlEnabled()) + { + try + { + directAccessUrl = store.requestContentDirectUrl(contentUrl, attachment, fileName, validFor); + } + catch (UnsupportedOperationException ex) + { + // expected exception + } + } + return directAccessUrl; + } + + protected String getContentUrl(NodeRef nodeRef) + { + ContentData contentData = getContentData(nodeRef, ContentModel.PROP_CONTENT); + + // check that the URL is available + if (contentData == null || contentData.getContentUrl() == null) + { + throw new IllegalArgumentException("The supplied nodeRef " + nodeRef + " has no content."); + } + + return contentData.getContentUrl(); + } + + protected String getFileName(NodeRef nodeRef) + { + String fileName = null; + + try + { + fileName = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_NAME); + } + catch (InvalidNodeRefException ex) + { + } + + return fileName; + } + + private Long adjustValidFor(Long validFor) + { + if (validFor == null || validFor > systemWideDirectUrlConfig.getDefaultExpiryTimeInSec()) + { + validFor = systemWideDirectUrlConfig.getDefaultExpiryTimeInSec(); + } + return validFor; + } + @Override public boolean isStorageClassesSupported(StorageClassSet storageClassSet) { @@ -691,4 +786,4 @@ public class ContentServiceImpl implements ContentService, ApplicationContextAwa return store.findStorageClassesTransitions(contentData.getContentUrl()); } -} \ No newline at end of file +} diff --git a/repository/src/main/java/org/alfresco/repo/content/caching/CachingContentStore.java b/repository/src/main/java/org/alfresco/repo/content/caching/CachingContentStore.java index 7031a1cd8e..1fd9c9ba7d 100644 --- a/repository/src/main/java/org/alfresco/repo/content/caching/CachingContentStore.java +++ b/repository/src/main/java/org/alfresco/repo/content/caching/CachingContentStore.java @@ -2,7 +2,7 @@ * #%L * Alfresco Repository * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited + * 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 @@ -44,6 +44,7 @@ import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentStreamListener; import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.DirectAccessUrl; +import org.alfresco.service.cmr.repository.NodeRef; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.BeanNameAware; @@ -106,7 +107,7 @@ public class CachingContentStore implements ContentStore, ApplicationEventPublis { eventPublisher.publishEvent(new CachingContentStoreCreatedEvent(this)); } - + @Override public boolean isContentUrlSupported(String contentUrl) { @@ -140,7 +141,7 @@ public class CachingContentStore implements ContentStore, ApplicationEventPublis /** * {@inheritDoc} *

- * For {@link #SPOOF_PROTOCOL spoofed} URLs, the URL always exists. + * For {@link FileContentStore#SPOOF_PROTOCOL spoofed} URLs, the URL always exists. */ @Override public boolean exists(String contentUrl) @@ -481,14 +482,36 @@ public class CachingContentStore implements ContentStore, ApplicationEventPublis return this.beanName; } - public boolean isDirectAccessSupported() + /** + * {@inheritDoc} + */ + public boolean isContentDirectUrlEnabled() { - return backingStore.isDirectAccessSupported(); + return backingStore.isContentDirectUrlEnabled(); } - public DirectAccessUrl getDirectAccessUrl(String contentUrl, Date expiresAt) + /** + * {@inheritDoc} + */ + public boolean isContentDirectUrlEnabled(String contentUrl) { - return backingStore.getDirectAccessUrl(contentUrl, expiresAt); + return backingStore.isContentDirectUrlEnabled(contentUrl); + } + + /** + * {@inheritDoc} + */ + public DirectAccessUrl requestContentDirectUrl(String contentUrl, boolean attachment, String fileName) + { + return backingStore.requestContentDirectUrl(contentUrl, attachment, fileName); + } + + /** + * {@inheritDoc} + */ + public DirectAccessUrl requestContentDirectUrl(String contentUrl, boolean attachment, String fileName, Long validFor) + { + return backingStore.requestContentDirectUrl(contentUrl, attachment, fileName, validFor); } @Override @@ -502,13 +525,13 @@ public class CachingContentStore implements ContentStore, ApplicationEventPublis { return backingStore.getSupportedStorageClasses(); } - + @Override public void updateStorageClasses(String contentUrl, StorageClassSet storageClassSet, Map parameters) { backingStore.updateStorageClasses(contentUrl, storageClassSet, parameters); } - + @Override public StorageClassSet findStorageClasses(String contentUrl) { diff --git a/repository/src/main/java/org/alfresco/repo/content/directurl/AbstractDirectUrlConfig.java b/repository/src/main/java/org/alfresco/repo/content/directurl/AbstractDirectUrlConfig.java new file mode 100644 index 0000000000..db6ebef9e6 --- /dev/null +++ b/repository/src/main/java/org/alfresco/repo/content/directurl/AbstractDirectUrlConfig.java @@ -0,0 +1,81 @@ +/* + * #%L + * Alfresco Repository + * %% + * 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 . + * #L% + */ +package org.alfresco.repo.content.directurl; + +/** + * Direct Access Url configuration settings. + * + * @author Sara Aspery + */ +public abstract class AbstractDirectUrlConfig implements DirectUrlConfig +{ + /** System-wide direct access URL configuration */ + private SystemWideDirectUrlConfig systemWideDirectUrlConfig; + + /** Direct access URL configuration settings */ + private Boolean enabled; + private Long defaultExpiryTimeInSec; + + public void setSystemWideDirectUrlConfig(SystemWideDirectUrlConfig systemWideDirectUrlConfig) + { + this.systemWideDirectUrlConfig = systemWideDirectUrlConfig; + } + + public void setEnabled(Boolean enabled) + { + this.enabled = enabled; + } + + public void setDefaultExpiryTimeInSec(Long defaultExpiryTimeInSec) + { + this.defaultExpiryTimeInSec = defaultExpiryTimeInSec; + } + + protected Boolean isSysWideEnabled() + { + return systemWideDirectUrlConfig.isEnabled(); + } + + public Long getSysWideDefaultExpiryTimeInSec() + { + return systemWideDirectUrlConfig.getDefaultExpiryTimeInSec(); + } + + public Long getSysWideMaxExpiryTimeInSec() + { + return systemWideDirectUrlConfig.getMaxExpiryTimeInSec(); + } + + public Boolean isEnabled() + { + return enabled; + } + + public Long getDefaultExpiryTimeInSec() + { + return defaultExpiryTimeInSec; + } +} diff --git a/repository/src/main/java/org/alfresco/repo/content/directurl/ContentStoreDirectUrlConfig.java b/repository/src/main/java/org/alfresco/repo/content/directurl/ContentStoreDirectUrlConfig.java new file mode 100644 index 0000000000..f6a390dbd6 --- /dev/null +++ b/repository/src/main/java/org/alfresco/repo/content/directurl/ContentStoreDirectUrlConfig.java @@ -0,0 +1,135 @@ +/* + * #%L + * Alfresco Repository + * %% + * 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 . + * #L% + */ +package org.alfresco.repo.content.directurl; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Content store direct access URL configuration settings. + * + * @author Sara Aspery + */ +public class ContentStoreDirectUrlConfig extends AbstractDirectUrlConfig +{ + private static final Log logger = LogFactory.getLog(ContentStoreDirectUrlConfig.class); + + private Long maxExpiryTimeInSec; + + public void setMaxExpiryTimeInSec(Long maxExpiryTimeInSec) + { + this.maxExpiryTimeInSec = maxExpiryTimeInSec; + } + + public Long getMaxExpiryTimeInSec() + { + return maxExpiryTimeInSec; + } + + /** + * Configuration initialise + */ + public void init() + { + validate(); + } + + /** + * {@inheritDoc} + */ + @Override + public void validate() + { + // Disable direct access URLs for the content store if any error found in the content store direct access URL config + try + { + validateDirectAccessUrlConfig(); + } + catch (InvalidDirectAccessUrlConfigException ex) + { + logger.error("Disabling content store direct access URLs due to configuration error: " + ex.getMessage()); + setEnabled(false); + } + logger.info("Content store direct access URLs are " + (isEnabled() ? "enabled" : "disabled")); + } + + /* Helper method to validate the content direct access url configuration settings */ + private void validateDirectAccessUrlConfig() throws InvalidDirectAccessUrlConfigException + { + if (isEnabled()) + { + if (getMaxExpiryTimeInSec() == null) + { + logger.warn(String.format("Maximum expiry time property is missing: setting to system-wide maximum [%s].", getSysWideMaxExpiryTimeInSec())); + setMaxExpiryTimeInSec(getSysWideMaxExpiryTimeInSec()); + } + else if (getMaxExpiryTimeInSec() > getSysWideMaxExpiryTimeInSec()) + { + String errorMsg = String.format("Content store direct access URL maximum expiry time [%s] exceeds system-wide maximum expiry time [%s].", + getMaxExpiryTimeInSec(), getSysWideMaxExpiryTimeInSec()); + throw new InvalidDirectAccessUrlConfigException(errorMsg); + } + + if (getDefaultExpiryTimeInSec() == null) + { + logger.warn(String.format("Default expiry time property is missing: setting to system-wide default [%s].", getSysWideDefaultExpiryTimeInSec())); + setDefaultExpiryTimeInSec(getSysWideDefaultExpiryTimeInSec()); + } + else if (getDefaultExpiryTimeInSec() > getMaxExpiryTimeInSec()) + { + logger.warn(String.format("Default expiry time property [%s] exceeds maximum expiry time for content store [%s]: setting to system-wide default [%s].", + getDefaultExpiryTimeInSec(), getMaxExpiryTimeInSec(), getSysWideDefaultExpiryTimeInSec())); + setDefaultExpiryTimeInSec(getSysWideDefaultExpiryTimeInSec()); + } + else if (getDefaultExpiryTimeInSec() > getSysWideDefaultExpiryTimeInSec()) + { + logger.warn(String.format("Default expiry time property [%s] exceeds system-wide default expiry time [%s]: setting to system-wide default.", + getDefaultExpiryTimeInSec(), getSysWideDefaultExpiryTimeInSec())); + setDefaultExpiryTimeInSec(getSysWideDefaultExpiryTimeInSec()); + } + + if (getDefaultExpiryTimeInSec() < 1) + { + String errorMsg = String.format("Content store direct access URL default expiry time [%s] is invalid.", getDefaultExpiryTimeInSec()); + throw new InvalidDirectAccessUrlConfigException(errorMsg); + } + + if (getDefaultExpiryTimeInSec() > getSysWideMaxExpiryTimeInSec()) + { + String errorMsg = String.format("Content store direct access URL default expiry time [%s] exceeds system-wide maximum expiry time [%s].", + getDefaultExpiryTimeInSec(), getSysWideMaxExpiryTimeInSec()); + throw new InvalidDirectAccessUrlConfigException(errorMsg); + } + + if (getDefaultExpiryTimeInSec() > getMaxExpiryTimeInSec()) + { + String errorMsg = String.format("Content store direct access URL default expiry time [%s] exceeds content store maximum expiry time [%s].", + getDefaultExpiryTimeInSec(), getMaxExpiryTimeInSec()); + throw new InvalidDirectAccessUrlConfigException(errorMsg); + } + } + } +} diff --git a/repository/src/main/java/org/alfresco/repo/content/directurl/DirectAccessUrlDisabledException.java b/repository/src/main/java/org/alfresco/repo/content/directurl/DirectAccessUrlDisabledException.java new file mode 100644 index 0000000000..d0702b398d --- /dev/null +++ b/repository/src/main/java/org/alfresco/repo/content/directurl/DirectAccessUrlDisabledException.java @@ -0,0 +1,44 @@ +/* + * #%L + * Alfresco Repository + * %% + * 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 . + * #L% + */ +package org.alfresco.repo.content.directurl; + +import org.alfresco.error.AlfrescoRuntimeException; + +/** + * Runtime exception thrown when direct access URLs are disabled. + * + * @author Sara Aspery + */ +public class DirectAccessUrlDisabledException extends AlfrescoRuntimeException +{ + + private static final long serialVersionUID = -6506082117146782993L; + + public DirectAccessUrlDisabledException(String msg) + { + super(msg); + } +} diff --git a/repository/src/main/java/org/alfresco/repo/content/directurl/DirectUrlConfig.java b/repository/src/main/java/org/alfresco/repo/content/directurl/DirectUrlConfig.java new file mode 100644 index 0000000000..68ed3a84da --- /dev/null +++ b/repository/src/main/java/org/alfresco/repo/content/directurl/DirectUrlConfig.java @@ -0,0 +1,41 @@ +/* + * #%L + * Alfresco Repository + * %% + * 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 . + * #L% + */ +package org.alfresco.repo.content.directurl; + +import org.alfresco.api.AlfrescoPublicApi; + +/** + * Direct Access Url configuration settings interface. + * + * @author Sara Aspery + */ +@AlfrescoPublicApi +public interface DirectUrlConfig +{ + Boolean isEnabled(); + Long getDefaultExpiryTimeInSec(); + void validate(); +} diff --git a/repository/src/main/java/org/alfresco/repo/content/directurl/InvalidDirectAccessUrlConfigException.java b/repository/src/main/java/org/alfresco/repo/content/directurl/InvalidDirectAccessUrlConfigException.java new file mode 100644 index 0000000000..5c6445ed5f --- /dev/null +++ b/repository/src/main/java/org/alfresco/repo/content/directurl/InvalidDirectAccessUrlConfigException.java @@ -0,0 +1,44 @@ +/* + * #%L + * Alfresco Repository + * %% + * 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 . + * #L% + */ +package org.alfresco.repo.content.directurl; + +import org.alfresco.error.AlfrescoRuntimeException; + +/** + * Runtime exception thrown when the direct access URL configuration settings are invalid. + * + * @author Sara Aspery + */ +public class InvalidDirectAccessUrlConfigException extends AlfrescoRuntimeException +{ + private static final long serialVersionUID = -6318313836484979887L; + + public InvalidDirectAccessUrlConfigException(String msg) + { + super(msg); + } +} + diff --git a/repository/src/main/java/org/alfresco/repo/content/directurl/SystemWideDirectUrlConfig.java b/repository/src/main/java/org/alfresco/repo/content/directurl/SystemWideDirectUrlConfig.java new file mode 100644 index 0000000000..aa1a632742 --- /dev/null +++ b/repository/src/main/java/org/alfresco/repo/content/directurl/SystemWideDirectUrlConfig.java @@ -0,0 +1,125 @@ +/* + * #%L + * Alfresco Repository + * %% + * 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 . + * #L% + */ +package org.alfresco.repo.content.directurl; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * System-wide direct access URL configuration settings. + * + * @author Sara Aspery + */ +public class SystemWideDirectUrlConfig implements DirectUrlConfig +{ + private static final Log logger = LogFactory.getLog(SystemWideDirectUrlConfig.class); + + /** Direct access url configuration settings */ + private Boolean enabled; + private Long defaultExpiryTimeInSec; + private Long maxExpiryTimeInSec; + + public void setEnabled(Boolean enabled) + { + this.enabled = enabled; + } + + public void setDefaultExpiryTimeInSec(Long defaultExpiryTimeInSec) + { + this.defaultExpiryTimeInSec = defaultExpiryTimeInSec; + } + + public void setMaxExpiryTimeInSec(Long maxExpiryTimeInSec) + { + this.maxExpiryTimeInSec = maxExpiryTimeInSec; + } + + public Boolean isEnabled() + { + return enabled; + } + + public Long getDefaultExpiryTimeInSec() + { + return defaultExpiryTimeInSec; + } + + public Long getMaxExpiryTimeInSec() + { + return maxExpiryTimeInSec; + } + + /** + * Configuration initialise + */ + public void init() + { + validate(); + } + + /** + * {@inheritDoc} + */ + @Override + public void validate() + { + // Disable direct access URLs system-wide if any error found in the system-wide direct access URL config + try + { + validateSystemDirectAccessUrlConfig(); + } + catch (InvalidDirectAccessUrlConfigException ex) + { + logger.error("Disabling system-wide direct access URLs due to configuration error: " + ex.getMessage()); + setEnabled(false); + } + logger.info("System-wide direct access URLs are " + (isEnabled() ? "enabled" : "disabled")); + } + + /* Helper method to validate the system-wide direct access url configuration settings */ + private void validateSystemDirectAccessUrlConfig() throws InvalidDirectAccessUrlConfigException + { + if (isEnabled()) + { + if (getDefaultExpiryTimeInSec() == null || getDefaultExpiryTimeInSec() < 1) + { + throw new InvalidDirectAccessUrlConfigException("System-wide direct access URL default expiry time is missing or invalid."); + } + + if (getMaxExpiryTimeInSec() == null || getMaxExpiryTimeInSec() < 1) + { + throw new InvalidDirectAccessUrlConfigException("System-wide direct access URL maximum expiry time is missing or invalid."); + } + + if (getDefaultExpiryTimeInSec() > getMaxExpiryTimeInSec()) + { + String errorMsg = String.format("System-wide direct access URL default expiry time [%s] exceeds maximum expiry time [%s].", + getDefaultExpiryTimeInSec(), getMaxExpiryTimeInSec()); + throw new InvalidDirectAccessUrlConfigException(errorMsg); + } + } + } +} diff --git a/repository/src/main/java/org/alfresco/repo/content/replication/AggregatingContentStore.java b/repository/src/main/java/org/alfresco/repo/content/replication/AggregatingContentStore.java index 0ca5a94451..01d336de39 100644 --- a/repository/src/main/java/org/alfresco/repo/content/replication/AggregatingContentStore.java +++ b/repository/src/main/java/org/alfresco/repo/content/replication/AggregatingContentStore.java @@ -2,7 +2,7 @@ * #%L * Alfresco Repository * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited + * 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 @@ -44,6 +44,7 @@ import org.alfresco.service.cmr.repository.ContentIOException; import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.DirectAccessUrl; +import org.alfresco.service.cmr.repository.NodeRef; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -67,10 +68,9 @@ import org.apache.commons.logging.LogFactory; * @see CachingContentStore */ public class AggregatingContentStore extends AbstractContentStore -{ +{ private static final Log logger = LogFactory.getLog(AggregatingContentStore.class); - private static final String REPLICATING_CONTENT_STORE_NOT_INITIALISED = "ReplicatingContentStore not initialised"; - + private ContentStore primaryStore; private List secondaryStores; @@ -138,7 +138,7 @@ public class AggregatingContentStore extends AbstractContentStore { if (primaryStore == null) { - throw new AlfrescoRuntimeException(REPLICATING_CONTENT_STORE_NOT_INITIALISED); + throw new AlfrescoRuntimeException("ReplicatingContentStore not initialised"); } // get a read lock so that we are sure that no replication is underway @@ -173,12 +173,11 @@ public class AggregatingContentStore extends AbstractContentStore } } - @Override public boolean exists(String contentUrl) { if (primaryStore == null) { - throw new AlfrescoRuntimeException(REPLICATING_CONTENT_STORE_NOT_INITIALISED); + throw new AlfrescoRuntimeException("ReplicatingContentStore not initialised"); } // get a read lock so that we are sure that no replication is underway @@ -244,7 +243,6 @@ public class AggregatingContentStore extends AbstractContentStore } } - @Override public ContentWriter getWriter(ContentContext ctx) { // get the writer @@ -259,7 +257,6 @@ public class AggregatingContentStore extends AbstractContentStore * * @return Returns the value returned by the delete on the primary store. */ - @Override public boolean delete(String contentUrl) throws ContentIOException { // delete on the primary store @@ -273,39 +270,62 @@ public class AggregatingContentStore extends AbstractContentStore } /** - * @return Returns true if at least one store supports direct access + * @return Returns {@code true} if at least one store supports direct access URLs */ - @Override - public boolean isDirectAccessSupported() + public boolean isContentDirectUrlEnabled() { // Check the primary store - boolean isDirectAccessSupported = primaryStore.isDirectAccessSupported(); + boolean isContentDirectUrlEnabled = primaryStore.isContentDirectUrlEnabled(); - if (!isDirectAccessSupported) + if (!isContentDirectUrlEnabled) { // Direct access is not supported by the primary store so we have to check the // other stores for (ContentStore store : secondaryStores) { + isContentDirectUrlEnabled = store.isContentDirectUrlEnabled(); - isDirectAccessSupported = store.isDirectAccessSupported(); - - if (isDirectAccessSupported) + if (isContentDirectUrlEnabled) { break; } } } - return isDirectAccessSupported; + return isContentDirectUrlEnabled; } - @Override - public DirectAccessUrl getDirectAccessUrl(String contentUrl, Date expiresAt) + /** + * @return Returns {@code true} if at least one store supports direct access URL for node + */ + public boolean isContentDirectUrlEnabled(String contentUrl) + { + // Check the primary store + boolean isContentDirectUrlEnabled = primaryStore.isContentDirectUrlEnabled(contentUrl); + + if (!isContentDirectUrlEnabled) + { + // Direct access is not supported by the primary store so we have to check the + // other stores + for (ContentStore store : secondaryStores) + { + isContentDirectUrlEnabled = store.isContentDirectUrlEnabled(contentUrl); + + if (isContentDirectUrlEnabled) + { + break; + } + } + } + + return isContentDirectUrlEnabled; + } + + public DirectAccessUrl requestContentDirectUrl(String contentUrl, boolean attachment, String fileName, Long validFor) { if (primaryStore == null) { - throw new AlfrescoRuntimeException(REPLICATING_CONTENT_STORE_NOT_INITIALISED); + throw new AlfrescoRuntimeException("ReplicatingContentStore not initialised"); } // get a read lock so that we are sure that no replication is underway @@ -321,13 +341,13 @@ public class AggregatingContentStore extends AbstractContentStore // Check the primary store try { - directAccessUrl = primaryStore.getDirectAccessUrl(contentUrl, expiresAt); + directAccessUrl = primaryStore.requestContentDirectUrl(contentUrl, attachment, fileName, validFor); } catch (UnsupportedOperationException e) { // The store does not support direct access URL directAccessUrlSupported = false; - } + } catch (UnsupportedContentUrlException e) { // The store can't handle the content URL @@ -344,7 +364,7 @@ public class AggregatingContentStore extends AbstractContentStore { try { - directAccessUrl = store.getDirectAccessUrl(contentUrl, expiresAt); + directAccessUrl = store.requestContentDirectUrl(contentUrl, attachment, fileName, validFor); } catch (UnsupportedOperationException e) { @@ -389,7 +409,7 @@ public class AggregatingContentStore extends AbstractContentStore public boolean isStorageClassesSupported(StorageClassSet storageClassesSet) { // We only need to provide info about the primary store, - // because the aggregating CS only allows to be written in the primary + // because the aggregating CS only allows to be written in the primary return primaryStore.isStorageClassesSupported(storageClassesSet); } @@ -463,7 +483,7 @@ public class AggregatingContentStore extends AbstractContentStore // The content URL was not supported throw new UnsupportedContentUrlException(this, contentUrl); } - + return storageClassesSet; } finally @@ -479,7 +499,7 @@ public class AggregatingContentStore extends AbstractContentStore // because the aggregating CS only allows to be written in the primary return primaryStore.getStorageClassesTransitions(); } - + @Override public Map> findStorageClassesTransitions(String contentUrl) { diff --git a/repository/src/main/java/org/alfresco/repo/content/transform/LocalCombinedConfig.java b/repository/src/main/java/org/alfresco/repo/content/transform/LocalCombinedConfig.java deleted file mode 100644 index f8a331acef..0000000000 --- a/repository/src/main/java/org/alfresco/repo/content/transform/LocalCombinedConfig.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * #%L - * Alfresco Repository - * %% - * Copyright (C) 2019 - 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 . - * #L% - */ -package org.alfresco.repo.content.transform; - -import org.alfresco.transform.client.model.config.Transformer; -import org.alfresco.transform.client.registry.CombinedConfig; -import org.alfresco.transform.client.registry.TransformServiceRegistryImpl; -import org.apache.commons.logging.Log; - -import java.util.List; - -import static org.alfresco.repo.content.transform.LocalTransformServiceRegistry.LOCAL_TRANSFORMER; -import static org.alfresco.repo.content.transform.LocalTransformServiceRegistry.URL; -import static org.alfresco.util.EqualsHelper.nullSafeEquals; - -/** - * Extends the standard CombinedConfig to add in removal of overridden or invalid transforms. - * - * @author adavis - */ -public class LocalCombinedConfig extends CombinedConfig -{ - public LocalCombinedConfig(Log log) - { - super(log); - } - - /** - * Discards a transformer that is invalid (e.g. T-Engines with the same name, baseUrl has not been specified on a - * T-Engine transform) or overridden an earlier transform with the same name). If the overridden transform is from - * a T-Engine and the overriding transform is not a pipeline or a failover, we also copy the {@code baseUrl} from - * the overridden transform so that the original T-Engine will still be called. - * - * @param i the current transform's index into combinedTransformers. - * @param combinedTransformers the full list of transformers in the order they were read. - * @param registry that wil hold the transforms. - * @param transformAndItsOrigin the current combinedTransformers element. - * @param transformer the current transformer. - * @param name the current transformer's name. - * @param readFrom where the current transformer was read from. - * @param isPipeline if the current transform is a pipeline. - * @param isFailover if the current transform is a failover. - * - * @returns the index of a transform to be removed. {@code -1} is returned if there should not be a remove. - * @throws IllegalArgumentException if the current transform has a problem and should be removed. - */ - @Override - protected int removeInvalidTransformer(int i, List combinedTransformers, - TransformServiceRegistryImpl registry, - TransformAndItsOrigin transformAndItsOrigin, Transformer transformer, - String name, String readFrom, boolean isPipeline, boolean isFailover) - { - int indexToRemove = -1; - - if (name == null || "".equals(name.trim())) - { - throw new IllegalArgumentException("Local transformer names may not be null. Read from " + readFrom); - } - - // Get the baseUrl - test code might change it - String baseUrl = transformAndItsOrigin.getBaseUrl(); - String testBaseUrl = ((LocalTransformServiceRegistry)registry).getBaseUrlIfTesting(name, baseUrl); - if (!nullSafeEquals(baseUrl, testBaseUrl)) - { - baseUrl = testBaseUrl; - transformAndItsOrigin = new TransformAndItsOrigin(transformer, baseUrl, readFrom); - combinedTransformers.set(i, transformAndItsOrigin); - } - - boolean isOneStepTransform = !isPipeline && !isFailover && !name.equals(LocalPassThroughTransform.NAME); - - // Check to see if the name has been used before. - int j = lastIndexOf(name, combinedTransformers, i); - if (j >= 0) - { - if (baseUrl != null) // If a T-Engine, else it is an override - { - throw new IllegalArgumentException("Local T-Engine transformer " + transformerName(name) + - " must be a unique name. Read from " + readFrom); - } - - if (isOneStepTransform) - { - // We need to set the baseUrl of the original transform in the one overriding, - // so we can talk to its T-Engine - TransformAndItsOrigin overriddenTransform = combinedTransformers.get(j); - String overriddenBaseUrl = overriddenTransform.getBaseUrl(); - Transformer overriddenTransformTransform = transformAndItsOrigin.getTransformer(); - TransformAndItsOrigin overridingTransform = new TransformAndItsOrigin( - overriddenTransformTransform, overriddenBaseUrl, readFrom); - combinedTransformers.set(i, overridingTransform); - } - indexToRemove = j; - } - else if (isOneStepTransform && baseUrl == null) - { - throw new IllegalArgumentException("Local T-Engine transformer " + transformerName(name) + - " must have its baseUrl set in " + LOCAL_TRANSFORMER + name + URL + " Read from " + - readFrom); - } - return indexToRemove; - } - - private static int lastIndexOf(String name, List combinedTransformers, int toIndex) - { - // Lists are short (< 100) entries and this is not a frequent or time critical step, so walking the list - // should be okay. - for (int j = toIndex-1; j >=0; j--) - { - TransformAndItsOrigin transformAndItsOrigin = combinedTransformers.get(j); - Transformer transformer = transformAndItsOrigin.getTransformer(); - String transformerName = transformer.getTransformerName(); - if (name.equals(transformerName)) - { - return j; - } - } - return -1; - } -} diff --git a/repository/src/main/java/org/alfresco/repo/content/transform/LocalTransformServiceRegistry.java b/repository/src/main/java/org/alfresco/repo/content/transform/LocalTransformServiceRegistry.java index bc4daa6ed7..ff1ab924ae 100644 --- a/repository/src/main/java/org/alfresco/repo/content/transform/LocalTransformServiceRegistry.java +++ b/repository/src/main/java/org/alfresco/repo/content/transform/LocalTransformServiceRegistry.java @@ -133,7 +133,7 @@ public class LocalTransformServiceRegistry extends TransformServiceRegistryImpl @Override public boolean readConfig() throws IOException { - CombinedConfig combinedConfig = new LocalCombinedConfig(getLog()); + CombinedConfig combinedConfig = new CombinedConfig(getLog()); List urls = getTEngineUrls(); boolean successReadingConfig = combinedConfig.addRemoteConfig(urls, "T-Engine"); successReadingConfig &= combinedConfig.addLocalConfig("alfresco/transforms"); @@ -315,12 +315,6 @@ public class LocalTransformServiceRegistry extends TransformServiceRegistryImpl options; } - // When testing, we need to be able to set the baseUrl when reading from a file. - public String getBaseUrlIfTesting(String name, String baseUrl) - { - return baseUrl; - } - @Override protected Log getLog() { diff --git a/repository/src/main/java/org/alfresco/repo/event2/ChildAssociationEventConsolidator.java b/repository/src/main/java/org/alfresco/repo/event2/ChildAssociationEventConsolidator.java index 8837f3648e..d09833b74e 100644 --- a/repository/src/main/java/org/alfresco/repo/event2/ChildAssociationEventConsolidator.java +++ b/repository/src/main/java/org/alfresco/repo/event2/ChildAssociationEventConsolidator.java @@ -27,7 +27,6 @@ package org.alfresco.repo.event2; import java.util.ArrayDeque; import java.util.Deque; - import org.alfresco.repo.event.v1.model.ChildAssociationResource; import org.alfresco.repo.event.v1.model.DataAttributes; import org.alfresco.repo.event.v1.model.EventData; @@ -117,8 +116,9 @@ public class ChildAssociationEventConsolidator implements ChildAssociationEventS { String parentId = childAssociationRef.getParentRef().getId(); String childId = childAssociationRef.getChildRef().getId(); + String assocQName = helper.getQNamePrefixString(childAssociationRef.getQName()); String assocType = helper.getQNamePrefixString(childAssociationRef.getTypeQName()); - return new ChildAssociationResource(parentId, childId, assocType); + return new ChildAssociationResource(parentId, childId, assocType, assocQName); } /** diff --git a/repository/src/main/java/org/alfresco/repo/event2/NodeResourceHelper.java b/repository/src/main/java/org/alfresco/repo/event2/NodeResourceHelper.java index 7ce7e40d21..9212f2a1b8 100644 --- a/repository/src/main/java/org/alfresco/repo/event2/NodeResourceHelper.java +++ b/repository/src/main/java/org/alfresco/repo/event2/NodeResourceHelper.java @@ -46,6 +46,7 @@ import org.alfresco.repo.event2.filter.NodePropertyFilter; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.permissions.AccessDeniedException; import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.MLText; import org.alfresco.service.cmr.repository.NodeRef; @@ -148,6 +149,7 @@ public class NodeResourceHelper implements InitializingBean .setModifiedByUser(getUserInfo((String) properties.get(ContentModel.PROP_MODIFIER), mapUserCache)) .setModifiedAt(getZonedDateTime((Date)properties.get(ContentModel.PROP_MODIFIED))) .setContent(getContentInfo(properties)) + .setPrimaryAssocQName(getPrimaryAssocQName(nodeRef)) .setPrimaryHierarchy(PathUtil.getNodeIdsInReverse(path, false)) .setProperties(mapToNodeProperties(properties)) .setAspectNames(getMappedAspects(nodeRef)); @@ -158,6 +160,23 @@ public class NodeResourceHelper implements InitializingBean return dictionaryService.isSubClass(className, ofClassQName); } + private String getPrimaryAssocQName(NodeRef nodeRef) + { + String result = null; + try + { + ChildAssociationRef primaryParent = nodeService.getPrimaryParent(nodeRef); + if(primaryParent != null && primaryParent.getQName() != null) + { + result = primaryParent.getQName().getPrefixedQName(namespaceService).getPrefixString(); + } + } catch (NamespaceException namespaceException) + { + LOGGER.error("Cannot return a valid primary association QName: " + namespaceException.getMessage()); + } + return result; + } + private UserInfo getUserInfo(String userName, Map mapUserCache) { UserInfo userInfo = mapUserCache.get(userName); diff --git a/repository/src/main/java/org/alfresco/repo/management/SafeApplicationEventMulticaster.java b/repository/src/main/java/org/alfresco/repo/management/SafeApplicationEventMulticaster.java index baae82464e..cb0fb3588d 100644 --- a/repository/src/main/java/org/alfresco/repo/management/SafeApplicationEventMulticaster.java +++ b/repository/src/main/java/org/alfresco/repo/management/SafeApplicationEventMulticaster.java @@ -33,6 +33,7 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; +import java.util.function.Predicate; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -152,6 +153,16 @@ public class SafeApplicationEventMulticaster implements ApplicationEventMulticas } } + @Override + public void removeApplicationListeners(Predicate> predicate) + { + } + + @Override + public void removeApplicationListenerBeans(Predicate predicate) + { + } + public void removeAllListeners() { synchronized (this.defaultRetriever) diff --git a/repository/src/main/java/org/alfresco/repo/rendition2/LocalSynchronousTransformClient.java b/repository/src/main/java/org/alfresco/repo/rendition2/LocalSynchronousTransformClient.java index 281b76adf3..60388f9be4 100644 --- a/repository/src/main/java/org/alfresco/repo/rendition2/LocalSynchronousTransformClient.java +++ b/repository/src/main/java/org/alfresco/repo/rendition2/LocalSynchronousTransformClient.java @@ -2,7 +2,7 @@ * #%L * Alfresco Repository * %% - * Copyright (C) 2019 Alfresco Software Limited + * Copyright (C) 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 @@ -120,21 +120,21 @@ public class LocalSynchronousTransformClient implements SynchronousTransformClie if (logger.isDebugEnabled()) { - logger.debug(TRANSFORM + " requested " + renditionName); + logger.debug(TRANSFORM + "requested " + renditionName); } transform.transform(reader, writer, actualOptions, renditionName, sourceNodeRef); if (logger.isDebugEnabled()) { - logger.debug(TRANSFORM + " created " + renditionName); + logger.debug(TRANSFORM + "created " + renditionName); } } catch (Exception e) { if (logger.isDebugEnabled()) { - logger.debug(TRANSFORM + " failed " + renditionName, e); + logger.debug(TRANSFORM + "failed " + renditionName, e); } throw e; } diff --git a/repository/src/main/java/org/alfresco/repo/search/impl/querymodel/impl/db/DBQuery.java b/repository/src/main/java/org/alfresco/repo/search/impl/querymodel/impl/db/DBQuery.java index fd98d9bc77..abb7c40efe 100644 --- a/repository/src/main/java/org/alfresco/repo/search/impl/querymodel/impl/db/DBQuery.java +++ b/repository/src/main/java/org/alfresco/repo/search/impl/querymodel/impl/db/DBQuery.java @@ -1,28 +1,28 @@ -/* - * #%L - * Alfresco Repository - * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited - * %% - * This file is part of the Alfresco software. - * If the software was purchased under a paid Alfresco license, the terms of - * the paid license agreement will prevail. Otherwise, the software is - * provided under the following open source license terms: - * - * Alfresco is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Alfresco is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Alfresco. If not, see . - * #L% - */ +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ package org.alfresco.repo.search.impl.querymodel.impl.db; import java.util.ArrayList; @@ -74,6 +74,10 @@ public class DBQuery extends BaseQuery implements DBQueryBuilderComponent Set selectorGroup; + private int limit = 0; + + private int offset = 0; + /** * @param source Source * @param constraint Constraint @@ -133,6 +137,22 @@ public class DBQuery extends BaseQuery implements DBQueryBuilderComponent this.sinceTxId = sinceTxId; } + public int getLimit() { + return limit; + } + + public void setLimit(int limit) { + this.limit = limit; + } + + public int getOffset() { + return offset; + } + + public void setOffset(int offset) { + this.offset = offset; + } + public List getJoins() { HashMap singleJoins = new HashMap(); diff --git a/repository/src/main/java/org/alfresco/repo/search/impl/querymodel/impl/db/DBQueryEngine.java b/repository/src/main/java/org/alfresco/repo/search/impl/querymodel/impl/db/DBQueryEngine.java index 612f958a65..96ca208c73 100644 --- a/repository/src/main/java/org/alfresco/repo/search/impl/querymodel/impl/db/DBQueryEngine.java +++ b/repository/src/main/java/org/alfresco/repo/search/impl/querymodel/impl/db/DBQueryEngine.java @@ -76,8 +76,10 @@ import org.alfresco.service.namespace.QName; import org.alfresco.util.Pair; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.apache.ibatis.executor.result.DefaultResultContext; import org.apache.ibatis.session.ResultContext; import org.apache.ibatis.session.ResultHandler; +import org.apache.ibatis.session.RowBounds; import org.mybatis.spring.SqlSessionTemplate; /** @@ -89,7 +91,11 @@ public class DBQueryEngine implements QueryEngine protected static final Log logger = LogFactory.getLog(DBQueryEngine.class); protected static final String SELECT_BY_DYNAMIC_QUERY = "alfresco.metadata.query.select_byDynamicQuery"; - + + private static final int DEFAULT_MIN_PAGING_BATCH_SIZE = 2500; + + private static final int DEFAULT_MAX_PAGING_BATCH_SIZE = 10000; + protected SqlSessionTemplate template; protected QNameDAO qnameDAO; @@ -114,6 +120,12 @@ public class DBQueryEngine implements QueryEngine private boolean maxPermissionCheckEnabled; + private boolean usePagingQuery = false; + + private int minPagingBatchSize = DEFAULT_MIN_PAGING_BATCH_SIZE; + + private int maxPagingBatchSize = DEFAULT_MAX_PAGING_BATCH_SIZE; + protected EntityLookupCache nodesCache; private List> stores; @@ -149,7 +161,31 @@ public class DBQueryEngine implements QueryEngine { this.permissionService = permissionService; } - + + public boolean isUsePagingQuery() { + return usePagingQuery; + } + + public void setUsePagingQuery(boolean usePagingQuery) { + this.usePagingQuery = usePagingQuery; + } + + public int getMinPagingBatchSize() { + return minPagingBatchSize; + } + + public void setMinPagingBatchSize(int minPagingBatchSize) { + this.minPagingBatchSize = minPagingBatchSize; + } + + public int getMaxPagingBatchSize() { + return maxPagingBatchSize; + } + + public void setMaxPagingBatchSize(int maxPagingBatchSize) { + this.maxPagingBatchSize = maxPagingBatchSize; + } + public void setMetadataIndexCheck2(OptionalPatchApplicationCheckBootstrapBean metadataIndexCheck2) { this.metadataIndexCheck2 = metadataIndexCheck2; @@ -331,7 +367,7 @@ public class DBQueryEngine implements QueryEngine int requiredNodes = computeRequiredNodesCount(options); logger.debug("- query sent to the database"); - template.select(pickQueryTemplate(options, dbQuery), dbQuery, new ResultHandler() + performTmdqSelect(pickQueryTemplate(options, dbQuery), dbQuery, requiredNodes, new ResultHandler() { @Override public void handleResult(ResultContext context) @@ -399,6 +435,54 @@ public class DBQueryEngine implements QueryEngine return frs; } + private void performTmdqSelect(String statement, DBQuery dbQuery, int requiredNodes, ResultHandler handler) + { + if (usePagingQuery) + { + performTmdqSelectPaging(statement, dbQuery, requiredNodes, handler); + } + else + { + performTmdqSelectStreaming(statement, dbQuery, handler); + } + } + + private void performTmdqSelectStreaming(String statement, DBQuery dbQuery, ResultHandler handler) + { + template.select(statement, dbQuery, handler); + } + + private void performTmdqSelectPaging(String statement, DBQuery dbQuery, int requiredNodes, ResultHandler handler) + { + int batchStart = 0; + int batchSize = requiredNodes * 2; + batchSize = Math.min(Math.max(batchSize, minPagingBatchSize), maxPagingBatchSize); + DefaultResultContext resultCtx = new DefaultResultContext<>(); + while (!resultCtx.isStopped()) + { + dbQuery.setOffset(batchStart); + dbQuery.setLimit(batchSize); + List batch = template.selectList(statement, dbQuery); + for (Node node : batch) + { + resultCtx.nextResultObject(node); + handler.handleResult(resultCtx); + if (resultCtx.isStopped()) + { + break; + } + } + if (batch.size() < batchSize) + { + resultCtx.stop(); + } + else + { + batchStart += batchSize; + } + } + } + private DBResultSet createResultSet(QueryOptions options, List nodes, int numberFound) { DBResultSet dbResultSet = new DBResultSet(options.getAsSearchParmeters(), nodes, nodeDAO, nodeService, tenantService, Integer.MAX_VALUE); diff --git a/repository/src/main/java/org/alfresco/repo/security/authority/AuthorityServiceImpl.java b/repository/src/main/java/org/alfresco/repo/security/authority/AuthorityServiceImpl.java index 84bafac0a8..0d4ecf8e2a 100644 --- a/repository/src/main/java/org/alfresco/repo/security/authority/AuthorityServiceImpl.java +++ b/repository/src/main/java/org/alfresco/repo/security/authority/AuthorityServiceImpl.java @@ -1,61 +1,61 @@ -/* - * #%L - * Alfresco Repository - * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited - * %% - * This file is part of the Alfresco software. - * If the software was purchased under a paid Alfresco license, the terms of - * the paid license agreement will prevail. Otherwise, the software is - * provided under the following open source license terms: - * - * Alfresco is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Alfresco is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Alfresco. If not, see . - * #L% - */ +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ package org.alfresco.repo.security.authority; -import java.util.AbstractSet; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Set; -import java.util.TreeSet; - -import org.alfresco.model.ContentModel; -import org.alfresco.query.PagingRequest; -import org.alfresco.query.PagingResults; -import org.alfresco.repo.policy.ClassPolicyDelegate; -import org.alfresco.repo.policy.PolicyComponent; -import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.security.authority.AuthorityServicePolicies.OnAuthorityAddedToGroup; -import org.alfresco.repo.security.authority.AuthorityServicePolicies.OnAuthorityRemovedFromGroup; -import org.alfresco.repo.security.authority.AuthorityServicePolicies.OnGroupDeleted; -import org.alfresco.repo.security.permissions.PermissionServiceSPI; -import org.alfresco.repo.security.person.UserNameMatcher; -import org.alfresco.repo.tenant.TenantService; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.security.AuthenticationService; -import org.alfresco.service.cmr.security.AuthorityService; -import org.alfresco.service.cmr.security.AuthorityType; -import org.alfresco.service.cmr.security.PermissionService; -import org.alfresco.service.cmr.security.PersonService; -import org.alfresco.util.Pair; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.extensions.surf.util.ParameterCheck; +import java.util.AbstractSet; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; + +import org.alfresco.model.ContentModel; +import org.alfresco.query.PagingRequest; +import org.alfresco.query.PagingResults; +import org.alfresco.repo.policy.ClassPolicyDelegate; +import org.alfresco.repo.policy.PolicyComponent; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authority.AuthorityServicePolicies.OnAuthorityAddedToGroup; +import org.alfresco.repo.security.authority.AuthorityServicePolicies.OnAuthorityRemovedFromGroup; +import org.alfresco.repo.security.authority.AuthorityServicePolicies.OnGroupDeleted; +import org.alfresco.repo.security.permissions.PermissionServiceSPI; +import org.alfresco.repo.security.person.UserNameMatcher; +import org.alfresco.repo.tenant.TenantService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.security.AuthenticationService; +import org.alfresco.service.cmr.security.AuthorityService; +import org.alfresco.service.cmr.security.AuthorityType; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.util.Pair; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.extensions.surf.util.ParameterCheck; /** * The default implementation of the authority service. @@ -64,6 +64,8 @@ import org.springframework.extensions.surf.util.ParameterCheck; */ public class AuthorityServiceImpl implements AuthorityService, InitializingBean { + public static final String GROUP_ALFRESCO_SYSTEM_ADMINISTRATORS_AUTHORITY = PermissionService.GROUP_PREFIX + "ALFRESCO_SYSTEM_ADMINISTRATORS"; + private static Set DEFAULT_ZONES = new HashSet(); static @@ -83,11 +85,11 @@ public class AuthorityServiceImpl implements AuthorityService, InitializingBean private Set guestSet = Collections.singleton(PermissionService.GUEST_AUTHORITY); private Set allSet = Collections.singleton(PermissionService.ALL_AUTHORITIES); private Set adminGroups = Collections.emptySet(); - private Set guestGroups = Collections.emptySet(); - - private ClassPolicyDelegate onAuthorityAddedToGroups; - private ClassPolicyDelegate onAuthorityRemovedFromGroup; - private ClassPolicyDelegate onGroupDeletedDelegate; + private Set guestGroups = Collections.emptySet(); + + private ClassPolicyDelegate onAuthorityAddedToGroups; + private ClassPolicyDelegate onAuthorityRemovedFromGroup; + private ClassPolicyDelegate onGroupDeletedDelegate; private PolicyComponent policyComponent; public AuthorityServiceImpl() @@ -133,18 +135,18 @@ public class AuthorityServiceImpl implements AuthorityService, InitializingBean public void setGuestGroups(Set guestGroups) { this.guestGroups = guestGroups; - } - - public void setPolicyComponent(PolicyComponent policyComponent) - { - this.policyComponent = policyComponent; - } - - public void init() - { - onAuthorityAddedToGroups = policyComponent.registerClassPolicy(AuthorityServicePolicies.OnAuthorityAddedToGroup.class); - onAuthorityRemovedFromGroup = policyComponent.registerClassPolicy(AuthorityServicePolicies.OnAuthorityRemovedFromGroup.class); - onGroupDeletedDelegate = policyComponent.registerClassPolicy(AuthorityServicePolicies.OnGroupDeleted.class); + } + + public void setPolicyComponent(PolicyComponent policyComponent) + { + this.policyComponent = policyComponent; + } + + public void init() + { + onAuthorityAddedToGroups = policyComponent.registerClassPolicy(AuthorityServicePolicies.OnAuthorityAddedToGroup.class); + onAuthorityRemovedFromGroup = policyComponent.registerClassPolicy(AuthorityServicePolicies.OnAuthorityRemovedFromGroup.class); + onGroupDeletedDelegate = policyComponent.registerClassPolicy(AuthorityServicePolicies.OnGroupDeleted.class); } @Override @@ -482,12 +484,12 @@ public class AuthorityServiceImpl implements AuthorityService, InitializingBean */ public void addAuthority(Collection parentNames, String childName) { - authorityDAO.addAuthority(parentNames, childName); - - OnAuthorityAddedToGroup policy = onAuthorityAddedToGroups.get(ContentModel.TYPE_AUTHORITY); - for (String parentGroup : parentNames) - { - policy.onAuthorityAddedToGroup(parentGroup, childName); + authorityDAO.addAuthority(parentNames, childName); + + OnAuthorityAddedToGroup policy = onAuthorityAddedToGroups.get(ContentModel.TYPE_AUTHORITY); + for (String parentGroup : parentNames) + { + policy.onAuthorityAddedToGroup(parentGroup, childName); } } @@ -565,18 +567,18 @@ public class AuthorityServiceImpl implements AuthorityService, InitializingBean } } authorityDAO.deleteAuthority(name); - permissionServiceSPI.deletePermissions(name); - - if (isGroup(type)) - { - OnGroupDeleted onGroupDelete = onGroupDeletedDelegate.get(ContentModel.TYPE_AUTHORITY); - onGroupDelete.onGroupDeleted(name, cascade); + permissionServiceSPI.deletePermissions(name); + + if (isGroup(type)) + { + OnGroupDeleted onGroupDelete = onGroupDeletedDelegate.get(ContentModel.TYPE_AUTHORITY); + onGroupDelete.onGroupDeleted(name, cascade); } - } - - private boolean isGroup(AuthorityType authorityType) - { - return AuthorityType.GROUP == authorityType || AuthorityType.EVERYONE == authorityType; + } + + private boolean isGroup(AuthorityType authorityType) + { + return AuthorityType.GROUP == authorityType || AuthorityType.EVERYONE == authorityType; } /** @@ -622,9 +624,9 @@ public class AuthorityServiceImpl implements AuthorityService, InitializingBean @Override public void removeAuthority(String parentName, String childName) { - authorityDAO.removeAuthority(parentName, childName); - - OnAuthorityRemovedFromGroup policy = onAuthorityRemovedFromGroup.get(ContentModel.TYPE_AUTHORITY); + authorityDAO.removeAuthority(parentName, childName); + + OnAuthorityRemovedFromGroup policy = onAuthorityRemovedFromGroup.get(ContentModel.TYPE_AUTHORITY); policy.onAuthorityRemovedFromGroup(parentName, childName); } @@ -770,6 +772,16 @@ public class AuthorityServiceImpl implements AuthorityService, InitializingBean return authorityDAO.getShortName(name); } + @Override + public boolean hasSysAdminAuthority() + { + final String currentUserName = AuthenticationUtil.getRunAsUser(); + if (currentUserName == null) + { + return false; + } + return getAuthoritiesForUser(currentUserName).contains(GROUP_ALFRESCO_SYSTEM_ADMINISTRATORS_AUTHORITY); + } /** * Lazy load set of authorities. Try not to iterate or ask for the size. Needed for the case where there diff --git a/repository/src/main/java/org/alfresco/service/cmr/repository/ContentService.java b/repository/src/main/java/org/alfresco/service/cmr/repository/ContentService.java index a2edb0249f..80fb2fc9b9 100644 --- a/repository/src/main/java/org/alfresco/service/cmr/repository/ContentService.java +++ b/repository/src/main/java/org/alfresco/service/cmr/repository/ContentService.java @@ -2,7 +2,7 @@ * #%L * Alfresco Repository * %% - * Copyright (C) 2005 - 2019 Alfresco Software Limited + * 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 @@ -25,6 +25,7 @@ */ package org.alfresco.service.cmr.repository; + import java.util.Collections; import java.util.Date; import java.util.Map; @@ -183,7 +184,7 @@ public interface ContentService */ @Auditable(parameters = {"nodeRef", "propertyQName", "update", "storageClasses"}) public ContentWriter getWriter(NodeRef nodeRef, QName propertyQName, boolean update, - StorageClassSet storageClassSet) throws InvalidNodeRefException, InvalidTypeException; + StorageClassSet storageClassSet) throws InvalidNodeRefException, InvalidTypeException; /** * Gets a writer to a temporary location. The longevity of the stored @@ -195,32 +196,46 @@ public interface ContentService public ContentWriter getTempWriter(); /** - * Gets a presigned URL to directly access a binary content. It is up to the - * content store if it can fulfil this request with an expiry time (in - * milliseconds) or not. + * Checks if the system and at least one store supports the retrieving of direct access URLs. * - * @param nodeRef - * a reference to a node having a content property - * @param expiresAt - * an optional expiry date, so the direct access url would become - * invalid when the expiry date is reached - * @return A direct access URL object for a binary content or returns null if not supported - * @throws IllegalArgumentException if there is no binary content for the node + * @return {@code true} if direct access URLs retrieving is supported, {@code false} otherwise */ - @Auditable(parameters = {"nodeRef", "expiresAt"}) - public DirectAccessUrl getDirectAccessUrl(NodeRef nodeRef, Date expiresAt); + boolean isContentDirectUrlEnabled(); /** - * Checks whether or not the current {@link ContentService} supports the provided {@link Set} storage classes + * Checks if the system and store supports the retrieving of a direct access {@code URL} for the given node. * - * @param storageClassSet The storage classes that will be checked whether or not are supported - * @return true if the storage classes are supported, false otherwise. + * @return {@code true} if direct access URLs retrieving is supported for the node, {@code false} otherwise */ - default boolean isStorageClassesSupported(StorageClassSet storageClassSet) + boolean isContentDirectUrlEnabled(NodeRef nodeRef); + + /** + * Gets a presigned URL to directly access the content. It is up to the actual store + * implementation if it can fulfil this request with an expiry time or not. + * + * @param nodeRef Node ref for which to obtain the direct access {@code URL}. + * @param attachment {@code true} if an attachment URL is requested, {@code false} for an embedded {@code URL}. + * @return A direct access {@code URL} object for the content. + * @throws UnsupportedOperationException if the store is unable to provide the information. + */ + default DirectAccessUrl requestContentDirectUrl(NodeRef nodeRef, boolean attachment) { - return false; + return requestContentDirectUrl(nodeRef, attachment, null); } + /** + * Gets a presigned URL to directly access the content. It is up to the actual store + * implementation if it can fulfil this request with an expiry time or not. + * + * @param nodeRef Node ref for which to obtain the direct access {@code URL}. + * @param attachment {@code true} if an attachment URL is requested, {@code false} for an embedded {@code URL}. + * @param validFor The time at which the direct access {@code URL} will expire. + * @return A direct access {@code URL} object for the content. + * @throws UnsupportedOperationException if the store is unable to provide the information. + */ + @Auditable(parameters = {"nodeRef", "validFor"}) + DirectAccessUrl requestContentDirectUrl(NodeRef nodeRef, boolean attachment, Long validFor); + /** * @return Returns the complete {@link Set} of supported storage classes by this {@link ContentService} */ diff --git a/repository/src/main/java/org/alfresco/service/cmr/security/AuthorityService.java b/repository/src/main/java/org/alfresco/service/cmr/security/AuthorityService.java index 6d6964b62d..5094302539 100644 --- a/repository/src/main/java/org/alfresco/service/cmr/security/AuthorityService.java +++ b/repository/src/main/java/org/alfresco/service/cmr/security/AuthorityService.java @@ -1,28 +1,28 @@ -/* - * #%L - * Alfresco Repository - * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited - * %% - * This file is part of the Alfresco software. - * If the software was purchased under a paid Alfresco license, the terms of - * the paid license agreement will prevail. Otherwise, the software is - * provided under the following open source license terms: - * - * Alfresco is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Alfresco is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Alfresco. If not, see . - * #L% - */ +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ package org.alfresco.service.cmr.security; import java.util.Collection; @@ -511,4 +511,19 @@ public interface AuthorityService */ @Auditable(parameters = {"type"}) public Set findAuthorities(AuthorityType type, String parentAuthority, boolean immediate, String displayNamePattern, String zoneName); -} + + /** + * Check the current user has system administration authority. + * + * @return true if the currently authenticated user has the system administration authority, otherwise false + * @throws UnsupportedOperationException if the implementing class (i.e. external clients) doesn't provide an implementation for the {@code hasSysAdminAuthority} operation + * + * @since 7.1 + */ + @Auditable + // See PRODMAN-493 -> REPO-5659 + default boolean hasSysAdminAuthority() + { + throw new UnsupportedOperationException("hasSysAdminAuthority"); + } +} diff --git a/repository/src/main/java/org/alfresco/transform/client/registry/CombinedConfig.java b/repository/src/main/java/org/alfresco/transform/client/registry/CombinedConfig.java index 8234ad14d6..1b208c5f61 100644 --- a/repository/src/main/java/org/alfresco/transform/client/registry/CombinedConfig.java +++ b/repository/src/main/java/org/alfresco/transform/client/registry/CombinedConfig.java @@ -30,10 +30,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.repo.content.transform.LocalPassThroughTransform; import org.alfresco.service.cmr.repository.MimetypeService; -import org.alfresco.transform.client.model.config.SupportedSourceAndTarget; import org.alfresco.transform.client.model.config.TransformConfig; -import org.alfresco.transform.client.model.config.TransformOption; -import org.alfresco.transform.client.model.config.TransformStep; import org.alfresco.transform.client.model.config.Transformer; import org.alfresco.util.ConfigFileFinder; import org.apache.commons.logging.Log; @@ -47,58 +44,22 @@ import org.apache.http.util.EntityUtils; import java.io.IOException; import java.io.StringReader; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import static org.alfresco.repo.content.metadata.AsynchronousExtractor.isMetadataEmbedMimetype; -import static org.alfresco.repo.content.metadata.AsynchronousExtractor.isMetadataExtractMimetype; /** - * This class reads multiple T-Engine config and local files and registers them all with a registry as if they were all + * This class reads multiple T-Engine config and local files and registers as if they were all * in one file. Transform options are shared between all sources.

* - * The caller should make calls to {@link #addRemoteConfig(List, String)} and {@link #addLocalConfig(String)} followed - * by a call to {@link #register(TransformServiceRegistryImpl)}. + * The caller should make calls to {@link #addRemoteConfig(List, String)}, {@link #addLocalConfig(String)} or + * {@link #addTransformConfig(TransformConfig, String, String)} followed by a call to + * {@link #register(TransformServiceRegistryImpl)}. * * @author adavis */ -public class CombinedConfig +public class CombinedConfig extends CombinedTransformConfig { private final Log log; - public static class TransformAndItsOrigin - { - final Transformer transformer; - final String baseUrl; - final String readFrom; - - public TransformAndItsOrigin(Transformer transformer, String baseUrl, String readFrom) - { - this.transformer = transformer; - this.baseUrl = baseUrl; - this.readFrom = readFrom; - } - - public Transformer getTransformer() - { - return transformer; - } - - public String getBaseUrl() - { - return baseUrl; - } - } - - Map> combinedTransformOptions = new HashMap<>(); - List combinedTransformers = new ArrayList<>(); - private ObjectMapper jsonObjectMapper = new ObjectMapper(); private ConfigFileFinder configFileFinder; private int tEngineCount; @@ -113,9 +74,7 @@ public class CombinedConfig protected void readJson(JsonNode jsonNode, String readFrom, String baseUrl) { TransformConfig transformConfig = jsonObjectMapper.convertValue(jsonNode, TransformConfig.class); - transformConfig.getTransformOptions().forEach((key, map) -> combinedTransformOptions.put(key, map)); - transformConfig.getTransformers().forEach(transformer -> combinedTransformers.add( - new TransformAndItsOrigin(transformer, baseUrl, readFrom))); + addTransformConfig(transformConfig, readFrom, baseUrl); } }; } @@ -255,6 +214,12 @@ public class CombinedConfig return message; } + @Override + protected boolean isPassThroughTransformName(String name) + { + return name.equals(LocalPassThroughTransform.NAME); + } + /** * Adds a PassThrough transform where the source and target mimetypes are identical, or transforms to "text/plain" * from selected text based types. @@ -273,277 +238,7 @@ public class CombinedConfig data.setTEngineCount(tEngineCount); data.setFileCount(configFileFinder.getFileCount()); - combinedTransformers = removeInvalidTransformers(combinedTransformers, registry); - combinedTransformers = sortTransformers(combinedTransformers, registry); - addWildcardSupportedSourceAndTarget(combinedTransformers); - - combinedTransformers.forEach(transformer -> - registry.register(transformer.transformer, combinedTransformOptions, - transformer.baseUrl, transformer.readFrom)); - } - - /** - * Discards transformers that are invalid (e.g. transformers that have both pipeline and failover sections). Calls - * {@link #removeInvalidTransformer(int, List, TransformServiceRegistryImpl, TransformAndItsOrigin, Transformer, - * String, String, boolean, boolean)} for each transform, so that subclasses (LocalCombinedConfig), may also - * discard invalid transforms or overridden transforms. - * - * @param combinedTransformers the full list of transformers in the order they were read. - * @param registry that wil hold the transforms. - */ - private List removeInvalidTransformers(List combinedTransformers, - TransformServiceRegistryImpl registry) - { - for (int i=0; i pipeline = transformer.getTransformerPipeline(); - List failover = transformer.getTransformerFailover(); - boolean isPipeline = pipeline != null && !pipeline.isEmpty(); - boolean isFailover = failover != null && !failover.isEmpty(); - - if (isPipeline && isFailover) - { - throw new IllegalArgumentException("Transformer " + transformerName(name) + - " cannot have pipeline and failover sections. Read from " + readFrom); - } - - // Local transforms may override each other or be invalid - int indexToRemove = removeInvalidTransformer(i, combinedTransformers, registry, transformAndItsOrigin, - transformer, name, readFrom, isPipeline, isFailover); - - // If required remove the requested transform - if (indexToRemove >= 0) - { - combinedTransformers.remove(indexToRemove); - // this may also require the current index i to be changed so we don't skip one. - if (i >= indexToRemove) - { - i--; - } - } - } - catch (IllegalArgumentException e) - { - String msg = e.getMessage(); - registry.logError(msg); - combinedTransformers.remove(i--); - } - } - return combinedTransformers; - } - - protected int removeInvalidTransformer(int i, List combinedTransformers, - TransformServiceRegistryImpl registry, - TransformAndItsOrigin transformAndItsOrigin, Transformer transformer, - String name, String readFrom, boolean isPipeline, boolean isFailover) - { - return -1; - } - - protected static String transformerName(String name) - { - return name == null ? " without a name" : "\"" + name + "\""; - } - - // Sort transformers so there are no forward references, if that is possible. - private List sortTransformers(List original, TransformServiceRegistryImpl registry) - { - List transformers = new ArrayList<>(original.size()); - List todo = new ArrayList<>(original.size()); - Set transformerNames = new HashSet<>(); - boolean added; - do - { - added = false; - for (TransformAndItsOrigin entry : original) - { - String name = entry.transformer.getTransformerName(); - List pipeline = entry.transformer.getTransformerPipeline(); - Set referencedTransformerNames = new HashSet<>(); - boolean addEntry = true; - if (pipeline != null) - { - for (TransformStep step : pipeline) - { - String stepName = step.getTransformerName(); - referencedTransformerNames.add(stepName); - } - } - List failover = entry.transformer.getTransformerFailover(); - if (failover != null) - { - referencedTransformerNames.addAll(failover); - } - - for (String referencedTransformerName : referencedTransformerNames) - { - if (!transformerNames.contains(referencedTransformerName)) - { - todo.add(entry); - addEntry = false; - break; - } - } - - if (addEntry) - { - transformers.add(entry); - added = true; - if (name != null) - { - transformerNames.add(name); - } - } - } - original.clear(); - original.addAll(todo); - todo.clear(); - } - while (added && !original.isEmpty()); - - transformers.addAll(todo); - - for (TransformAndItsOrigin transformAndItsOrigin : original) - { - String name = transformAndItsOrigin.getTransformer().getTransformerName(); - registry.logError("Transformer " + transformerName(name) + - " ignored as step transforms do not exist. Read from " + transformAndItsOrigin.readFrom); - } - - return transformers; - } - - private void addWildcardSupportedSourceAndTarget(List combinedTransformers) - { - Map transformers = new HashMap<>(); - combinedTransformers.forEach(ct -> transformers.put(ct.transformer.getTransformerName(), ct.transformer)); - - combinedTransformers.forEach(transformAndItsOrigin -> - { - Transformer transformer = transformAndItsOrigin.transformer; - - // If there are no SupportedSourceAndTarget, then work out all the wildcard combinations. - if (transformer.getSupportedSourceAndTargetList().isEmpty()) - { - List pipeline = transformer.getTransformerPipeline(); - List failover = transformer.getTransformerFailover(); - boolean isPipeline = pipeline != null && !pipeline.isEmpty(); - boolean isFailover = failover != null && !failover.isEmpty(); - if (isFailover) - { - // Copy all SupportedSourceAndTarget values from each step transformer - Set supportedSourceAndTargets = failover.stream().flatMap( - name -> transformers.get(name).getSupportedSourceAndTargetList().stream()). - collect(Collectors.toSet()); - transformer.setSupportedSourceAndTargetList(supportedSourceAndTargets); - } - else if (isPipeline) - { - // Build up SupportedSourceAndTarget values. The list of source types and max sizes will come from the - // initial step transformer that have a target mimetype that matches the first intermediate mimetype. - // We then step through all intermediate transformers checking the next intermediate type is supported. - // When we get to the last step transformer, it provides all the target mimetypes based on the previous - // intermediate mimeype. Any combinations supported by the first transformer are excluded. - boolean first = true; - String sourceMediaType = null; - Set sourceMediaTypesAndMaxSizes = null; - Set firstTransformOptions = null; - for (TransformStep step : pipeline) - { - String name = step.getTransformerName(); - Transformer stepTransformer = transformers.get(name); - if (stepTransformer == null) - { - break; - } - - String stepTrg = step.getTargetMediaType(); - if (first) - { - first = false; - sourceMediaTypesAndMaxSizes = stepTransformer.getSupportedSourceAndTargetList().stream(). - filter(s -> stepTrg.equals(s.getTargetMediaType())). - collect(Collectors.toSet()); - sourceMediaType = stepTrg; - firstTransformOptions = stepTransformer.getTransformOptions(); - } - else - { - final String src = sourceMediaType; - if (stepTrg == null) // if final step - { - // Create a cartesian product of sourceMediaType,MaxSourceSize and TargetMediaType where - // the source matches the last intermediate. - Set supportedSourceAndTargets = sourceMediaTypesAndMaxSizes.stream(). - flatMap(s -> stepTransformer.getSupportedSourceAndTargetList().stream(). - filter(st -> - { - String targetMimetype = st.getTargetMediaType(); - return st.getSourceMediaType().equals(src) && - !(isMetadataExtractMimetype(targetMimetype) || - isMetadataEmbedMimetype(targetMimetype)); - }). - map(t -> t.getTargetMediaType()). - map(trg -> SupportedSourceAndTarget.builder(). - withSourceMediaType(s.getSourceMediaType()). - withMaxSourceSizeBytes(s.getMaxSourceSizeBytes()). - withPriority(s.getPriority()). - withTargetMediaType(trg).build())). - collect(Collectors.toSet()); - - // Exclude duplicates with the first transformer, if it has the same options. - // There is no point doing more work. - Set transformOptions = transformer.getTransformOptions(); - if (sameOptions(transformOptions, firstTransformOptions)) - { - supportedSourceAndTargets.removeAll(sourceMediaTypesAndMaxSizes); - } - - transformer.setSupportedSourceAndTargetList(supportedSourceAndTargets); - } - else // if intermediate step - { - // Check source to target is supported (it normally is) - if (!stepTransformer.getSupportedSourceAndTargetList().stream(). - anyMatch(st -> st.getSourceMediaType().equals(src) && - st.getTargetMediaType().equals(stepTrg))) - { - break; - } - - sourceMediaType = stepTrg; - } - } - } - } - } - }); - } - - private boolean sameOptions(Set transformOptionNames1, Set transformOptionNames2) - { - // They have the same names - if (transformOptionNames1.equals(transformOptionNames2)) - { - return true; - } - - // Check the actual options. - Set transformOptions1 = getTransformOptions(transformOptionNames1); - Set transformOptions2 = getTransformOptions(transformOptionNames2); - return transformOptions1.equals(transformOptions2); - } - - private Set getTransformOptions(Set transformOptionNames) - { - Set transformOptions = new HashSet<>(); - transformOptionNames.forEach(name->transformOptions.addAll(combinedTransformOptions.get(name))); - return transformOptions; + combineTransformerConfig(registry); + registerCombinedTransformers(registry); } } diff --git a/repository/src/main/java/org/alfresco/transform/client/registry/TransformServiceRegistryImpl.java b/repository/src/main/java/org/alfresco/transform/client/registry/TransformServiceRegistryImpl.java index 2467a924ff..72cca58777 100644 --- a/repository/src/main/java/org/alfresco/transform/client/registry/TransformServiceRegistryImpl.java +++ b/repository/src/main/java/org/alfresco/transform/client/registry/TransformServiceRegistryImpl.java @@ -175,6 +175,12 @@ public abstract class TransformServiceRegistryImpl extends AbstractTransformRegi getLog().error(msg); } + @Override + protected void logWarn(String msg) + { + getLog().warn(msg); + } + @Override public String findTransformerName(final String sourceMimetype, final long sourceSizeInBytes, final String targetMimetype, final Map actualOptions, diff --git a/repository/src/main/resources/alfresco/bootstrap/alfrescoAuthorityStore.xml b/repository/src/main/resources/alfresco/bootstrap/alfrescoAuthorityStore.xml index 0e327634ac..50f259bf56 100644 --- a/repository/src/main/resources/alfresco/bootstrap/alfrescoAuthorityStore.xml +++ b/repository/src/main/resources/alfresco/bootstrap/alfrescoAuthorityStore.xml @@ -63,6 +63,17 @@ GROUP_ALFRESCO_MODEL_ADMINISTRATORS + + + + + + + GROUP_ALFRESCO_SYSTEM_ADMINISTRATORS + GROUP_ALFRESCO_SYSTEM_ADMINISTRATORS + GROUP_ALFRESCO_SYSTEM_ADMINISTRATORS + + @@ -110,6 +121,11 @@ view:pathref="${system.authorities_container.childname}/cm:GROUP_ALFRESCO_MODEL_ADMINISTRATORS" view:childName="cm:GROUP_ALFRESCO_MODEL_ADMINISTRATORS" /> + + + @@ -146,10 +162,15 @@ view:pathref="${system.authorities_container.childname}/cm:GROUP_ALFRESCO_MODEL_ADMINISTRATORS" view:childName="cm:GROUP_ALFRESCO_MODEL_ADMINISTRATORS" /> + + + - \ No newline at end of file + diff --git a/repository/src/main/resources/alfresco/bootstrap/alfrescoAuthorityStoreDefaultMembers.xml b/repository/src/main/resources/alfresco/bootstrap/alfrescoAuthorityStoreDefaultMembers.xml index 9cdf49e989..b9b2a6cd9e 100644 --- a/repository/src/main/resources/alfresco/bootstrap/alfrescoAuthorityStoreDefaultMembers.xml +++ b/repository/src/main/resources/alfresco/bootstrap/alfrescoAuthorityStoreDefaultMembers.xml @@ -40,6 +40,15 @@ view:childName="cm:${alfresco_user_store.adminusername}" /> + + + + + + + + @@ -66,4 +75,4 @@ - \ No newline at end of file + diff --git a/repository/src/main/resources/alfresco/content-services-context.xml b/repository/src/main/resources/alfresco/content-services-context.xml index aed14cdd9e..dfbdf56633 100644 --- a/repository/src/main/resources/alfresco/content-services-context.xml +++ b/repository/src/main/resources/alfresco/content-services-context.xml @@ -1,359 +1,376 @@ - - - - - - - false - - - ContentStore - - - - ${filecontentstore.subsystem.name} - - - - manager - - - - - - - - true - - - ContentStore - - - unencrypted - - - - managed - unencrypted - - - - - - - - - - - fileContentStore - - - - org.alfresco.repo.content.ContentStore - org.alfresco.repo.content.ContentStoreCaps - - - - - - - - - - - - - - - - ${dir.contentstore.deleted} - - - - - - - - - - - - - - - - - - - - - - ${system.content.orphanProtectDays} - - - ${system.content.deletionFailureAction} - - - - - - - - - - - - - - - - - - - - - ${system.content.cleanerBatchSize} - - - - - - ${system.content.eagerOrphanCleanup} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ${policy.content.update.ignoreEmpty} - - - - - - - - - - - - - - - - - - UTF-8 - - - - - - - - - - - - - - - - - - - - - - - classpath:alfresco/mimetype/mimetype-map.xml - classpath:alfresco/mimetype/mimetype-map-openoffice.xml - classpath*:alfresco/module/*/mimetype-map*.xml - classpath*:alfresco/extension/mimetype/*-map.xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - classpath:alfresco/ml/content-filter-lang.xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - EEE, d MMM yyyy HH:mm:ss Z - EEE, d MMM yy HH:mm:ss Z - d MMM yyyy HH:mm:ss Z - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - org.alfresco.repo.content.transform.TransformerConfigMBean - - - - - - - - - - - transformerDebugLog - - - - org.apache.commons.logging.Log - - - - - - - - - - - transformerLog - - - - org.apache.commons.logging.Log - - - - - - + + + + + + + false + + + ContentStore + + + + ${filecontentstore.subsystem.name} + + + + manager + + + + + + + + true + + + ContentStore + + + unencrypted + + + + managed + unencrypted + + + + + + + + + + + fileContentStore + + + + org.alfresco.repo.content.ContentStore + org.alfresco.repo.content.ContentStoreCaps + + + + + + + + + + + + + + + + ${dir.contentstore.deleted} + + + + + + + + + + + + + + + + + + + + + + ${system.content.orphanProtectDays} + + + ${system.content.deletionFailureAction} + + + + + + + + + + + + + + + + + + + + + ${system.content.cleanerBatchSize} + + + + + + ${system.content.eagerOrphanCleanup} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${policy.content.update.ignoreEmpty} + + + + + + + + + + + + + + + + + + + + + UTF-8 + + + + + + + + + + + + + + + + + + + + + + + classpath:alfresco/mimetype/mimetype-map.xml + classpath:alfresco/mimetype/mimetype-map-openoffice.xml + classpath*:alfresco/module/*/mimetype-map*.xml + classpath*:alfresco/extension/mimetype/*-map.xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + classpath:alfresco/ml/content-filter-lang.xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + EEE, d MMM yyyy HH:mm:ss Z + EEE, d MMM yy HH:mm:ss Z + d MMM yyyy HH:mm:ss Z + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + org.alfresco.repo.content.transform.TransformerConfigMBean + + + + + + + + + + + transformerDebugLog + + + + org.apache.commons.logging.Log + + + + + + + + + + + transformerLog + + + + org.apache.commons.logging.Log + + + + + + + + + + + + + + + + + + + + diff --git a/repository/src/main/resources/alfresco/dbscripts/upgrade/7.1.0/org.alfresco.repo.domain.dialect.PostgreSQLDialect/remove-alf_server-table.sql b/repository/src/main/resources/alfresco/dbscripts/upgrade/7.1.0/org.alfresco.repo.domain.dialect.PostgreSQLDialect/remove-alf_server-table.sql index 357afd3339..cb5679598b 100644 --- a/repository/src/main/resources/alfresco/dbscripts/upgrade/7.1.0/org.alfresco.repo.domain.dialect.PostgreSQLDialect/remove-alf_server-table.sql +++ b/repository/src/main/resources/alfresco/dbscripts/upgrade/7.1.0/org.alfresco.repo.domain.dialect.PostgreSQLDialect/remove-alf_server-table.sql @@ -12,6 +12,7 @@ ALTER TABLE alf_transaction DROP COLUMN IF EXISTS server_id; DROP TABLE IF EXISTS alf_server; +DROP SEQUENCE IF EXISTS alf_server_seq; -- -- Record script finish diff --git a/repository/src/main/resources/alfresco/ibatis/org.alfresco.repo.domain.dialect.Dialect/metadata-query-common-SqlMap.xml b/repository/src/main/resources/alfresco/ibatis/org.alfresco.repo.domain.dialect.Dialect/metadata-query-common-SqlMap.xml index 8230226816..ecc97b3451 100644 --- a/repository/src/main/resources/alfresco/ibatis/org.alfresco.repo.domain.dialect.Dialect/metadata-query-common-SqlMap.xml +++ b/repository/src/main/resources/alfresco/ibatis/org.alfresco.repo.domain.dialect.Dialect/metadata-query-common-SqlMap.xml @@ -177,7 +177,8 @@ - - + + limit #{offset}, #{limit} + \ No newline at end of file diff --git a/repository/src/main/resources/alfresco/messages/content-model_da.properties b/repository/src/main/resources/alfresco/messages/content-model_da.properties index d0cc7d281d..fc0f5bda22 100644 --- a/repository/src/main/resources/alfresco/messages/content-model_da.properties +++ b/repository/src/main/resources/alfresco/messages/content-model_da.properties @@ -125,7 +125,7 @@ cm_contentmodel.aspect.cm_auditable.title=Kan overv\u00e5ges cm_contentmodel.aspect.cm_auditable.description=Kan overv\u00e5ges cm_contentmodel.property.cm_created.title=Oprettelsesdato cm_contentmodel.property.cm_created.description=Oprettelsesdato -cm_contentmodel.property.cm_creator.title=Oprettet af +cm_contentmodel.property.cm_creator.title=Opretter cm_contentmodel.property.cm_creator.description=Den person, der oprettede elementet cm_contentmodel.property.cm_modified.title=\u00c6ndringsdato cm_contentmodel.property.cm_modified.description=Tidspunkt for, hvorn\u00e5r elementet senest blev \u00e6ndret diff --git a/repository/src/main/resources/alfresco/messages/iptc-model_da.properties b/repository/src/main/resources/alfresco/messages/iptc-model_da.properties index 3be0ce09bc..a74ab303f5 100644 --- a/repository/src/main/resources/alfresco/messages/iptc-model_da.properties +++ b/repository/src/main/resources/alfresco/messages/iptc-model_da.properties @@ -93,7 +93,7 @@ iptcxmp_iptcmodel.property.Iptc4xmpExt_DigitalSourcefileType.title=Originalfotos iptcxmp_iptcmodel.property.Iptc4xmpExt_DigitalSourcefileType.description=Kildens digitale filtype. iptcxmp_iptcmodel.property.Iptc4xmpExt_DigitalSourceType.title=Kildetype til dette foto iptcxmp_iptcmodel.property.Iptc4xmpExt_DigitalSourceType.description=Kildetype til dette digitale billede -iptcxmp_iptcmodel.property.Iptc4xmpExt_Event.title=Event +iptcxmp_iptcmodel.property.Iptc4xmpExt_Event.title=H\u00e6ndelse iptcxmp_iptcmodel.property.Iptc4xmpExt_Event.description=Navngiver eller beskriver den specifikke begivenhed indholdet relaterer til. iptcxmp_iptcmodel.property.plus_ImageSupplierID.title=Billedes leverand\u00f8r-ID iptcxmp_iptcmodel.property.plus_ImageSupplierID.description=Identificerer emnets den nyeste leverand\u00f8r, som ikke n\u00f8dvendigvis er ejer eller opretter. @@ -125,7 +125,7 @@ iptcxmp_iptcmodel.property.plus_LicensorCity.title=By iptcxmp_iptcmodel.property.plus_LicensorCity.description=By for en person eller virksomhed, der skal kontaktes for at f\u00e5 en licens til brug af emnet, eller som har licens p\u00e5 emnet. iptcxmp_iptcmodel.property.plus_LicensorCountry.title=Land iptcxmp_iptcmodel.property.plus_LicensorCountry.description=Land for en person eller virksomhed, der skal kontaktes for at f\u00e5 en licens til brug af emnet, eller som har licens p\u00e5 emnet. -iptcxmp_iptcmodel.property.plus_LicensorEmail.title=e-mail +iptcxmp_iptcmodel.property.plus_LicensorEmail.title=E-mailadresse iptcxmp_iptcmodel.property.plus_LicensorEmail.description=E-mail for en person eller virksomhed, der skal kontaktes for at f\u00e5 en licens til brug af emnet, eller som har licens p\u00e5 emnet. iptcxmp_iptcmodel.property.plus_LicensorExtendedAddress.title=Udviddet adresse iptcxmp_iptcmodel.property.plus_LicensorExtendedAddress.description=Udvidet adresse for en person eller virksomhed, der skal kontaktes for at f\u00e5 en licens til brug af emnet, eller som har licens p\u00e5 emnet. diff --git a/repository/src/main/resources/alfresco/messages/patch-service.properties b/repository/src/main/resources/alfresco/messages/patch-service.properties index ef0c0c90c0..51701e3a01 100644 --- a/repository/src/main/resources/alfresco/messages/patch-service.properties +++ b/repository/src/main/resources/alfresco/messages/patch-service.properties @@ -406,4 +406,4 @@ patch.db-V6.3-add-indexes-node-transaction.description=Create additional indexes patch.db-V7.1.0-remove-alf_server-table.description=Removes alf_server table and constraints - +patch.alfrescoSystemAdministrators.description=Adds the 'GROUP_ALFRESCO_SYSTEM_ADMINISTRATORS' group diff --git a/repository/src/main/resources/alfresco/messages/system-messages_de.properties b/repository/src/main/resources/alfresco/messages/system-messages_de.properties index 46705571d4..dc2ee67561 100755 --- a/repository/src/main/resources/alfresco/messages/system-messages_de.properties +++ b/repository/src/main/resources/alfresco/messages/system-messages_de.properties @@ -38,7 +38,7 @@ system.schema_comp.index_columns_validator=Number of columns in index doesn''t m system.schema_comp.column_names_validator=Column types do not match. Was {0}, but expected {1} system.schema_comp.schema_version_validator=version must be at least ''{0}'' # Optional long running patch messages... -system.schema_comp.patch_run_suggestion=The following problems will be resolved once the long running patch {0} has been run +system.schema_comp.patch_run_suggestion=Die folgenden Probleme werden behoben, sobald das lange Ausf\u00fchrungspatch ''{0}'' ausgef\u00fchrt wurde. # Clustering system.cluster.license.not_enabled=License does not permit clustering: clustering is disabled. diff --git a/repository/src/main/resources/alfresco/messages/system-messages_es.properties b/repository/src/main/resources/alfresco/messages/system-messages_es.properties index f78a5a9f07..9ccf8bb2a3 100755 --- a/repository/src/main/resources/alfresco/messages/system-messages_es.properties +++ b/repository/src/main/resources/alfresco/messages/system-messages_es.properties @@ -38,7 +38,7 @@ system.schema_comp.index_columns_validator=Number of columns in index doesn''t m system.schema_comp.column_names_validator=Column types do not match. Was {0}, but expected {1} system.schema_comp.schema_version_validator=version must be at least ''{0}'' # Optional long running patch messages... -system.schema_comp.patch_run_suggestion=The following problems will be resolved once the long running patch {0} has been run +system.schema_comp.patch_run_suggestion=Los siguientes problemas se resolver\u00e1n una vez que se haya ejecutado el parche de larga duraci\u00f3n {0} # Clustering system.cluster.license.not_enabled=License does not permit clustering: clustering is disabled. diff --git a/repository/src/main/resources/alfresco/messages/system-messages_fr.properties b/repository/src/main/resources/alfresco/messages/system-messages_fr.properties index 16501e5c0d..290ebbaa74 100755 --- a/repository/src/main/resources/alfresco/messages/system-messages_fr.properties +++ b/repository/src/main/resources/alfresco/messages/system-messages_fr.properties @@ -38,7 +38,7 @@ system.schema_comp.index_columns_validator=Number of columns in index doesn''t m system.schema_comp.column_names_validator=Column types do not match. Was {0}, but expected {1} system.schema_comp.schema_version_validator=version must be at least ''{0}'' # Optional long running patch messages... -system.schema_comp.patch_run_suggestion=The following problems will be resolved once the long running patch {0} has been run +system.schema_comp.patch_run_suggestion=Les probl\u00e8mes suivants seront r\u00e9solus une fois la longue ex\u00e9cution du correctif {0} termin\u00e9e # Clustering system.cluster.license.not_enabled=License does not permit clustering: clustering is disabled. diff --git a/repository/src/main/resources/alfresco/messages/system-messages_it.properties b/repository/src/main/resources/alfresco/messages/system-messages_it.properties index b9ed640c4c..71ba9be8b6 100755 --- a/repository/src/main/resources/alfresco/messages/system-messages_it.properties +++ b/repository/src/main/resources/alfresco/messages/system-messages_it.properties @@ -38,7 +38,7 @@ system.schema_comp.index_columns_validator=Number of columns in index doesn''t m system.schema_comp.column_names_validator=Column types do not match. Was {0}, but expected {1} system.schema_comp.schema_version_validator=version must be at least ''{0}'' # Optional long running patch messages... -system.schema_comp.patch_run_suggestion=The following problems will be resolved once the long running patch {0} has been run +system.schema_comp.patch_run_suggestion=I problemi seguenti verranno risolti al termine dell''esecuzione della patch di lunga durata {0} # Clustering system.cluster.license.not_enabled=License does not permit clustering: clustering is disabled. diff --git a/repository/src/main/resources/alfresco/messages/system-messages_ja.properties b/repository/src/main/resources/alfresco/messages/system-messages_ja.properties index a367d3aa8f..1d8a8427f5 100755 --- a/repository/src/main/resources/alfresco/messages/system-messages_ja.properties +++ b/repository/src/main/resources/alfresco/messages/system-messages_ja.properties @@ -38,7 +38,7 @@ system.schema_comp.index_columns_validator=Number of columns in index doesn''t m system.schema_comp.column_names_validator=Column types do not match. Was {0}, but expected {1} system.schema_comp.schema_version_validator=version must be at least ''{0}'' # Optional long running patch messages... -system.schema_comp.patch_run_suggestion=The following problems will be resolved once the long running patch {0} has been run +system.schema_comp.patch_run_suggestion=\u9577\u6642\u9593\u30d1\u30b9{0}\u3092\u5b9f\u884c\u3059\u308b\u3068\u3001\u6b21\u306e\u554f\u984c\u304c\u89e3\u6c7a\u3055\u308c\u307e\u3059 # Clustering system.cluster.license.not_enabled=License does not permit clustering: clustering is disabled. diff --git a/repository/src/main/resources/alfresco/messages/system-messages_nb.properties b/repository/src/main/resources/alfresco/messages/system-messages_nb.properties index 2f2a08c4f4..c681d4042b 100755 --- a/repository/src/main/resources/alfresco/messages/system-messages_nb.properties +++ b/repository/src/main/resources/alfresco/messages/system-messages_nb.properties @@ -38,7 +38,7 @@ system.schema_comp.index_columns_validator=Number of columns in index doesn''t m system.schema_comp.column_names_validator=Column types do not match. Was {0}, but expected {1} system.schema_comp.schema_version_validator=version must be at least ''{0}'' # Optional long running patch messages... -system.schema_comp.patch_run_suggestion=The following problems will be resolved once the long running patch {0} has been run +system.schema_comp.patch_run_suggestion=F\u00f8lgende problemer vil bli l\u00f8st s\u00e5 snart den lengekj\u00f8rende oppdateringen {0} har blitt kj\u00f8rt # Clustering system.cluster.license.not_enabled=License does not permit clustering: clustering is disabled. diff --git a/repository/src/main/resources/alfresco/messages/system-messages_nl.properties b/repository/src/main/resources/alfresco/messages/system-messages_nl.properties index dca3bf4a2f..35ce63c7a6 100755 --- a/repository/src/main/resources/alfresco/messages/system-messages_nl.properties +++ b/repository/src/main/resources/alfresco/messages/system-messages_nl.properties @@ -38,7 +38,7 @@ system.schema_comp.index_columns_validator=Number of columns in index doesn''t m system.schema_comp.column_names_validator=Column types do not match. Was {0}, but expected {1} system.schema_comp.schema_version_validator=version must be at least ''{0}'' # Optional long running patch messages... -system.schema_comp.patch_run_suggestion=The following problems will be resolved once the long running patch {0} has been run +system.schema_comp.patch_run_suggestion=De volgende problemen worden opgelost zodra de langlopende patch {0} is uitgevoerd # Clustering system.cluster.license.not_enabled=License does not permit clustering: clustering is disabled. diff --git a/repository/src/main/resources/alfresco/messages/system-messages_pt_BR.properties b/repository/src/main/resources/alfresco/messages/system-messages_pt_BR.properties index 3fa3581af7..a69825e0f8 100644 --- a/repository/src/main/resources/alfresco/messages/system-messages_pt_BR.properties +++ b/repository/src/main/resources/alfresco/messages/system-messages_pt_BR.properties @@ -38,7 +38,7 @@ system.schema_comp.index_columns_validator=Number of columns in index doesn''t m system.schema_comp.column_names_validator=Column types do not match. Was {0}, but expected {1} system.schema_comp.schema_version_validator=version must be at least ''{0}'' # Optional long running patch messages... -system.schema_comp.patch_run_suggestion=The following problems will be resolved once the long running patch {0} has been run +system.schema_comp.patch_run_suggestion=Os seguintes problemas ser\u00e3o resolvidos ap\u00f3s o patch de longa execu\u00e7\u00e3o {0} ter sido executado # Clustering system.cluster.license.not_enabled=License does not permit clustering: clustering is disabled. diff --git a/repository/src/main/resources/alfresco/messages/system-messages_ru.properties b/repository/src/main/resources/alfresco/messages/system-messages_ru.properties index 6333cd4d9f..1ff9c950bb 100755 --- a/repository/src/main/resources/alfresco/messages/system-messages_ru.properties +++ b/repository/src/main/resources/alfresco/messages/system-messages_ru.properties @@ -38,7 +38,7 @@ system.schema_comp.index_columns_validator=Number of columns in index doesn''t m system.schema_comp.column_names_validator=Column types do not match. Was {0}, but expected {1} system.schema_comp.schema_version_validator=version must be at least ''{0}'' # Optional long running patch messages... -system.schema_comp.patch_run_suggestion=The following problems will be resolved once the long running patch {0} has been run +system.schema_comp.patch_run_suggestion=\u0421\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b \u0431\u0443\u0434\u0443\u0442 \u0440\u0435\u0448\u0435\u043d\u044b \u043f\u043e\u0441\u043b\u0435 \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u0434\u043b\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0433\u043e \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u043c\u043e\u0433\u043e \u0438\u0441\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f {0} # Clustering system.cluster.license.not_enabled=License does not permit clustering: clustering is disabled. diff --git a/repository/src/main/resources/alfresco/messages/system-messages_zh_CN.properties b/repository/src/main/resources/alfresco/messages/system-messages_zh_CN.properties index 791476f720..89cc58c2d1 100755 --- a/repository/src/main/resources/alfresco/messages/system-messages_zh_CN.properties +++ b/repository/src/main/resources/alfresco/messages/system-messages_zh_CN.properties @@ -38,7 +38,7 @@ system.schema_comp.index_columns_validator=Number of columns in index doesn''t m system.schema_comp.column_names_validator=Column types do not match. Was {0}, but expected {1} system.schema_comp.schema_version_validator=version must be at least ''{0}'' # Optional long running patch messages... -system.schema_comp.patch_run_suggestion=The following problems will be resolved once the long running patch {0} has been run +system.schema_comp.patch_run_suggestion=\u4e00\u65e6\u8fd0\u884c\u4e86\u957f\u65f6\u95f4\u8fd0\u884c\u7684\u4fee\u8865\u7a0b\u5e8f{0}\uff0c\u4ee5\u4e0b\u95ee\u9898\u5c06\u5f97\u5230\u89e3\u51b3 # Clustering system.cluster.license.not_enabled=License does not permit clustering: clustering is disabled. diff --git a/repository/src/main/resources/alfresco/patch/patch-services-context.xml b/repository/src/main/resources/alfresco/patch/patch-services-context.xml index 49a5b90d25..e3daaeef11 100644 --- a/repository/src/main/resources/alfresco/patch/patch-services-context.xml +++ b/repository/src/main/resources/alfresco/patch/patch-services-context.xml @@ -1424,7 +1424,37 @@ ${system.remove-alf_server-table-from-db.ignored} - + + + patch.alfrescoSystemAdministrators + patch.alfrescoSystemAdministrators.description + 0 + 15001 + 15002 + + + + + + + + + + + + ALFRESCO_SYSTEM_ADMINISTRATORS + + + ALFRESCO_SYSTEM_ADMINISTRATORS + + + + APP.DEFAULT + AUTH.ALF + + + + + diff --git a/repository/src/main/resources/alfresco/public-services-security-context.xml b/repository/src/main/resources/alfresco/public-services-security-context.xml index 8b3ba3eb0d..caded70ecf 100644 --- a/repository/src/main/resources/alfresco/public-services-security-context.xml +++ b/repository/src/main/resources/alfresco/public-services-security-context.xml @@ -490,15 +490,16 @@ org.alfresco.service.cmr.repository.ContentService.getStoreTotalSpace=ACL_ALLOW - org.alfresco.service.cmr.repository.ContentService.getStoreFreeSpace=ACL_ALLOW + org.alfresco.service.cmr.repository.ContentService.getStoreFreeSpace=ACL_ALLOW org.alfresco.service.cmr.repository.ContentService.getSupportedStorageClasses=ACL_ALLOW org.alfresco.service.cmr.repository.ContentService.isStorageClassesSupported=ACL_ALLOW org.alfresco.service.cmr.repository.ContentService.getStorageClassesTransitions=ACL_ALLOW org.alfresco.service.cmr.repository.ContentService.getRawReader=ACL_METHOD.ROLE_ADMINISTRATOR org.alfresco.service.cmr.repository.ContentService.getReader=ACL_NODE.0.sys:base.ReadContent org.alfresco.service.cmr.repository.ContentService.getWriter=ACL_NODE.0.sys:base.WriteContent - org.alfresco.service.cmr.repository.ContentService.getDirectAccessUrl=ACL_NODE.0.sys:base.ReadContent org.alfresco.service.cmr.repository.ContentService.getTempWriter=ACL_ALLOW + org.alfresco.service.cmr.repository.ContentService.requestContentDirectUrl=ACL_NODE.0.sys:base.ReadContent + org.alfresco.service.cmr.repository.ContentService.isContentDirectUrlEnabled=ACL_ALLOW org.alfresco.service.cmr.repository.ContentService.findStorageClasses=ACL_NODE.0.sys:base.ReadContent org.alfresco.service.cmr.repository.ContentService.updateStorageClasses=ACL_ALLOW org.alfresco.service.cmr.repository.ContentService.*=ACL_DENY @@ -796,6 +797,7 @@ org.alfresco.service.cmr.security.AuthorityService.hasAdminAuthority=ACL_ALLOW org.alfresco.service.cmr.security.AuthorityService.hasGuestAuthority=ACL_ALLOW + org.alfresco.service.cmr.security.AuthorityService.hasSysAdminAuthority=ACL_ALLOW org.alfresco.service.cmr.security.AuthorityService.isAdminAuthority=ACL_ALLOW org.alfresco.service.cmr.security.AuthorityService.isGuestAuthority=ACL_ALLOW org.alfresco.service.cmr.security.AuthorityService.countUsers=ACL_ALLOW diff --git a/repository/src/main/resources/alfresco/repository.properties b/repository/src/main/resources/alfresco/repository.properties index ef6bade8a1..44e2cf2d90 100644 --- a/repository/src/main/resources/alfresco/repository.properties +++ b/repository/src/main/resources/alfresco/repository.properties @@ -1,1247 +1,1313 @@ -# Repository configuration - -repository.name=Main Repository - -# Schema number -version.schema=15001 - -# Directory configuration - -dir.root=./alf_data - -dir.contentstore=${dir.root}/contentstore -dir.contentstore.deleted=${dir.root}/contentstore.deleted -dir.contentstore.bucketsPerMinute=0 - -# ContentStore subsystem: default choice -filecontentstore.subsystem.name=unencryptedContentStore - -# The location of cached content -dir.cachedcontent=${dir.root}/cachedcontent - -# The value for the maximum permitted size in bytes of all content. -# No value (or a negative long) will be taken to mean that no limit should be applied. -# See content-services-context.xml -system.content.maximumFileSizeLimit= - -# -# The server mode. Set value in alfresco-global.properties -# UNKNOWN | TEST | BACKUP | PRODUCTION -# -system.serverMode=UNKNOWN - -# The location for lucene index files -dir.indexes=${dir.root}/lucene-indexes - -# The location for index backups -dir.indexes.backup=${dir.root}/backup-lucene-indexes - -# The location for lucene index locks -dir.indexes.lock=${dir.indexes}/locks - -#Directory to find external license -dir.license.external=. -# Spring resource location of external license files -location.license.external=file://${dir.license.external}/*.lic -# Spring resource location of embedded license files -location.license.embedded=/WEB-INF/alfresco/license/*.lic -# Spring resource location of license files on shared classpath -location.license.shared=classpath*:/alfresco/extension/license/*.lic - -# WebDAV initialization properties -system.webdav.servlet.enabled=true -system.webdav.url.path.prefix= -system.webdav.storeName=${protocols.storeName} -system.webdav.rootPath=${protocols.rootPath} -# File name patterns that trigger rename shuffle detection -# pattern is used by move - tested against full path after it has been lower cased. -system.webdav.renameShufflePattern=(.*/\\..*)|(.*[a-f0-9]{8}+$)|(.*\\.tmp$)|(.*atmp[0-9]+$)|(.*\\.wbk$)|(.*\\.bak$)|(.*\\~$)|(.*backup.*\\.do[ct]{1}[x]?[m]?$)|(.*\\.sb\\-\\w{8}\\-\\w{6}$) -system.webdav.activities.enabled=false - - -system.workflow.jbpm.comment.property.max.length=-1 -system.workflow.comment.property.max.length=4000 - -#Determines if Activiti definitions are visible -system.workflow.engine.activiti.definitions.visible=true - - -# Determines if the Activiti engine is enabled -system.workflow.engine.activiti.enabled=true -system.workflow.engine.activiti.idblocksize=100 -system.workflow.engine.activiti.taskvariableslimit=20000 - -# Determines if the workflows that are deployed to the activiti engine should -# be deployed in the tenant-context of the thread IF the tenant-service is enabled -# If set to false, all workflows deployed will be shared among tenants. Recommended -# setting is true unless there is a good reason to not allow deploy tenant-specific -# worklfows when a MT-environment is set up. -system.workflow.deployWorkflowsInTenant=true -#Determines if historic process instance are retained in case of canceling a process instance -system.workflow.engine.activiti.retentionHistoricProcessInstance=false - -# The maximum number of groups to check for pooled tasks. For performance -# reasons, this is limited to 500 by default. -system.workflow.maxAuthoritiesForPooledTasks=500 - -# The maximum number of pooled tasks to return in a query. It may be necessary -# to limit this depending on UI limitations. -system.workflow.maxPooledTasks=-1 - -# The maximum number of reviewers for "Group Review and Approve" workflow. -# Use '0' for unlimited. -system.workflow.maxGroupReviewers=0 - -index.subsystem.name=noindex - -# ######################################### # -# Index Tracking Configuration # -# ######################################### # -# -# Index tracking information of a certain age is cleaned out by a scheduled job. -# Any clustered system that has been offline for longer than this period will need to be seeded -# with a more recent backup of the Lucene indexes or the indexes will have to be fully rebuilt. -# Use -1 to disable purging. This can be switched on at any stage. -index.tracking.minRecordPurgeAgeDays=30 -# Unused transactions will be purged in chunks determined by commit time boundaries. 'index.tracking.purgeSize' specifies the size -# of the chunk (in ms). Default is a couple of hours. -index.tracking.purgeSize=7200000 - -# Change the failure behaviour of the configuration checker -system.bootstrap.config_check.strict=true - - -# -# How long should shutdown wait to complete normally before -# taking stronger action and calling System.exit() -# in ms, 10,000 is 10 seconds -# -shutdown.backstop.timeout=10000 -shutdown.backstop.enabled=false - -# Server Single User Mode -# note: -# only allow named user (note: if blank or not set then will allow all users) -# assuming maxusers is not set to 0 -#server.singleuseronly.name=admin - -# Server Max Users - limit number of users with non-expired tickets -# note: -# -1 allows any number of users, assuming not in single-user mode -# 0 prevents further logins, including the ability to enter single-user mode -server.maxusers=-1 - -# -# Disable all shared caches (mutable and immutable) -# These properties are used for diagnostic purposes -system.cache.disableMutableSharedCaches=false -system.cache.disableImmutableSharedCaches=false - -# The maximum capacity of the parent assocs cache (the number of nodes whose parents can be cached) -system.cache.parentAssocs.maxSize=130000 - -# The average number of parents expected per cache entry. This parameter is multiplied by the above -# value to compute a limit on the total number of cached parents, which will be proportional to the -# cache's memory usage. The cache will be pruned when this limit is exceeded to avoid excessive -# memory usage. -system.cache.parentAssocs.limitFactor=8 - -# -# Properties to limit resources spent on individual searches -# -# The maximum time spent pruning results -system.acl.maxPermissionCheckTimeMillis=10000 -# The maximum number of search results to perform permission checks against -system.acl.maxPermissionChecks=1000 -system.acl.maxPermissionCheckEnabled=false - -# The maximum number of filefolder list results -system.filefolderservice.defaultListMaxResults=5000 -# DEPRECATED: Use 'system.auditableData.preserve' -system.preserve.modificationData=false -# The default to preserve all cm:auditable data on a node when the process is not directly driven by a user action -system.auditableData.preserve=${system.preserve.modificationData} -# Specific control of how the FileFolderService treats cm:auditable data when performing moves -system.auditableData.FileFolderService=${system.auditableData.preserve} -# Specific control of whether ACL changes on a node trigger the cm:auditable aspect -system.auditableData.ACLs=${system.auditableData.preserve} - -# Properties to control read permission evaluation for acegi -system.readpermissions.optimise=true -system.readpermissions.bulkfetchsize=1000 - -# -# Manually control how the system handles maximum string lengths. -# Any zero or negative value is ignored. -# Only change this after consulting support or reading the appropriate Javadocs for -# org.alfresco.repo.domain.schema.SchemaBootstrap for V2.1.2. -# Before database migration, the string value storage may need to be adjusted using the scheduled job -system.maximumStringLength=-1 -system.maximumStringLength.jobCronExpression=* * * * * ? 2099 -system.maximumStringLength.jobQueryRange=10000 -system.maximumStringLength.jobThreadCount=4 - -# -# Limit hibernate session size by trying to amalgamate events for the L2 session invalidation -# - hibernate works as is up to this size -# - after the limit is hit events that can be grouped invalidate the L2 cache by type and not instance -# events may not group if there are post action listener registered (this is not the case with the default distribution) -system.hibernateMaxExecutions=20000 - -# -# Determine if modification timestamp propagation from child to parent nodes is respected or not. -# Even if 'true', the functionality is only supported for child associations that declare the -# 'propagateTimestamps' element in the dictionary definition. -system.enableTimestampPropagation=true - -# -# Enable system model integrity checking. -# WARNING: Changing this is unsupported; bugs may corrupt data -system.integrity.enabled=true -# Do integrity violations fail transactions -# WARNING: Changing this is unsupported; bugs may corrupt data -system.integrity.failOnViolation=true -# The number of errors to report when violations are detected -system.integrity.maxErrorsPerTransaction=5 -# Add call stacks to integrity events so that errors are logged with possible causes -# WARNING: This is expensive and should only be switched on for diagnostic purposes -system.integrity.trace=false - -# -# Decide if content should be removed from the system immediately after being orphaned. -# Do not change this unless you have examined the impact it has on your backup procedures. -system.content.eagerOrphanCleanup=false -# The number of days to keep orphaned content in the content stores. -# This has no effect on the 'deleted' content stores, which are not automatically emptied. -system.content.orphanProtectDays=14 -# The action to take when a store or stores fails to delete orphaned content -# IGNORE: Just log a warning. The binary remains and the record is expunged -# KEEP_URL: Log a warning and create a URL entry with orphan time 0. It won't be processed or removed. -system.content.deletionFailureAction=IGNORE -# The CRON expression to trigger the deletion of resources associated with orphaned content. -system.content.orphanCleanup.cronExpression=0 0 4 * * ? -# The batch size user by the content store cleaner -system.content.cleanerBatchSize=1000 - -# The CRON expression to trigger the cleanup of deleted nodes and dangling transactions that are old enough -system.nodeServiceCleanup.cronExpression=0 0 21 * * ? - -# When transforming archive files (.zip etc) into text representations (such as -# for full text indexing), should the files within the archive be processed too? -# If enabled, transformation takes longer, but searches of the files find more. -transformer.Archive.includeContents=false - -# Database configuration -db.schema.name= -db.schema.stopAfterSchemaBootstrap=false -db.schema.update=true -db.schema.update.lockRetryCount=24 -db.schema.update.lockRetryWaitSeconds=5 -db.driver=org.gjt.mm.mysql.Driver -db.name=alfresco -db.url=jdbc:mysql:///${db.name} -db.username=alfresco -db.password=alfresco -db.pool.initial=10 -db.pool.max=275 -db.txn.isolation=-1 -db.pool.statements.enable=true -db.pool.statements.max=40 -db.pool.min=10 -db.pool.idle=10 -db.pool.wait.max=5000 - -db.pool.validate.query= -db.pool.evict.interval=600000 -db.pool.evict.idle.min=1800000 -# -# note: for 'db.pool.evict.num.tests' see http://commons.apache.org/dbcp/configuration.html (numTestsPerEvictionRun) -# and also following extract from "org.apache.commons.pool.impl.GenericKeyedObjectPool" (1.5.5) -# -# * The number of objects to examine during each run of the idle object evictor thread (if any). -# * When a negative value is supplied, ceil({@link #getNumIdle})/abs({@link #getNumTestsPerEvictionRun}) -# * tests will be run. I.e., when the value is -n, roughly one nth of the -# * idle objects will be tested per run. -# -db.pool.evict.num.tests=-1 - -db.pool.evict.validate=false -db.pool.validate.borrow=true -db.pool.validate.return=false - -db.pool.abandoned.detect=false -db.pool.abandoned.time=300 -# -# db.pool.abandoned.log=true (logAbandoned) adds overhead (http://commons.apache.org/dbcp/configuration.html) -# and also requires db.pool.abandoned.detect=true (removeAbandoned) -# -db.pool.abandoned.log=false - - -# Audit configuration -audit.enabled=true -audit.tagging.enabled=true -audit.alfresco-access.enabled=false -audit.alfresco-access.sub-actions.enabled=false -audit.cmischangelog.enabled=false -audit.dod5015.enabled=false -# Setting this flag to true will force startup failure when invalid audit configurations are detected -audit.config.strict=false -# Audit map filter for AccessAuditor - restricts recorded events to user driven events -audit.filter.alfresco-access.default.enabled=false -audit.filter.alfresco-access.transaction.user=~System;~null;.* -audit.filter.alfresco-access.transaction.type=cm:folder;cm:content;st:site -audit.filter.alfresco-access.transaction.path=~/sys:archivedItem;~/ver:;.* - - -# System Configuration -system.store=system://system -system.descriptor.childname=sys:descriptor -system.descriptor.current.childname=sys:descriptor-current - -# User config -alfresco_user_store.store=user://alfrescoUserStore -alfresco_user_store.system_container.childname=sys:system -alfresco_user_store.user_container.childname=sys:people - -# note: default admin username - should not be changed after installation -alfresco_user_store.adminusername=admin - -# Initial password - editing this will not have any effect once the repository is installed -alfresco_user_store.adminpassword=209c6174da490caeb422f3fa5a7ae634 - -# note: default guest username - should not be changed after installation -alfresco_user_store.guestusername=guest - -# Used to move home folders to a new location -home_folder_provider_synchronizer.enabled=false -home_folder_provider_synchronizer.override_provider= -home_folder_provider_synchronizer.keep_empty_parents=false - -# Spaces Archive Configuration -spaces.archive.store=archive://SpacesStore - -# Spaces Configuration -spaces.store=workspace://SpacesStore -spaces.company_home.childname=app:company_home -spaces.guest_home.childname=app:guest_home -spaces.dictionary.childname=app:dictionary -spaces.templates.childname=app:space_templates -spaces.imap_attachments.childname=cm:Imap Attachments -spaces.imap_home.childname=cm:Imap Home -spaces.imapConfig.childname=app:imap_configs -spaces.imap_templates.childname=app:imap_templates -spaces.scheduled_actions.childname=cm:Scheduled Actions -spaces.emailActions.childname=app:email_actions -spaces.searchAction.childname=cm:search -spaces.templates.content.childname=app:content_templates -spaces.templates.email.childname=app:email_templates -spaces.templates.email.invite1.childname=app:invite_email_templates -spaces.templates.email.notify.childname=app:notify_email_templates -spaces.templates.email.following.childname=app:following -spaces.templates.rss.childname=app:rss_templates -spaces.savedsearches.childname=app:saved_searches -spaces.scripts.childname=app:scripts -spaces.content_forms.childname=app:forms -spaces.user_homes.childname=app:user_homes -spaces.user_homes.regex.key=userName -spaces.user_homes.regex.pattern= -spaces.user_homes.regex.group_order= -spaces.sites.childname=st:sites -spaces.templates.email.invite.childname=cm:invite -spaces.templates.email.activities.childname=cm:activities -spaces.rendition.rendering_actions.childname=app:rendering_actions -spaces.replication.replication_actions.childname=app:replication_actions -spaces.transfers.childname=app:transfers -spaces.transfer_groups.childname=app:transfer_groups -spaces.transfer_temp.childname=app:temp -spaces.inbound_transfer_records.childname=app:inbound_transfer_records -spaces.webscripts.childname=cm:webscripts -spaces.extension_webscripts.childname=cm:extensionwebscripts -spaces.models.childname=app:models -spaces.workflow.definitions.childname=app:workflow_defs -spaces.templates.email.workflowemailnotification.childname=cm:workflownotification -spaces.nodetemplates.childname=app:node_templates -spaces.shared.childname=app:shared -spaces.solr_facets.root.childname=srft:facets -spaces.smartfolders.childname=app:smart_folders -spaces.smartdownloads.childname=app:smart_downloads -spaces.transfer_summary_report.location=/${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.transfers.childname}/${spaces.inbound_transfer_records.childname} -spaces.quickshare.link_expiry_actions.childname=app:quick_share_link_expiry_actions - - -# ADM VersionStore Configuration -version.store.initialVersion=true -version.store.enableAutoVersioning=true -version.store.enableAutoVersionOnUpdateProps=false -version.store.deprecated.lightWeightVersionStore=workspace://lightWeightVersionStore -version.store.version2Store=workspace://version2Store - -# Optional Comparator class name to sort versions. -# Set to: org.alfresco.repo.version.common.VersionLabelComparator -# if upgrading from a version that used unordered sequences in a cluster. -version.store.versionComparatorClass= - -# Folders for storing people -system.system_container.childname=sys:system -system.people_container.childname=sys:people -system.authorities_container.childname=sys:authorities -system.zones_container.childname=sys:zones - -# Folders for storing workflow related info -system.workflow_container.childname=sys:workflow - -# Folder for storing shared remote credentials -system.remote_credentials_container.childname=sys:remote_credentials - -# Folder for storing syncset definitions -system.syncset_definition_container.childname=sys:syncset_definitions - -# Folder for storing download archives -system.downloads_container.childname=sys:downloads - -# Folder for storing IdP's certificate definitions -system.certificate_container.childname=sys:samlcertificate - -# Are user names case sensitive? -user.name.caseSensitive=false -domain.name.caseSensitive=false -domain.separator= - -#Format caption extracted from the XML Schema. -xforms.formatCaption=true - -# ECM content usages/quotas -system.usages.enabled=false -system.usages.clearBatchSize=0 -system.usages.updateBatchSize=50 - -# Repository endpoint - used by Activity Service -repo.remote.endpoint=/service - -# Some authentication mechanisms may need to create people -# in the repository on demand. This enables that feature. -# If disabled an error will be generated for missing -# people. If enabled then a person will be created and -# persisted. -create.missing.people=${server.transaction.allow-writes} - -# Create home folders (unless disabled, see next property) as people are created (true) or create them lazily (false) -home.folder.creation.eager=true -# Disable home folder creation - if true then home folders are not created (neither eagerly nor lazily) -home.folder.creation.disabled=false - -# Should we consider zero byte content to be the same as no content when firing -# content update policies? Prevents 'premature' firing of inbound content rules -# for some clients such as Mac OS X Finder -policy.content.update.ignoreEmpty=true - -# Default value of alfresco.rmi.services.host is 0.0.0.0 which means 'listen on all adapters'. -# This allows connections to JMX both remotely and locally. -# -alfresco.rmi.services.port=50500 -alfresco.rmi.services.external.host=localhost -alfresco.rmi.services.host=0.0.0.0 - -# If the RMI address is in-use, how many retries should be done before aborting -# Default value of alfresco.rmi.services.retries is 0 which means 'Don't retry if the address is in-use' -alfresco.rmi.services.retries=4 -# How long in milliseconds to wait after a failed server socket bind, before retrying -alfresco.rmi.services.retryInterval=250 - -# RMI service ports for the individual services. -# These eight services are available remotely. -# -# Assign individual ports for each service for best performance -# or run several services on the same port, you can even run everything on 50500 if -# running through a firewall. -# -# Specify 0 to use a random unused port. -# -monitor.rmi.service.port=50508 - -# -# enable or disable individual RMI services -# -monitor.rmi.service.enabled=false - - -# Should the Mbean server bind to an existing server. Set to true for most application servers. -# false for WebSphere clusters. -mbean.server.locateExistingServerIfPossible=true - -# Rendition Service 2 -renditionService2.enabled=true - -# Thumbnail Service -system.thumbnail.generate=true - -# when creating doc via CMIS - optionally configure set of renditions names to request async -cmis.create.doc.request.renditions.set= - -# Default thumbnail limits -# When creating thumbnails, only use the first pageLimit pages -system.thumbnail.definition.default.timeoutMs=-1 -system.thumbnail.definition.default.readLimitTimeMs=-1 -system.thumbnail.definition.default.maxSourceSizeKBytes=-1 -system.thumbnail.definition.default.readLimitKBytes=-1 -system.thumbnail.definition.default.pageLimit=-1 -system.thumbnail.definition.default.maxPages=-1 - -# Max mimetype sizes to create thumbnail icons -system.thumbnail.mimetype.maxSourceSizeKBytes.pdf=-1 -system.thumbnail.mimetype.maxSourceSizeKBytes.txt=-1 -system.thumbnail.mimetype.maxSourceSizeKBytes.docx=-1 -system.thumbnail.mimetype.maxSourceSizeKBytes.xlsx=-1 -system.thumbnail.mimetype.maxSourceSizeKBytes.pptx=-1 -system.thumbnail.mimetype.maxSourceSizeKBytes.odt=-1 -system.thumbnail.mimetype.maxSourceSizeKBytes.ods=-1 -system.thumbnail.mimetype.maxSourceSizeKBytes.odp=-1 - -# Configuration for handling of failing thumbnails. -# See NodeEligibleForRethumbnailingEvaluator's javadoc for details. -# -# Retry periods limit the frequency with which the repository will attempt to create Share thumbnails -# for content nodes which have previously failed in their thumbnail attempts. -# These periods are in seconds. -# -# 604800s = 60s * 60m * 24h * 7d = 1 week -system.thumbnail.retryPeriod=60 -system.thumbnail.retryCount=2 -system.thumbnail.quietPeriod=604800 -system.thumbnail.quietPeriodRetriesEnabled=true -system.thumbnail.redeployStaticDefsOnStartup=true - -content.metadata.async.extract.enabled=true -content.metadata.async.embed.enabled=true - -# The default timeout for metadata mapping extracters -content.metadataExtracter.default.timeoutMs=20000 - -# Local transformer urls to T-engines to service transform requests via http. Enabled by default. -localTransform.core-aio.url=http://localhost:8090/ - -# When a local transformer .url is set, this value indicates the amount of time to wait after a connection failure -# before retrying the connection to allow a docker container to (re)start. -localTransform.core-aio.startupRetryPeriodSeconds=60 - -# Property to enable upgrade from 2.1-A -V2.1-A.fixes.to.schema=0 -#V2.1-A.fixes.to.schema=82 - -# The default authentication chain -authentication.chain=alfrescoNtlm1:alfrescoNtlm - -# Do authentication tickets expire or live for ever? -authentication.ticket.ticketsExpire=true - -# If ticketsEpire is true then how they should expire? -# Valid values are: AFTER_INACTIVITY, AFTER_FIXED_TIME, DO_NOT_EXPIRE -# The default is AFTER_FIXED_TIME -authentication.ticket.expiryMode=AFTER_INACTIVITY - -# If authentication.ticket.ticketsExpire is true and -# authentication.ticket.expiryMode is AFTER_FIXED_TIME or AFTER_INACTIVITY, -# this controls the minimum period for which tickets are valid. -# The default is PT1H for one hour. -authentication.ticket.validDuration=PT1H - -# Use one ticket for all user sessions -# For the pre 4.2 behaviour of one ticket per session set this to false. -authentication.ticket.useSingleTicketPerUser=true - -authentication.alwaysAllowBasicAuthForAdminConsole.enabled=true -authentication.getRemoteUserTimeoutMilliseconds=10000 - -# FTP access -ftp.enabled=false - -# Default root path for protocols -protocols.storeName=${spaces.store} -protocols.rootPath=/${spaces.company_home.childname} - -# OpenCMIS -opencmis.connector.default.store=${spaces.store} -opencmis.connector.default.rootPath=/${spaces.company_home.childname} -opencmis.connector.default.typesDefaultMaxItems=500 -opencmis.connector.default.typesDefaultDepth=-1 -opencmis.connector.default.objectsDefaultMaxItems=10000 -opencmis.connector.default.objectsDefaultDepth=100 -opencmis.connector.default.contentChangesDefaultMaxItems=10000 -opencmis.connector.default.openHttpSession=false -opencmis.activities.enabled=true -opencmis.bulkUpdateProperties.maxItemsSize=1000 -opencmis.bulkUpdateProperties.batchSize=20 -opencmis.bulkUpdateProperties.workerThreads=2 -opencmis.maxContentSizeMB=4096 -opencmis.memoryThresholdKB=4096 - -# URL generation overrides - -# if true, the context path of OpenCMIS generated urls will be set to "opencmis.context.value", otherwise it will be taken from the request url -opencmis.context.override=false -opencmis.context.value= -# if true, the servlet path of OpenCMIS generated urls will be set to "opencmis.servletpath.value", otherwise it will be taken from the request url -opencmis.servletpath.override=false -opencmis.servletpath.value= -opencmis.server.override=false -opencmis.server.value= - -# IMAP -imap.server.enabled=false -imap.server.port=143 -imap.server.attachments.extraction.enabled=true - -# Default IMAP mount points -imap.config.home.store=${spaces.store} -imap.config.home.rootPath=/${spaces.company_home.childname} -imap.config.home.folderPath=${spaces.imap_home.childname} -imap.config.server.mountPoints=AlfrescoIMAP -imap.config.server.mountPoints.default.mountPointName=IMAP -imap.config.server.mountPoints.default.modeName=ARCHIVE -imap.config.server.mountPoints.default.store=${spaces.store} -imap.config.server.mountPoints.default.rootPath=${protocols.rootPath} -imap.config.server.mountPoints.value.AlfrescoIMAP.mountPointName=Alfresco IMAP -imap.config.server.mountPoints.value.AlfrescoIMAP.modeName=MIXED - -#Imap extraction settings -#imap.attachments.mode: -# SEPARATE -- All attachments for each email will be extracted to separate folder. -# COMMON -- All attachments for all emails will be extracted to one folder. -# SAME -- Attachments will be extracted to the same folder where email lies. -imap.attachments.mode=SEPARATE -imap.attachments.folder.store=${spaces.store} -imap.attachments.folder.rootPath=/${spaces.company_home.childname} -imap.attachments.folder.folderPath=${spaces.imap_attachments.childname} - -# Activities Feed - refer to subsystem - -# Feed max ID range to limit maximum number of entries -activities.feed.max.idRange=1000000 -# Feed max size (number of entries) -activities.feed.max.size=200 -# Feed max age (eg. 44640 mins => 31 days) -activities.feed.max.ageMins=44640 - -activities.feed.generator.jsonFormatOnly=true -activities.feed.fetchBatchSize=250 -activities.feedNotifier.batchSize=200 -activities.feedNotifier.numThreads=2 - -# Subsystem unit test values. Will not have any effect on production servers -subsystems.test.beanProp.default.longProperty=123456789123456789 -subsystems.test.beanProp.default.anotherStringProperty=Global Default -subsystems.test.beanProp=inst1,inst2,inst3 -subsystems.test.beanProp.value.inst2.boolProperty=true -subsystems.test.beanProp.value.inst3.anotherStringProperty=Global Instance Default -subsystems.test.simpleProp2=true -subsystems.test.simpleProp3=Global Default3 - -# Default Async Action Thread Pool -default.async.action.threadPriority=1 -default.async.action.corePoolSize=8 -default.async.action.maximumPoolSize=20 - -# Deployment Service -deployment.service.numberOfSendingThreads=5 -deployment.service.corePoolSize=2 -deployment.service.maximumPoolSize=3 -deployment.service.threadPriority=5 -# How long to wait in mS before refreshing a target lock - detects shutdown servers -deployment.service.targetLockRefreshTime=60000 -# How long to wait in mS from the last communication before deciding that deployment has failed, possibly -# the destination is no longer available? -deployment.service.targetLockTimeout=3600000 -# Deployment method used to deploy this Alfresco instance (DEFAULT, INSTALLER, DOCKER_COMPOSE, HELM_CHART, ANSIBLE, ZIP, QUICK_START) -deployment.method=DEFAULT - -#Invitation Service -# Should send emails as part of invitation process. -notification.email.siteinvite=true -# Moderated invite Activiti workflow -site.invite.moderated.workflowId=activiti$activitiInvitationModerated -# Add intneral users Activiti workflow (use activiti$activitiInvitationNominated to revert to requiring accept of invite for internal users) -site.invite.nominated.workflowId=activiti$activitiInvitationNominatedAddDirect -# Add external users Activiti workflow -site.invite.nominatedExternal.workflowId=activiti$activitiInvitationNominated - -# Replication Service -replication.enabled=false - -# Transfer Service -transferservice.receiver.enabled=false -transferservice.receiver.stagingDir=${java.io.tmpdir}/alfresco-transfer-staging -# -# How long to wait in mS before refreshing a transfer lock - detects shutdown servers -# Default 1 minute. -transferservice.receiver.lockRefreshTime=60000 -# -# How many times to attempt retry the transfer lock -transferservice.receiver.lockRetryCount=3 -# How long to wait, in mS, before retrying the transfer lock -transferservice.receiver.lockRetryWait=100 -# -# How long to wait, in mS, since the last contact with from the client before -# timing out a transfer. Needs to be long enough to cope with network delays and "thinking -# time" for both source and destination. Default 5 minutes. -transferservice.receiver.lockTimeOut=300000 - -# OrphanReaper -orphanReaper.lockRefreshTime=60000 -orphanReaper.lockTimeOut=3600000 - - -# security -security.anyDenyDenies=true -# Whether to post-process denies. Only applies to solr4+ when anyDenyDenies is true. -security.postProcessDenies=false - -# -# Encryption properties -# -# default keystores location -dir.keystore=classpath:alfresco/keystore - -# general encryption parameters -encryption.keySpec.class=org.alfresco.encryption.DESEDEKeyGenerator -encryption.keyAlgorithm=AES -encryption.cipherAlgorithm=AES/CBC/PKCS5Padding - -# secret key keystore configuration -encryption.keystore.location=${dir.keystore}/keystore -# configuration via metadata is deprecated -encryption.keystore.keyMetaData.location= -encryption.keystore.provider= -encryption.keystore.type=pkcs12 - -# backup secret key keystore configuration -encryption.keystore.backup.location=${dir.keystore}/backup-keystore -# configuration via metadata is deprecated -encryption.keystore.backup.keyMetaData.location= -encryption.keystore.backup.provider= -encryption.keystore.backup.type=pkcs12 - -# Should encryptable properties be re-encrypted with new encryption keys on botstrap? -encryption.bootstrap.reencrypt=false - -# mac/md5 encryption -encryption.mac.messageTimeout=30000 -encryption.mac.algorithm=HmacSHA1 - -# ssl encryption -encryption.ssl.keystore.location=${dir.keystore}/ssl.keystore -encryption.ssl.keystore.provider= -encryption.ssl.keystore.type=JCEKS -# configuration via metadata is deprecated -encryption.ssl.keystore.keyMetaData.location= -encryption.ssl.truststore.location=${dir.keystore}/ssl.truststore -encryption.ssl.truststore.provider= -encryption.ssl.truststore.type=JCEKS -# configuration via metadata is deprecated -encryption.ssl.truststore.keyMetaData.location= - -# Re-encryptor properties -encryption.reencryptor.chunkSize=100 -encryption.reencryptor.numThreads=2 - -# SOLR connection details (e.g. for JMX) -solr.host=localhost -solr.port=8983 -solr.port.ssl=8984 -solr.solrUser=solr -solr.solrPassword=solr -# none, https -solr.secureComms=https -solr.sharedSecret= -solr.sharedSecret.header=X-Alfresco-Search-Secret -solr.cmis.alternativeDictionary=DEFAULT_DICTIONARY - -solr.max.total.connections=40 -solr.max.host.connections=40 - -# Solr connection timeouts -# solr connect timeout in ms -solr.solrConnectTimeout=5000 - -# cron expression defining how often the Solr Admin client (used by JMX) pings Solr if it goes away -solr.solrPingCronExpression=0 0/5 * * * ? * - - -#Default SOLR store mappings mappings -solr.store.mappings=solrMappingAlfresco,solrMappingArchive -solr.store.mappings.value.solrMappingAlfresco.httpClientFactory=solrHttpClientFactory -solr.store.mappings.value.solrMappingAlfresco.baseUrl=/solr/alfresco -solr.store.mappings.value.solrMappingAlfresco.protocol=workspace -solr.store.mappings.value.solrMappingAlfresco.identifier=SpacesStore -solr.store.mappings.value.solrMappingArchive.httpClientFactory=solrHttpClientFactory -solr.store.mappings.value.solrMappingArchive.baseUrl=/solr/archive -solr.store.mappings.value.solrMappingArchive.protocol=archive -solr.store.mappings.value.solrMappingArchive.identifier=SpacesStore - -#Default SOLR 4 store mappings mappings -solr4.store.mappings=solrMappingAlfresco,solrMappingArchive -solr4.store.mappings.value.solrMappingAlfresco.httpClientFactory=solrHttpClientFactory -solr4.store.mappings.value.solrMappingAlfresco.baseUrl=/solr4/alfresco -solr4.store.mappings.value.solrMappingAlfresco.protocol=workspace -solr4.store.mappings.value.solrMappingAlfresco.identifier=SpacesStore -solr4.store.mappings.value.solrMappingArchive.httpClientFactory=solrHttpClientFactory -solr4.store.mappings.value.solrMappingArchive.baseUrl=/solr4/archive -solr4.store.mappings.value.solrMappingArchive.protocol=archive -solr4.store.mappings.value.solrMappingArchive.identifier=SpacesStore - -#Default SOLR 6 store mappings mappings -solr6.store.mappings=solrMappingAlfresco,solrMappingArchive,solrMappingHistory -solr6.store.mappings.value.solrMappingAlfresco.httpClientFactory=solrHttpClientFactory -solr6.store.mappings.value.solrMappingAlfresco.baseUrl=/solr/alfresco -solr6.store.mappings.value.solrMappingAlfresco.protocol=workspace -solr6.store.mappings.value.solrMappingAlfresco.identifier=SpacesStore -solr6.store.mappings.value.solrMappingArchive.httpClientFactory=solrHttpClientFactory -solr6.store.mappings.value.solrMappingArchive.baseUrl=/solr/archive -solr6.store.mappings.value.solrMappingArchive.protocol=archive -solr6.store.mappings.value.solrMappingArchive.identifier=SpacesStore -solr6.store.mappings.value.solrMappingHistory.httpClientFactory=solrHttpClientFactory -solr6.store.mappings.value.solrMappingHistory.baseUrl=/solr/history -solr6.store.mappings.value.solrMappingHistory.protocol=workspace -solr6.store.mappings.value.solrMappingHistory.identifier=history - -# -# URL Shortening Properties -# -urlshortening.bitly.username=brianalfresco -urlshortening.bitly.api.key=R_ca15c6c89e9b25ccd170bafd209a0d4f -urlshortening.bitly.url.length=20 - -# -# Bulk Filesystem Importer -# - -# The number of threads to employ in a batch import -bulkImport.batch.numThreads=4 - -# The size of a batch in a batch import i.e. the number of files to import in a -# transaction/thread -bulkImport.batch.batchSize=20 - - -# -# Caching Content Store -# -system.content.caching.cacheOnInbound=true -system.content.caching.maxDeleteWatchCount=1 -# Clean up every day at 3 am -system.content.caching.contentCleanup.cronExpression=0 0 3 * * ? -system.content.caching.minFileAgeMillis=60000 -system.content.caching.maxUsageMB=4096 -# maxFileSizeMB - 0 means no max file size. -system.content.caching.maxFileSizeMB=0 -# When the CachingContentStore is about to write a cache file but the disk usage is in excess of panicThresholdPct -# (default 90%) then the cache file is not written and the cleaner is started (if not already running) in a new thread. -system.content.caching.panicThresholdPct=90 -# When a cache file has been written that results in cleanThresholdPct (default 80%) of maxUsageBytes -# being exceeded then the cached content cleaner is invoked (if not already running) in a new thread. -system.content.caching.cleanThresholdPct=80 -# An aggressive cleaner is run till the targetUsagePct (default 70%) of maxUsageBytes is achieved -system.content.caching.targetUsagePct=70 -# Threshold in seconds indicating a minimal gap between normal cleanup starts -system.content.caching.normalCleanThresholdSec=0 - -mybatis.useLocalCaches=false - -fileFolderService.checkHidden.enabled=true - - -ticket.cleanup.cronExpression=0 0 * * * ? - -# -# Download Service Cleanup -# -download.cleaner.startDelayMilliseconds=3600000 -# 1 hour -download.cleaner.repeatIntervalMilliseconds=3600000 -download.cleaner.maxAgeMins=60 -# -1 or 0 for not using batches -download.cleaner.batchSize=1000 - -# you could set this to false for new installations greater then ACS 6.2 -# see MNT-20212 -download.cleaner.cleanAllSysDownloadFolders=true - -# -# Download Service Limits, in bytes -# -download.maxContentSize=2152852358 - -# Max size of view trashcan files -# -trashcan.MaxSize=1000 - -# -# Use bridge tables for caching authority evaluation. -# -authority.useBridgeTable=true - -# Limit the number of results from findAuthority query -authority.findAuthorityLimit=10000 - -# enable QuickShare - if false then the QuickShare-specific REST APIs will return 403 Forbidden -system.quickshare.enabled=true -system.quickshare.email.from.default=noreply@alfresco.com -# By default the difference between the quick share expiry date and the current time must be at least 1 day (24 hours). -# However, this can be changed to at least 1 hour or 1 minute for testing purposes. For example, -# setting the value to MINUTES, means the service will calculate the difference between NOW and the given expiry date -# in terms of minutes and checks for the difference to be greater than 1 minute. -# DAYS | HOURS | MINUTES -system.quickshare.expiry_date.enforce.minimum.period=DAYS - -# Oubound Mail -mail.service.corePoolSize=8 -mail.service.maximumPoolSize=20 - -nodes.bulkLoad.cachingThreshold=10 - -# Multi-Tenancy - -# if "dir.contentstore.tenants" is set then -# tenants are not co-mingled and all content roots will appear below this container (in sub-folder) -# and when creating a tenant the "contentRootPath" (root content store directory for a given tenant) will be ignored -dir.contentstore.tenants= - -# Gateway Authentication -# gateway authentication is disabled if empty host is specified -alfresco.authentication.gateway.host= -alfresco.authentication.gateway.protocol=https -alfresco.authentication.gateway.port=443 -alfresco.authentication.gateway.outboundHeaders=Authorization,key -alfresco.authentication.gateway.inboundHeaders=X-Alfresco-Authenticator-Key,X-Alfresco-Remote-User -alfresco.authentication.gateway.prefixUrl=/publicapi -alfresco.authentication.gateway.bufferSize=2048 -alfresco.authentication.gateway.connectTimeout=10000 -alfresco.authentication.gateway.readTimeout=120000 -alfresco.authentication.gateway.httpTcpNodelay=true -alfresco.authentication.gateway.httpConnectionStalecheck=true - -# webscripts config -webscripts.encryptTempFiles=false -webscripts.tempDirectoryName=Alfresco-WebScripts -# 4mb -webscripts.memoryThreshold=4194304 -# 4gb -webscripts.setMaxContentSize=5368709120 - -# Property to enable index upgrade for metadata query (MDQ) -# -# The indexes are not added unless this value is changed -# Adding each the supporting indexes may take several hours depending on the size of the database. -# The required indexes may be added in stages. -# See: classpath:alfresco/dbscripts/upgrade/4.2/${db.script.dialect}/metadata-query-indexes.sql -# See: classpath:alfresco/dbscripts/upgrade/5.1/${db.script.dialect}/metadata-query-indexes-2.sql -system.metadata-query-indexes.ignored=true -system.metadata-query-indexes-more.ignored=true - -# -# Do we defer running the shared folder patch? -# -system.patch.sharedFolder.deferred=false -# Default value is run new years day 2030 i.e. not run. -system.patch.sharedFolder.cronExpression=0 0 0 ? 1 1 2030 - -# -# Default values for deferring the running of the addUnmovableAspect patch -# -system.patch.addUnmovableAspect.deferred=false -system.patch.addUnmovableAspect.cronExpression=0 0 0 ? 1 1 2030 - -# Property to enable removal of all JBPM related data from the database -# -# The tables are not removed from the databasen unless explicitly requested by setting this property to false. -# See: classpath:alfresco/dbscripts/upgrade/5.2/${db.script.dialect}/remove-jbpm-tables-from-db.sql -system.remove-jbpm-tables-from-db.ignored=true - -# -# Use a canned query when requested to search for people if " [hint:useCQ]" is provided in search term -# -people.search.honor.hint.useCQ=true - -# Delays cron jobs after bootstrap to allow server to fully come up before jobs start -system.cronJob.startDelayMilliseconds=60000 - -# Schedule for reading mimetype config definitions dynamically. Initially checks every 10 seconds and then switches to -# every hour after the configuration is read successfully. If there is a error later reading the config, the -# checks return to every 10 seconds. -mimetype.config.cronExpression=0 30 0/1 * * ? -mimetype.config.initialAndOnError.cronExpression=0/10 * * * * ? - -# Optional property to specify an external file or directory that will be read for mimetype definitions from YAML -# files (possibly added to a volume via k8 ConfigMaps). -mimetype.config.dir=shared/classes/alfresco/extension/mimetypes - -# Schedule for reading rendition config definitions dynamically. Initially checks every 10 seconds and then switches to -# every hour after the configuration is read successfully. If there is a error later reading the config, the -# checks return to every 10 seconds. -rendition.config.cronExpression=2 30 0/1 * * ? -rendition.config.initialAndOnError.cronExpression=0/10 * * * * ? - -# Optional property to specify an external file or directory that will be read for rendition definitions from YAML -# files (possibly added to a volume via k8 ConfigMaps). -rendition.config.dir=shared/classes/alfresco/extension/transform/renditions - -# Optional property to specify an external file or directory that will be read for transformer json config. -local.transform.pipeline.config.dir=shared/classes/alfresco/extension/transform/pipelines - -# Used to disable transforms locally. -local.transform.service.enabled=true - -# Schedule for reading local transform config, so that T-Engines and local pipeline config is dynamically -# picked up, or reintegrated after an outage. Initially checks every 10 seconds and then switches to every hour -# after the configuration is read successfully. If there is a error later reading the config, the checks return to -# every 10 seconds. -local.transform.service.cronExpression=4 30 0/1 * * ? -local.transform.service.initialAndOnError.cronExpression=0/10 * * * * ? - -# -# Check that the declared mimetype (of the Node) is the same as the derived -# mimetype of the content (via Tika) before a transformation takes place. -# Only files in the repository (not intermediate files in a transformer -# pipeline) are checked. This property provides a trade off between a -# security check and a relatively expensive (Tika) operation. -# -# There are a few issues with the Tika mimetype detection. So that transformations -# still take place where the detected mimetype is not the same as the declared mimetype, -# another property (transformer.strict.mimetype.check.whitelist.mimetypes) contains pairs -# of declared and detected mimetypes that should be allowed. This parameter value is a -# sequence of ; separated pairs. The declared and derived mimetypes are also ; separated. -# -transformer.strict.mimetype.check=true - -# A white list of declared and detected mimetypes, that don't match, but should still be transformed. -transformer.strict.mimetype.check.whitelist.mimetypes=application/eps;application/postscript;application/illustrator;application/pdf;application/x-tar;application/x-gtar;application/acp;application/zip;application/vnd.stardivision.math;application/x-tika-msoffice - -# -# Enable transformation retrying if the file has MIME type differ than file extension. -# Ignored if transformer.strict.mimetype.check is true as these transformations -# will not take place. -# -content.transformer.retryOn.different.mimetype=true - -# Debug and Log buffer sizes -transformer.debug.entries=0 -transformer.log.entries=50 - -# -# Lock timeout configuration -# -system.lockTryTimeout=100 -system.lockTryTimeout.DictionaryDAOImpl=10000 -system.lockTryTimeout.MessageServiceImpl=${system.lockTryTimeout} -system.lockTryTimeout.PolicyComponentImpl=${system.lockTryTimeout} - - -# Scheduled job to clean up unused properties from the alf_prop_xxx tables. -# Default setting of "0 0 3 ? * SAT" is to run every Saturday at 3am. -attributes.propcleaner.cronExpression=0 0 3 ? * SAT - -# Control Alfresco JMX connectivity -alfresco.jmx.connector.enabled=false - -# Dissallow Attribute Service Entries with "Serializable" objects in key Segments -# Please, see MNT-11895 for details. -system.propval.uniquenessCheck.enabled=true - -# Requests for ephemeral (in-memory) locks with expiry times (in seconds) greater -# than this value will result in persistent locks being created instead. By default -# this value is equal to the maximum allowed expiry for ephemeral locks, therefore -# this feature is disabled by default. Setting this to -1 would mean that ALL -# requests for ephemeral locks would result in persistent locks being created. -alfresco.ephemeralLock.expiryThresh=172800 - -# SurfConfigFolder Patch -# -# Do we defer running the surf-config folder patch? -# -system.patch.surfConfigFolder.deferred=false -# Default value. i.e. never run. It can be triggered using JMX -system.patch.surfConfigFolder.cronExpression=* * * * * ? 2099 - -# -# Solr Facets Config Properties -# -solr_facets.root.path=/app:company_home/app:dictionary -solr_facets.root=${solr_facets.root.path}/${spaces.solr_facets.root.childname} -solr_facets.inheritanceHierarchy=default,custom - -models.enforceTenantInNamespace=false - -# Allowed protocols for links -links.protocosl.white.list=http,https,ftp,mailto - -# Fixed ACLs -# Required for fixing MNT-15368 - Time Consumed for Updating Folder Permission -# ADMAccessControlListDAO.setFixedAcls called on a large folder hierarchy will take a long time for its execution. -# For this reason now method can also be called asynchronously if transaction reaches system.fixedACLs.maxTransactionTime. -# In this case setFixedAcls method recursion will be stopped and unfinished nodes will be marked with ASPECT_PENDING_FIX_ACL. -# Pending nodes will be processed by FixedAclUpdater, programmatically called but also configured as a scheduled job. -system.fixedACLs.maxTransactionTime=10000 -# fixedACLsUpdater - lock time to live -system.fixedACLsUpdater.lockTTL=10000 -# fixedACLsUpdater - maximum number of nodes to process per execution -system.fixedACLsUpdater.maxItemBatchSize=100 -# fixedACLsUpdater - the number of threads to use -system.fixedACLsUpdater.numThreads=4 -# fixedACLsUpdater - Force shared ACL to propagate through children even if there is an unexpected ACL -system.fixedACLsUpdater.forceSharedACL=false -# fixedACLsUpdater cron expression - fire at midnight every day -system.fixedACLsUpdater.cronExpression=0 0 0 * * ? - -cmis.disable.hidden.leading.period.files=false - -#Smart Folders Config Properties -smart.folders.enabled=false - -#Smart reference config -smart.reference.classpath.hash=${smart.folders.config.vanilla.processor.classpath}->1,${smart.folders.config.system.templates.classpath}->2 - -#Smart store config - -#Company home relative download associations of smart entries -smart.download.associations.folder=${spaces.dictionary.childname}/${spaces.smartdownloads.childname} - -#Generic virtualization methods config - -#Vanilla JSON templates javascript processor classpath. A java script processor used to -#covert JSON templates to internal smart folder definitions. - -smart.folders.config.vanilla.processor.classpath=/org/alfresco/repo/virtual/node/vanilla.js - -#System virtualization method config - -#System virtualization method aspect. -smart.folders.config.system.aspect=smf:systemConfigSmartFolder -#System virtualization method aspect defined template location property. -smart.folders.config.system.aspect.template.location.property=smf:system-template-location -#Classpath to be explored for *.json entries defining system templates. -smart.folders.config.system.templates.classpath=/org/alfresco/repo/virtual/node -#A company home relative name or qname path location of repository system templates. -smart.folders.config.system.templates.path=${spaces.dictionary.childname}/${spaces.smartfolders.childname} -#Content sub type of repository system templates. -smart.folders.config.system.templates.template.type=smf:smartFolderTemplate - -#Custom virtualization method config - -#Custom virtualization method aspect. -smart.folders.config.custom.aspect=smf:customConfigSmartFolder -#Custom virtualization method aspect template content association. -smart.folders.config.custom.aspect.template.association=smf:custom-template-association - - -#Type virtualization method config - -#A company home relative name or qname path location of the type mapped templates. -smart.folders.config.type.templates.path=${spaces.dictionary.childname}/${spaces.smartfolders.childname} -#Type and aspect qname regular expression filter. -smart.folders.config.type.templates.qname.filter=none - -# Preferred password encoding, md4, sha256, bcrypt10 -system.preferred.password.encoding=md4 - -# Upgrade Password Hash Job -system.upgradePasswordHash.jobBatchSize=100 -system.upgradePasswordHash.jobQueryRange=10000 -system.upgradePasswordHash.jobThreadCount=4 -system.upgradePasswordHash.jobCronExpression=* * * * * ? 2099 - -system.api.discovery.enabled=true - -# Maximum query size for category/tag fetch when not explicitly set by paging parameters -category.queryFetchSize=5000 - -# Brute force protection -authentication.protection.enabled=true -authentication.protection.limit=10 -authentication.protection.periodSeconds=6 - -system.email.sender.default=noreply@alfresco.com -# reset password workflow will expire in an hour -system.reset-password.endTimer=PT1H -system.reset-password.sendEmailAsynchronously=true - -# HeartBeat -heartbeat.target.url= -heartbeat.enabled=true - -# CSRF filter overrides -csrf.filter.enabled=true -csrf.filter.referer= -csrf.filter.referer.always=false -csrf.filter.origin= -csrf.filter.origin.always=false - -# CORS settings -cors.enabled=false -cors.allowed.origins= -cors.allowed.methods=GET,POST,HEAD,OPTIONS,PUT,DELETE -cors.allowed.headers=Authorization,Content-Type,Cache-Control,X-Requested-With,accept,Origin,Access-Control-Request-Method,Access-Control-Request-Headers,X-CSRF-Token -cors.exposed.headers=Access-Control-Allow-Origin,Access-Control-Allow-Credentials -cors.support.credentials=true -cors.preflight.maxage=10 - -# Alfresco Rest Api-Explorer -api-explorer.url= - -# Events subsystem -events.subsystem.autoStart=false -# Messaging subsystem -messaging.subsystem.autoStart=true - - -# Raw events -acs.repo.rendition.events.endpoint=jms:acs-repo-rendition-events?jmsMessageType=Text - -# Transform request events -acs.repo.transform.request.endpoint=jms:acs-repo-transform-request?jmsMessageType=Text - -# If enabled doesn't allow to set content properties via NodeService -contentPropertyRestrictions.enabled=true -contentPropertyRestrictions.whitelist= - -# Repo events2 -# Type and aspect filters which should be excluded -# Note: System folders node types are added by default -repo.event2.filter.nodeTypes=sys:*, fm:*, cm:thumbnail, cm:failedThumbnail, cm:rating, rma:rmsite include_subtypes -repo.event2.filter.nodeAspects=sys:* -repo.event2.filter.childAssocTypes=rn:rendition -# Comma separated list of users which should be excluded -# Note: username's case-sensitivity depends on the {user.name.caseSensitive} setting -repo.event2.filter.users= -# Topic name -repo.event2.topic.endpoint=amqp:topic:alfresco.repo.event2 -# Thread pool for async enqueue of repo events -repo.event2.queue.enqueueThreadPool.priority=1 -repo.event2.queue.enqueueThreadPool.coreSize=8 -repo.event2.queue.enqueueThreadPool.maximumSize=10 -# Thread pool for async dequeue and delivery of repo events -repo.event2.queue.dequeueThreadPool.priority=1 -repo.event2.queue.dequeueThreadPool.coreSize=1 -repo.event2.queue.dequeueThreadPool.maximumSize=1 - - -# MNT-21083 -# --DELETE_NOT_EXISTS - default settings -system.delete_not_exists.batchsize=100000 -system.delete_not_exists.delete_batchsize=1000 -system.delete_not_exists.read_only=false -system.delete_not_exists.timeout_seconds=-1 -system.prop_table_cleaner.algorithm=V2 - -# Configure the expiration time of the direct access url. This is the length of time in seconds that the link is valid for. -# Note: It is up to the actual ContentStore implementation if it can fulfil this request or not. -alfresco.content.directAccessUrl.lifetimeInSec=300 - -# Creates additional indexes on alf_node and alf_transaction. Recommended for large repositories. -system.new-node-transaction-indexes.ignored=true - -# Allows the configuration of maximum limits of the temp files to be deleted or the maximum time allowed to run for the job -system.tempFileCleaner.maxFilesToDelete= -system.tempFileCleaner.maxTimeToRun= - -# Property to long running migration to remove alf_server in v7+ patch.db-V7.1.0-remove-alf_server-table -system.remove-alf_server-table-from-db.ignored=true +# Repository configuration + +repository.name=Main Repository + +# Schema number +version.schema=16000 + +# Directory configuration + +dir.root=./alf_data + +dir.contentstore=${dir.root}/contentstore +dir.contentstore.deleted=${dir.root}/contentstore.deleted +dir.contentstore.bucketsPerMinute=0 + +# ContentStore subsystem: default choice +filecontentstore.subsystem.name=unencryptedContentStore + +# The location of cached content +dir.cachedcontent=${dir.root}/cachedcontent + +# The value for the maximum permitted size in bytes of all content. +# No value (or a negative long) will be taken to mean that no limit should be applied. +# See content-services-context.xml +system.content.maximumFileSizeLimit= + +# +# The server mode. Set value in alfresco-global.properties +# UNKNOWN | TEST | BACKUP | PRODUCTION +# +system.serverMode=UNKNOWN + +# The location for lucene index files +dir.indexes=${dir.root}/lucene-indexes + +# The location for index backups +dir.indexes.backup=${dir.root}/backup-lucene-indexes + +# The location for lucene index locks +dir.indexes.lock=${dir.indexes}/locks + +#Directory to find external license +dir.license.external=. +# Spring resource location of external license files +location.license.external=file://${dir.license.external}/*.lic +# Spring resource location of embedded license files +location.license.embedded=/WEB-INF/alfresco/license/*.lic +# Spring resource location of license files on shared classpath +location.license.shared=classpath*:/alfresco/extension/license/*.lic + +# WebDAV initialization properties +system.webdav.servlet.enabled=true +system.webdav.url.path.prefix= +system.webdav.storeName=${protocols.storeName} +system.webdav.rootPath=${protocols.rootPath} +# File name patterns that trigger rename shuffle detection +# pattern is used by move - tested against full path after it has been lower cased. +system.webdav.renameShufflePattern=(.*/\\..*)|(.*[a-f0-9]{8}+$)|(.*\\.tmp$)|(.*atmp[0-9]+$)|(.*\\.wbk$)|(.*\\.bak$)|(.*\\~$)|(.*backup.*\\.do[ct]{1}[x]?[m]?$)|(.*\\.sb\\-\\w{8}\\-\\w{6}$) +system.webdav.activities.enabled=false + + +system.workflow.jbpm.comment.property.max.length=-1 +system.workflow.comment.property.max.length=4000 + +#Determines if Activiti definitions are visible +system.workflow.engine.activiti.definitions.visible=true + + +# Determines if the Activiti engine is enabled +system.workflow.engine.activiti.enabled=true +system.workflow.engine.activiti.idblocksize=100 +system.workflow.engine.activiti.taskvariableslimit=20000 + +# Determines if the workflows that are deployed to the activiti engine should +# be deployed in the tenant-context of the thread IF the tenant-service is enabled +# If set to false, all workflows deployed will be shared among tenants. Recommended +# setting is true unless there is a good reason to not allow deploy tenant-specific +# worklfows when a MT-environment is set up. +system.workflow.deployWorkflowsInTenant=true +#Determines if historic process instance are retained in case of canceling a process instance +system.workflow.engine.activiti.retentionHistoricProcessInstance=false + +# The maximum number of groups to check for pooled tasks. For performance +# reasons, this is limited to 500 by default. +system.workflow.maxAuthoritiesForPooledTasks=500 + +# The maximum number of pooled tasks to return in a query. It may be necessary +# to limit this depending on UI limitations. +system.workflow.maxPooledTasks=-1 + +# The maximum number of reviewers for "Group Review and Approve" workflow. +# Use '0' for unlimited. +system.workflow.maxGroupReviewers=0 + +index.subsystem.name=noindex + +# ######################################### # +# Index Tracking Configuration # +# ######################################### # +# +# Index tracking information of a certain age is cleaned out by a scheduled job. +# Any clustered system that has been offline for longer than this period will need to be seeded +# with a more recent backup of the Lucene indexes or the indexes will have to be fully rebuilt. +# Use -1 to disable purging. This can be switched on at any stage. +index.tracking.minRecordPurgeAgeDays=30 +# Unused transactions will be purged in chunks determined by commit time boundaries. 'index.tracking.purgeSize' specifies the size +# of the chunk (in ms). Default is a couple of hours. +index.tracking.purgeSize=7200000 + +# Change the failure behaviour of the configuration checker +system.bootstrap.config_check.strict=true + + +# +# How long should shutdown wait to complete normally before +# taking stronger action and calling System.exit() +# in ms, 10,000 is 10 seconds +# +shutdown.backstop.timeout=10000 +shutdown.backstop.enabled=false + +# Server Single User Mode +# note: +# only allow named user (note: if blank or not set then will allow all users) +# assuming maxusers is not set to 0 +#server.singleuseronly.name=admin + +# Server Max Users - limit number of users with non-expired tickets +# note: +# -1 allows any number of users, assuming not in single-user mode +# 0 prevents further logins, including the ability to enter single-user mode +server.maxusers=-1 + +# +# Disable all shared caches (mutable and immutable) +# These properties are used for diagnostic purposes +system.cache.disableMutableSharedCaches=false +system.cache.disableImmutableSharedCaches=false + +# The maximum capacity of the parent assocs cache (the number of nodes whose parents can be cached) +system.cache.parentAssocs.maxSize=130000 + +# The average number of parents expected per cache entry. This parameter is multiplied by the above +# value to compute a limit on the total number of cached parents, which will be proportional to the +# cache's memory usage. The cache will be pruned when this limit is exceeded to avoid excessive +# memory usage. +system.cache.parentAssocs.limitFactor=8 + +# +# Properties to limit resources spent on individual searches +# +# The maximum time spent pruning results +system.acl.maxPermissionCheckTimeMillis=10000 +# The maximum number of search results to perform permission checks against +system.acl.maxPermissionChecks=1000 +system.acl.maxPermissionCheckEnabled=false + +# The maximum number of filefolder list results +system.filefolderservice.defaultListMaxResults=5000 +# DEPRECATED: Use 'system.auditableData.preserve' +system.preserve.modificationData=false +# The default to preserve all cm:auditable data on a node when the process is not directly driven by a user action +system.auditableData.preserve=${system.preserve.modificationData} +# Specific control of how the FileFolderService treats cm:auditable data when performing moves +system.auditableData.FileFolderService=${system.auditableData.preserve} +# Specific control of whether ACL changes on a node trigger the cm:auditable aspect +system.auditableData.ACLs=${system.auditableData.preserve} + +# Properties to control read permission evaluation for acegi +system.readpermissions.optimise=true +system.readpermissions.bulkfetchsize=1000 + +# +# Manually control how the system handles maximum string lengths. +# Any zero or negative value is ignored. +# Only change this after consulting support or reading the appropriate Javadocs for +# org.alfresco.repo.domain.schema.SchemaBootstrap for V2.1.2. +# Before database migration, the string value storage may need to be adjusted using the scheduled job +system.maximumStringLength=-1 +system.maximumStringLength.jobCronExpression=* * * * * ? 2099 +system.maximumStringLength.jobQueryRange=10000 +system.maximumStringLength.jobThreadCount=4 + +# +# Limit hibernate session size by trying to amalgamate events for the L2 session invalidation +# - hibernate works as is up to this size +# - after the limit is hit events that can be grouped invalidate the L2 cache by type and not instance +# events may not group if there are post action listener registered (this is not the case with the default distribution) +system.hibernateMaxExecutions=20000 + +# +# Determine if modification timestamp propagation from child to parent nodes is respected or not. +# Even if 'true', the functionality is only supported for child associations that declare the +# 'propagateTimestamps' element in the dictionary definition. +system.enableTimestampPropagation=true + +# +# Enable system model integrity checking. +# WARNING: Changing this is unsupported; bugs may corrupt data +system.integrity.enabled=true +# Do integrity violations fail transactions +# WARNING: Changing this is unsupported; bugs may corrupt data +system.integrity.failOnViolation=true +# The number of errors to report when violations are detected +system.integrity.maxErrorsPerTransaction=5 +# Add call stacks to integrity events so that errors are logged with possible causes +# WARNING: This is expensive and should only be switched on for diagnostic purposes +system.integrity.trace=false + +# +# Decide if content should be removed from the system immediately after being orphaned. +# Do not change this unless you have examined the impact it has on your backup procedures. +system.content.eagerOrphanCleanup=false +# The number of days to keep orphaned content in the content stores. +# This has no effect on the 'deleted' content stores, which are not automatically emptied. +system.content.orphanProtectDays=14 +# The action to take when a store or stores fails to delete orphaned content +# IGNORE: Just log a warning. The binary remains and the record is expunged +# KEEP_URL: Log a warning and create a URL entry with orphan time 0. It won't be processed or removed. +system.content.deletionFailureAction=IGNORE +# The CRON expression to trigger the deletion of resources associated with orphaned content. +system.content.orphanCleanup.cronExpression=0 0 4 * * ? +# The batch size user by the content store cleaner +system.content.cleanerBatchSize=1000 + +# The CRON expression to trigger the cleanup of deleted nodes and dangling transactions that are old enough +system.nodeServiceCleanup.cronExpression=0 0 21 * * ? + +# When transforming archive files (.zip etc) into text representations (such as +# for full text indexing), should the files within the archive be processed too? +# If enabled, transformation takes longer, but searches of the files find more. +transformer.Archive.includeContents=false + +# Database configuration +db.schema.name= +db.schema.stopAfterSchemaBootstrap=false +db.schema.update=true +db.schema.update.lockRetryCount=24 +db.schema.update.lockRetryWaitSeconds=5 +db.driver=org.gjt.mm.mysql.Driver +db.name=alfresco +db.url=jdbc:mysql:///${db.name} +db.username=alfresco +db.password=alfresco +db.pool.initial=10 +db.pool.max=275 +db.txn.isolation=-1 +db.pool.statements.enable=true +db.pool.statements.max=40 +db.pool.min=10 +db.pool.idle=10 +db.pool.wait.max=5000 + +db.pool.validate.query= +db.pool.evict.interval=600000 +db.pool.evict.idle.min=1800000 +# +# note: for 'db.pool.evict.num.tests' see http://commons.apache.org/dbcp/configuration.html (numTestsPerEvictionRun) +# and also following extract from "org.apache.commons.pool.impl.GenericKeyedObjectPool" (1.5.5) +# +# * The number of objects to examine during each run of the idle object evictor thread (if any). +# * When a negative value is supplied, ceil({@link #getNumIdle})/abs({@link #getNumTestsPerEvictionRun}) +# * tests will be run. I.e., when the value is -n, roughly one nth of the +# * idle objects will be tested per run. +# +db.pool.evict.num.tests=-1 + +db.pool.evict.validate=false +db.pool.validate.borrow=true +db.pool.validate.return=false + +db.pool.abandoned.detect=false +db.pool.abandoned.time=300 +# +# db.pool.abandoned.log=true (logAbandoned) adds overhead (http://commons.apache.org/dbcp/configuration.html) +# and also requires db.pool.abandoned.detect=true (removeAbandoned) +# +db.pool.abandoned.log=false + + +# Audit configuration +audit.enabled=true +audit.tagging.enabled=true +audit.alfresco-access.enabled=false +audit.alfresco-access.sub-actions.enabled=false +audit.cmischangelog.enabled=false +audit.dod5015.enabled=false +# Setting this flag to true will force startup failure when invalid audit configurations are detected +audit.config.strict=false +# Audit map filter for AccessAuditor - restricts recorded events to user driven events +audit.filter.alfresco-access.default.enabled=false +audit.filter.alfresco-access.transaction.user=~System;~null;.* +audit.filter.alfresco-access.transaction.type=cm:folder;cm:content;st:site +audit.filter.alfresco-access.transaction.path=~/sys:archivedItem;~/ver:;.* + + +# System Configuration +system.store=system://system +system.descriptor.childname=sys:descriptor +system.descriptor.current.childname=sys:descriptor-current + +# User config +alfresco_user_store.store=user://alfrescoUserStore +alfresco_user_store.system_container.childname=sys:system +alfresco_user_store.user_container.childname=sys:people + +# note: default admin username - should not be changed after installation +alfresco_user_store.adminusername=admin + +# Initial password - editing this will not have any effect once the repository is installed +alfresco_user_store.adminpassword=209c6174da490caeb422f3fa5a7ae634 + +# note: default guest username - should not be changed after installation +alfresco_user_store.guestusername=guest + +# Used to move home folders to a new location +home_folder_provider_synchronizer.enabled=false +home_folder_provider_synchronizer.override_provider= +home_folder_provider_synchronizer.keep_empty_parents=false + +# Spaces Archive Configuration +spaces.archive.store=archive://SpacesStore + +# Spaces Configuration +spaces.store=workspace://SpacesStore +spaces.company_home.childname=app:company_home +spaces.guest_home.childname=app:guest_home +spaces.dictionary.childname=app:dictionary +spaces.templates.childname=app:space_templates +spaces.imap_attachments.childname=cm:Imap Attachments +spaces.imap_home.childname=cm:Imap Home +spaces.imapConfig.childname=app:imap_configs +spaces.imap_templates.childname=app:imap_templates +spaces.scheduled_actions.childname=cm:Scheduled Actions +spaces.emailActions.childname=app:email_actions +spaces.searchAction.childname=cm:search +spaces.templates.content.childname=app:content_templates +spaces.templates.email.childname=app:email_templates +spaces.templates.email.invite1.childname=app:invite_email_templates +spaces.templates.email.notify.childname=app:notify_email_templates +spaces.templates.email.following.childname=app:following +spaces.templates.rss.childname=app:rss_templates +spaces.savedsearches.childname=app:saved_searches +spaces.scripts.childname=app:scripts +spaces.content_forms.childname=app:forms +spaces.user_homes.childname=app:user_homes +spaces.user_homes.regex.key=userName +spaces.user_homes.regex.pattern= +spaces.user_homes.regex.group_order= +spaces.sites.childname=st:sites +spaces.templates.email.invite.childname=cm:invite +spaces.templates.email.activities.childname=cm:activities +spaces.rendition.rendering_actions.childname=app:rendering_actions +spaces.replication.replication_actions.childname=app:replication_actions +spaces.transfers.childname=app:transfers +spaces.transfer_groups.childname=app:transfer_groups +spaces.transfer_temp.childname=app:temp +spaces.inbound_transfer_records.childname=app:inbound_transfer_records +spaces.webscripts.childname=cm:webscripts +spaces.extension_webscripts.childname=cm:extensionwebscripts +spaces.models.childname=app:models +spaces.workflow.definitions.childname=app:workflow_defs +spaces.templates.email.workflowemailnotification.childname=cm:workflownotification +spaces.nodetemplates.childname=app:node_templates +spaces.shared.childname=app:shared +spaces.solr_facets.root.childname=srft:facets +spaces.smartfolders.childname=app:smart_folders +spaces.smartdownloads.childname=app:smart_downloads +spaces.transfer_summary_report.location=/${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.transfers.childname}/${spaces.inbound_transfer_records.childname} +spaces.quickshare.link_expiry_actions.childname=app:quick_share_link_expiry_actions + + +# ADM VersionStore Configuration +version.store.initialVersion=true +version.store.enableAutoVersioning=true +version.store.enableAutoVersionOnUpdateProps=false +version.store.deprecated.lightWeightVersionStore=workspace://lightWeightVersionStore +version.store.version2Store=workspace://version2Store + +# Optional Comparator class name to sort versions. +# Set to: org.alfresco.repo.version.common.VersionLabelComparator +# if upgrading from a version that used unordered sequences in a cluster. +version.store.versionComparatorClass= + +# Folders for storing people +system.system_container.childname=sys:system +system.people_container.childname=sys:people +system.authorities_container.childname=sys:authorities +system.zones_container.childname=sys:zones + +# Folders for storing workflow related info +system.workflow_container.childname=sys:workflow + +# Folder for storing shared remote credentials +system.remote_credentials_container.childname=sys:remote_credentials + +# Folder for storing syncset definitions +system.syncset_definition_container.childname=sys:syncset_definitions + +# Folder for storing download archives +system.downloads_container.childname=sys:downloads + +# Folder for storing IdP's certificate definitions +system.certificate_container.childname=sys:samlcertificate + +# Are user names case sensitive? +user.name.caseSensitive=false +domain.name.caseSensitive=false +domain.separator= + +#Format caption extracted from the XML Schema. +xforms.formatCaption=true + +# ECM content usages/quotas +system.usages.enabled=false +system.usages.clearBatchSize=0 +system.usages.updateBatchSize=50 + +# Repository endpoint - used by Activity Service +repo.remote.endpoint=/service + +# Some authentication mechanisms may need to create people +# in the repository on demand. This enables that feature. +# If disabled an error will be generated for missing +# people. If enabled then a person will be created and +# persisted. +create.missing.people=${server.transaction.allow-writes} + +# Create home folders (unless disabled, see next property) as people are created (true) or create them lazily (false) +home.folder.creation.eager=true +# Disable home folder creation - if true then home folders are not created (neither eagerly nor lazily) +home.folder.creation.disabled=false + +# Should we consider zero byte content to be the same as no content when firing +# content update policies? Prevents 'premature' firing of inbound content rules +# for some clients such as Mac OS X Finder +policy.content.update.ignoreEmpty=true + +# Default value of alfresco.rmi.services.host is 0.0.0.0 which means 'listen on all adapters'. +# This allows connections to JMX both remotely and locally. +# +alfresco.rmi.services.port=50500 +alfresco.rmi.services.external.host=localhost +alfresco.rmi.services.host=0.0.0.0 + +# If the RMI address is in-use, how many retries should be done before aborting +# Default value of alfresco.rmi.services.retries is 0 which means 'Don't retry if the address is in-use' +alfresco.rmi.services.retries=4 +# How long in milliseconds to wait after a failed server socket bind, before retrying +alfresco.rmi.services.retryInterval=250 + +# RMI service ports for the individual services. +# These eight services are available remotely. +# +# Assign individual ports for each service for best performance +# or run several services on the same port, you can even run everything on 50500 if +# running through a firewall. +# +# Specify 0 to use a random unused port. +# +monitor.rmi.service.port=50508 + +# +# enable or disable individual RMI services +# +monitor.rmi.service.enabled=false + + +# Should the Mbean server bind to an existing server. Set to true for most application servers. +# false for WebSphere clusters. +mbean.server.locateExistingServerIfPossible=true + +# Rendition Service 2 +renditionService2.enabled=true + +# Thumbnail Service +system.thumbnail.generate=true + +# when creating doc via CMIS - optionally configure set of renditions names to request async +cmis.create.doc.request.renditions.set= + +# Default thumbnail limits +# When creating thumbnails, only use the first pageLimit pages +system.thumbnail.definition.default.timeoutMs=-1 +system.thumbnail.definition.default.readLimitTimeMs=-1 +system.thumbnail.definition.default.maxSourceSizeKBytes=-1 +system.thumbnail.definition.default.readLimitKBytes=-1 +system.thumbnail.definition.default.pageLimit=-1 +system.thumbnail.definition.default.maxPages=-1 + +# Max mimetype sizes to create thumbnail icons +system.thumbnail.mimetype.maxSourceSizeKBytes.pdf=-1 +system.thumbnail.mimetype.maxSourceSizeKBytes.txt=-1 +system.thumbnail.mimetype.maxSourceSizeKBytes.docx=-1 +system.thumbnail.mimetype.maxSourceSizeKBytes.xlsx=-1 +system.thumbnail.mimetype.maxSourceSizeKBytes.pptx=-1 +system.thumbnail.mimetype.maxSourceSizeKBytes.odt=-1 +system.thumbnail.mimetype.maxSourceSizeKBytes.ods=-1 +system.thumbnail.mimetype.maxSourceSizeKBytes.odp=-1 + +# Configuration for handling of failing thumbnails. +# See NodeEligibleForRethumbnailingEvaluator's javadoc for details. +# +# Retry periods limit the frequency with which the repository will attempt to create Share thumbnails +# for content nodes which have previously failed in their thumbnail attempts. +# These periods are in seconds. +# +# 604800s = 60s * 60m * 24h * 7d = 1 week +system.thumbnail.retryPeriod=60 +system.thumbnail.retryCount=2 +system.thumbnail.quietPeriod=604800 +system.thumbnail.quietPeriodRetriesEnabled=true +system.thumbnail.redeployStaticDefsOnStartup=true + +content.metadata.async.extract.enabled=true +content.metadata.async.embed.enabled=true + +# The default timeout for metadata mapping extracters +content.metadataExtracter.default.timeoutMs=20000 + +# Local transformer urls to T-engines to service transform requests via http. Enabled by default. +localTransform.core-aio.url=http://localhost:8090/ + +# When a local transformer .url is set, this value indicates the amount of time to wait after a connection failure +# before retrying the connection to allow a docker container to (re)start. +localTransform.core-aio.startupRetryPeriodSeconds=60 + +# Property to enable upgrade from 2.1-A +V2.1-A.fixes.to.schema=0 +#V2.1-A.fixes.to.schema=82 + +# The default authentication chain +authentication.chain=alfrescoNtlm1:alfrescoNtlm + +# Do authentication tickets expire or live for ever? +authentication.ticket.ticketsExpire=true + +# If ticketsEpire is true then how they should expire? +# Valid values are: AFTER_INACTIVITY, AFTER_FIXED_TIME, DO_NOT_EXPIRE +# The default is AFTER_FIXED_TIME +authentication.ticket.expiryMode=AFTER_INACTIVITY + +# If authentication.ticket.ticketsExpire is true and +# authentication.ticket.expiryMode is AFTER_FIXED_TIME or AFTER_INACTIVITY, +# this controls the minimum period for which tickets are valid. +# The default is PT1H for one hour. +authentication.ticket.validDuration=PT1H + +# Use one ticket for all user sessions +# For the pre 4.2 behaviour of one ticket per session set this to false. +authentication.ticket.useSingleTicketPerUser=true + +authentication.alwaysAllowBasicAuthForAdminConsole.enabled=true +authentication.getRemoteUserTimeoutMilliseconds=10000 + +# FTP access +ftp.enabled=false + +# Default root path for protocols +protocols.storeName=${spaces.store} +protocols.rootPath=/${spaces.company_home.childname} + +# OpenCMIS +opencmis.connector.default.store=${spaces.store} +opencmis.connector.default.rootPath=/${spaces.company_home.childname} +opencmis.connector.default.typesDefaultMaxItems=500 +opencmis.connector.default.typesDefaultDepth=-1 +opencmis.connector.default.objectsDefaultMaxItems=10000 +opencmis.connector.default.objectsDefaultDepth=100 +opencmis.connector.default.contentChangesDefaultMaxItems=10000 +opencmis.connector.default.openHttpSession=false +opencmis.activities.enabled=true +opencmis.bulkUpdateProperties.maxItemsSize=1000 +opencmis.bulkUpdateProperties.batchSize=20 +opencmis.bulkUpdateProperties.workerThreads=2 +opencmis.maxContentSizeMB=4096 +opencmis.memoryThresholdKB=4096 + +# URL generation overrides + +# if true, the context path of OpenCMIS generated urls will be set to "opencmis.context.value", otherwise it will be taken from the request url +opencmis.context.override=false +opencmis.context.value= +# if true, the servlet path of OpenCMIS generated urls will be set to "opencmis.servletpath.value", otherwise it will be taken from the request url +opencmis.servletpath.override=false +opencmis.servletpath.value= +opencmis.server.override=false +opencmis.server.value= + +# IMAP +imap.server.enabled=false +imap.server.port=143 +imap.server.attachments.extraction.enabled=true + +# Default IMAP mount points +imap.config.home.store=${spaces.store} +imap.config.home.rootPath=/${spaces.company_home.childname} +imap.config.home.folderPath=${spaces.imap_home.childname} +imap.config.server.mountPoints=AlfrescoIMAP +imap.config.server.mountPoints.default.mountPointName=IMAP +imap.config.server.mountPoints.default.modeName=ARCHIVE +imap.config.server.mountPoints.default.store=${spaces.store} +imap.config.server.mountPoints.default.rootPath=${protocols.rootPath} +imap.config.server.mountPoints.value.AlfrescoIMAP.mountPointName=Alfresco IMAP +imap.config.server.mountPoints.value.AlfrescoIMAP.modeName=MIXED + +#Imap extraction settings +#imap.attachments.mode: +# SEPARATE -- All attachments for each email will be extracted to separate folder. +# COMMON -- All attachments for all emails will be extracted to one folder. +# SAME -- Attachments will be extracted to the same folder where email lies. +imap.attachments.mode=SEPARATE +imap.attachments.folder.store=${spaces.store} +imap.attachments.folder.rootPath=/${spaces.company_home.childname} +imap.attachments.folder.folderPath=${spaces.imap_attachments.childname} + +# Activities Feed - refer to subsystem + +# Feed max ID range to limit maximum number of entries +activities.feed.max.idRange=1000000 +# Feed max size (number of entries) +activities.feed.max.size=200 +# Feed max age (eg. 44640 mins => 31 days) +activities.feed.max.ageMins=44640 + +activities.feed.generator.jsonFormatOnly=true +activities.feed.fetchBatchSize=250 +activities.feedNotifier.batchSize=200 +activities.feedNotifier.numThreads=2 + +# Subsystem unit test values. Will not have any effect on production servers +subsystems.test.beanProp.default.longProperty=123456789123456789 +subsystems.test.beanProp.default.anotherStringProperty=Global Default +subsystems.test.beanProp=inst1,inst2,inst3 +subsystems.test.beanProp.value.inst2.boolProperty=true +subsystems.test.beanProp.value.inst3.anotherStringProperty=Global Instance Default +subsystems.test.simpleProp2=true +subsystems.test.simpleProp3=Global Default3 + +# Default Async Action Thread Pool +default.async.action.threadPriority=1 +default.async.action.corePoolSize=8 +default.async.action.maximumPoolSize=20 + +# Deployment Service +deployment.service.numberOfSendingThreads=5 +deployment.service.corePoolSize=2 +deployment.service.maximumPoolSize=3 +deployment.service.threadPriority=5 +# How long to wait in mS before refreshing a target lock - detects shutdown servers +deployment.service.targetLockRefreshTime=60000 +# How long to wait in mS from the last communication before deciding that deployment has failed, possibly +# the destination is no longer available? +deployment.service.targetLockTimeout=3600000 +# Deployment method used to deploy this Alfresco instance (DEFAULT, INSTALLER, DOCKER_COMPOSE, HELM_CHART, ANSIBLE, ZIP, QUICK_START) +deployment.method=DEFAULT + +#Invitation Service +# Should send emails as part of invitation process. +notification.email.siteinvite=true +# Moderated invite Activiti workflow +site.invite.moderated.workflowId=activiti$activitiInvitationModerated +# Add intneral users Activiti workflow (use activiti$activitiInvitationNominated to revert to requiring accept of invite for internal users) +site.invite.nominated.workflowId=activiti$activitiInvitationNominatedAddDirect +# Add external users Activiti workflow +site.invite.nominatedExternal.workflowId=activiti$activitiInvitationNominated + +# Replication Service +replication.enabled=false + +# Transfer Service +transferservice.receiver.enabled=false +transferservice.receiver.stagingDir=${java.io.tmpdir}/alfresco-transfer-staging +# +# How long to wait in mS before refreshing a transfer lock - detects shutdown servers +# Default 1 minute. +transferservice.receiver.lockRefreshTime=60000 +# +# How many times to attempt retry the transfer lock +transferservice.receiver.lockRetryCount=3 +# How long to wait, in mS, before retrying the transfer lock +transferservice.receiver.lockRetryWait=100 +# +# How long to wait, in mS, since the last contact with from the client before +# timing out a transfer. Needs to be long enough to cope with network delays and "thinking +# time" for both source and destination. Default 5 minutes. +transferservice.receiver.lockTimeOut=300000 + +# OrphanReaper +orphanReaper.lockRefreshTime=60000 +orphanReaper.lockTimeOut=3600000 + + +# security +security.anyDenyDenies=true +# Whether to post-process denies. Only applies to solr4+ when anyDenyDenies is true. +security.postProcessDenies=false + +# +# Encryption properties +# +# default keystores location +dir.keystore=classpath:alfresco/keystore + +# general encryption parameters +encryption.keySpec.class=org.alfresco.encryption.DESEDEKeyGenerator +encryption.keyAlgorithm=AES +encryption.cipherAlgorithm=AES/CBC/PKCS5Padding + +# secret key keystore configuration +encryption.keystore.location=${dir.keystore}/keystore +# configuration via metadata is deprecated +encryption.keystore.keyMetaData.location= +encryption.keystore.provider= +encryption.keystore.type=pkcs12 + +# backup secret key keystore configuration +encryption.keystore.backup.location=${dir.keystore}/backup-keystore +# configuration via metadata is deprecated +encryption.keystore.backup.keyMetaData.location= +encryption.keystore.backup.provider= +encryption.keystore.backup.type=pkcs12 + +# Should encryptable properties be re-encrypted with new encryption keys on botstrap? +encryption.bootstrap.reencrypt=false + +# mac/md5 encryption +encryption.mac.messageTimeout=30000 +encryption.mac.algorithm=HmacSHA1 + +# ssl encryption +encryption.ssl.keystore.location=${dir.keystore}/ssl.keystore +encryption.ssl.keystore.provider= +encryption.ssl.keystore.type=JCEKS +# configuration via metadata is deprecated +encryption.ssl.keystore.keyMetaData.location= +encryption.ssl.truststore.location=${dir.keystore}/ssl.truststore +encryption.ssl.truststore.provider= +encryption.ssl.truststore.type=JCEKS +# configuration via metadata is deprecated +encryption.ssl.truststore.keyMetaData.location= + +# Re-encryptor properties +encryption.reencryptor.chunkSize=100 +encryption.reencryptor.numThreads=2 + +# SOLR connection details (e.g. for JMX) +solr.host=localhost +solr.port=8983 +solr.port.ssl=8984 +solr.solrUser=solr +solr.solrPassword=solr +# none, https +solr.secureComms=https +solr.sharedSecret= +solr.sharedSecret.header=X-Alfresco-Search-Secret +solr.cmis.alternativeDictionary=DEFAULT_DICTIONARY + +solr.max.total.connections=40 +solr.max.host.connections=40 + +# Solr connection timeouts +# solr connect timeout in ms +solr.solrConnectTimeout=5000 + +# cron expression defining how often the Solr Admin client (used by JMX) pings Solr if it goes away +solr.solrPingCronExpression=0 0/5 * * * ? * + + +#Default SOLR store mappings mappings +solr.store.mappings=solrMappingAlfresco,solrMappingArchive +solr.store.mappings.value.solrMappingAlfresco.httpClientFactory=solrHttpClientFactory +solr.store.mappings.value.solrMappingAlfresco.baseUrl=/solr/alfresco +solr.store.mappings.value.solrMappingAlfresco.protocol=workspace +solr.store.mappings.value.solrMappingAlfresco.identifier=SpacesStore +solr.store.mappings.value.solrMappingArchive.httpClientFactory=solrHttpClientFactory +solr.store.mappings.value.solrMappingArchive.baseUrl=/solr/archive +solr.store.mappings.value.solrMappingArchive.protocol=archive +solr.store.mappings.value.solrMappingArchive.identifier=SpacesStore + +#Default SOLR 4 store mappings mappings +solr4.store.mappings=solrMappingAlfresco,solrMappingArchive +solr4.store.mappings.value.solrMappingAlfresco.httpClientFactory=solrHttpClientFactory +solr4.store.mappings.value.solrMappingAlfresco.baseUrl=/solr4/alfresco +solr4.store.mappings.value.solrMappingAlfresco.protocol=workspace +solr4.store.mappings.value.solrMappingAlfresco.identifier=SpacesStore +solr4.store.mappings.value.solrMappingArchive.httpClientFactory=solrHttpClientFactory +solr4.store.mappings.value.solrMappingArchive.baseUrl=/solr4/archive +solr4.store.mappings.value.solrMappingArchive.protocol=archive +solr4.store.mappings.value.solrMappingArchive.identifier=SpacesStore + +#Default SOLR 6 store mappings mappings +solr6.store.mappings=solrMappingAlfresco,solrMappingArchive,solrMappingHistory +solr6.store.mappings.value.solrMappingAlfresco.httpClientFactory=solrHttpClientFactory +solr6.store.mappings.value.solrMappingAlfresco.baseUrl=/solr/alfresco +solr6.store.mappings.value.solrMappingAlfresco.protocol=workspace +solr6.store.mappings.value.solrMappingAlfresco.identifier=SpacesStore +solr6.store.mappings.value.solrMappingArchive.httpClientFactory=solrHttpClientFactory +solr6.store.mappings.value.solrMappingArchive.baseUrl=/solr/archive +solr6.store.mappings.value.solrMappingArchive.protocol=archive +solr6.store.mappings.value.solrMappingArchive.identifier=SpacesStore +solr6.store.mappings.value.solrMappingHistory.httpClientFactory=solrHttpClientFactory +solr6.store.mappings.value.solrMappingHistory.baseUrl=/solr/history +solr6.store.mappings.value.solrMappingHistory.protocol=workspace +solr6.store.mappings.value.solrMappingHistory.identifier=history + +# +# URL Shortening Properties +# +urlshortening.bitly.username=brianalfresco +urlshortening.bitly.api.key=R_ca15c6c89e9b25ccd170bafd209a0d4f +urlshortening.bitly.url.length=20 + +# +# Bulk Filesystem Importer +# + +# The number of threads to employ in a batch import +bulkImport.batch.numThreads=4 + +# The size of a batch in a batch import i.e. the number of files to import in a +# transaction/thread +bulkImport.batch.batchSize=20 + + +# +# Caching Content Store +# +system.content.caching.cacheOnInbound=true +system.content.caching.maxDeleteWatchCount=1 +# Clean up every day at 3 am +system.content.caching.contentCleanup.cronExpression=0 0 3 * * ? +system.content.caching.minFileAgeMillis=60000 +system.content.caching.maxUsageMB=4096 +# maxFileSizeMB - 0 means no max file size. +system.content.caching.maxFileSizeMB=0 +# When the CachingContentStore is about to write a cache file but the disk usage is in excess of panicThresholdPct +# (default 90%) then the cache file is not written and the cleaner is started (if not already running) in a new thread. +system.content.caching.panicThresholdPct=90 +# When a cache file has been written that results in cleanThresholdPct (default 80%) of maxUsageBytes +# being exceeded then the cached content cleaner is invoked (if not already running) in a new thread. +system.content.caching.cleanThresholdPct=80 +# An aggressive cleaner is run till the targetUsagePct (default 70%) of maxUsageBytes is achieved +system.content.caching.targetUsagePct=70 +# Threshold in seconds indicating a minimal gap between normal cleanup starts +system.content.caching.normalCleanThresholdSec=0 + +mybatis.useLocalCaches=false + +fileFolderService.checkHidden.enabled=true + + +ticket.cleanup.cronExpression=0 0 * * * ? + +# +# Download Service Cleanup +# +download.cleaner.startDelayMilliseconds=3600000 +# 1 hour +download.cleaner.repeatIntervalMilliseconds=3600000 +download.cleaner.maxAgeMins=60 +# -1 or 0 for not using batches +download.cleaner.batchSize=1000 + +# you could set this to false for new installations greater then ACS 6.2 +# see MNT-20212 +download.cleaner.cleanAllSysDownloadFolders=true + +# +# Download Service Limits, in bytes +# +download.maxContentSize=2152852358 + +# Max size of view trashcan files +# +trashcan.MaxSize=1000 + +# +# Use bridge tables for caching authority evaluation. +# +authority.useBridgeTable=true + +# Limit the number of results from findAuthority query +authority.findAuthorityLimit=10000 + +# enable QuickShare - if false then the QuickShare-specific REST APIs will return 403 Forbidden +system.quickshare.enabled=true +system.quickshare.email.from.default=noreply@alfresco.com +# By default the difference between the quick share expiry date and the current time must be at least 1 day (24 hours). +# However, this can be changed to at least 1 hour or 1 minute for testing purposes. For example, +# setting the value to MINUTES, means the service will calculate the difference between NOW and the given expiry date +# in terms of minutes and checks for the difference to be greater than 1 minute. +# DAYS | HOURS | MINUTES +system.quickshare.expiry_date.enforce.minimum.period=DAYS + +# Oubound Mail +mail.service.corePoolSize=8 +mail.service.maximumPoolSize=20 + +nodes.bulkLoad.cachingThreshold=10 + +# Multi-Tenancy + +# if "dir.contentstore.tenants" is set then +# tenants are not co-mingled and all content roots will appear below this container (in sub-folder) +# and when creating a tenant the "contentRootPath" (root content store directory for a given tenant) will be ignored +dir.contentstore.tenants= + +# Gateway Authentication +# gateway authentication is disabled if empty host is specified +alfresco.authentication.gateway.host= +alfresco.authentication.gateway.protocol=https +alfresco.authentication.gateway.port=443 +alfresco.authentication.gateway.outboundHeaders=Authorization,key +alfresco.authentication.gateway.inboundHeaders=X-Alfresco-Authenticator-Key,X-Alfresco-Remote-User +alfresco.authentication.gateway.prefixUrl=/publicapi +alfresco.authentication.gateway.bufferSize=2048 +alfresco.authentication.gateway.connectTimeout=10000 +alfresco.authentication.gateway.readTimeout=120000 +alfresco.authentication.gateway.httpTcpNodelay=true +alfresco.authentication.gateway.httpConnectionStalecheck=true + +# webscripts config +webscripts.encryptTempFiles=false +webscripts.tempDirectoryName=Alfresco-WebScripts +# 4mb +webscripts.memoryThreshold=4194304 +# 4gb +webscripts.setMaxContentSize=5368709120 + +# Property to enable index upgrade for metadata query (MDQ) +# +# The indexes are not added unless this value is changed +# Adding each the supporting indexes may take several hours depending on the size of the database. +# The required indexes may be added in stages. +# See: classpath:alfresco/dbscripts/upgrade/4.2/${db.script.dialect}/metadata-query-indexes.sql +# See: classpath:alfresco/dbscripts/upgrade/5.1/${db.script.dialect}/metadata-query-indexes-2.sql +system.metadata-query-indexes.ignored=true +system.metadata-query-indexes-more.ignored=true + +# +# Do we defer running the shared folder patch? +# +system.patch.sharedFolder.deferred=false +# Default value is run new years day 2030 i.e. not run. +system.patch.sharedFolder.cronExpression=0 0 0 ? 1 1 2030 + +# +# Default values for deferring the running of the addUnmovableAspect patch +# +system.patch.addUnmovableAspect.deferred=false +system.patch.addUnmovableAspect.cronExpression=0 0 0 ? 1 1 2030 + +# Property to enable removal of all JBPM related data from the database +# +# The tables are not removed from the databasen unless explicitly requested by setting this property to false. +# See: classpath:alfresco/dbscripts/upgrade/5.2/${db.script.dialect}/remove-jbpm-tables-from-db.sql +system.remove-jbpm-tables-from-db.ignored=true + +# +# Use a canned query when requested to search for people if " [hint:useCQ]" is provided in search term +# +people.search.honor.hint.useCQ=true + +# Delays cron jobs after bootstrap to allow server to fully come up before jobs start +system.cronJob.startDelayMilliseconds=60000 + +# Schedule for reading mimetype config definitions dynamically. Initially checks every 10 seconds and then switches to +# every hour after the configuration is read successfully. If there is a error later reading the config, the +# checks return to every 10 seconds. +mimetype.config.cronExpression=0 30 0/1 * * ? +mimetype.config.initialAndOnError.cronExpression=0/10 * * * * ? + +# Optional property to specify an external file or directory that will be read for mimetype definitions from YAML +# files (possibly added to a volume via k8 ConfigMaps). +mimetype.config.dir=shared/classes/alfresco/extension/mimetypes + +# Schedule for reading rendition config definitions dynamically. Initially checks every 10 seconds and then switches to +# every hour after the configuration is read successfully. If there is a error later reading the config, the +# checks return to every 10 seconds. +rendition.config.cronExpression=2 30 0/1 * * ? +rendition.config.initialAndOnError.cronExpression=0/10 * * * * ? + +# Optional property to specify an external file or directory that will be read for rendition definitions from YAML +# files (possibly added to a volume via k8 ConfigMaps). +rendition.config.dir=shared/classes/alfresco/extension/transform/renditions + +# Optional property to specify an external file or directory that will be read for transformer json config. +local.transform.pipeline.config.dir=shared/classes/alfresco/extension/transform/pipelines + +# Used to disable transforms locally. +local.transform.service.enabled=true + +# Schedule for reading local transform config, so that T-Engines and local pipeline config is dynamically +# picked up, or reintegrated after an outage. Initially checks every 10 seconds and then switches to every hour +# after the configuration is read successfully. If there is a error later reading the config, the checks return to +# every 10 seconds. +local.transform.service.cronExpression=4 30 0/1 * * ? +local.transform.service.initialAndOnError.cronExpression=0/10 * * * * ? + +# +# Check that the declared mimetype (of the Node) is the same as the derived +# mimetype of the content (via Tika) before a transformation takes place. +# Only files in the repository (not intermediate files in a transformer +# pipeline) are checked. This property provides a trade off between a +# security check and a relatively expensive (Tika) operation. +# +# There are a few issues with the Tika mimetype detection. So that transformations +# still take place where the detected mimetype is not the same as the declared mimetype, +# another property (transformer.strict.mimetype.check.whitelist.mimetypes) contains pairs +# of declared and detected mimetypes that should be allowed. This parameter value is a +# sequence of ; separated pairs. The declared and derived mimetypes are also ; separated. +# +transformer.strict.mimetype.check=true + +# A white list of declared and detected mimetypes, that don't match, but should still be transformed. +transformer.strict.mimetype.check.whitelist.mimetypes=application/eps;application/postscript;application/illustrator;application/pdf;application/x-tar;application/x-gtar;application/acp;application/zip;application/vnd.stardivision.math;application/x-tika-msoffice + +# +# Enable transformation retrying if the file has MIME type differ than file extension. +# Ignored if transformer.strict.mimetype.check is true as these transformations +# will not take place. +# +content.transformer.retryOn.different.mimetype=true + +# Debug and Log buffer sizes +transformer.debug.entries=0 +transformer.log.entries=50 + +# +# Lock timeout configuration +# +system.lockTryTimeout=100 +system.lockTryTimeout.DictionaryDAOImpl=10000 +system.lockTryTimeout.MessageServiceImpl=${system.lockTryTimeout} +system.lockTryTimeout.PolicyComponentImpl=${system.lockTryTimeout} + + +# Scheduled job to clean up unused properties from the alf_prop_xxx tables. +# Default setting of "0 0 3 ? * SAT" is to run every Saturday at 3am. +attributes.propcleaner.cronExpression=0 0 3 ? * SAT + +# Control Alfresco JMX connectivity +alfresco.jmx.connector.enabled=false + +# Dissallow Attribute Service Entries with "Serializable" objects in key Segments +# Please, see MNT-11895 for details. +system.propval.uniquenessCheck.enabled=true + +# Requests for ephemeral (in-memory) locks with expiry times (in seconds) greater +# than this value will result in persistent locks being created instead. By default +# this value is equal to the maximum allowed expiry for ephemeral locks, therefore +# this feature is disabled by default. Setting this to -1 would mean that ALL +# requests for ephemeral locks would result in persistent locks being created. +alfresco.ephemeralLock.expiryThresh=172800 + +# SurfConfigFolder Patch +# +# Do we defer running the surf-config folder patch? +# +system.patch.surfConfigFolder.deferred=false +# Default value. i.e. never run. It can be triggered using JMX +system.patch.surfConfigFolder.cronExpression=* * * * * ? 2099 + +# +# Solr Facets Config Properties +# +solr_facets.root.path=/app:company_home/app:dictionary +solr_facets.root=${solr_facets.root.path}/${spaces.solr_facets.root.childname} +solr_facets.inheritanceHierarchy=default,custom + +models.enforceTenantInNamespace=false + +# Allowed protocols for links +links.protocosl.white.list=http,https,ftp,mailto + +# Fixed ACLs +# Required for fixing MNT-15368 - Time Consumed for Updating Folder Permission +# ADMAccessControlListDAO.setFixedAcls called on a large folder hierarchy will take a long time for its execution. +# For this reason now method can also be called asynchronously if transaction reaches system.fixedACLs.maxTransactionTime. +# In this case setFixedAcls method recursion will be stopped and unfinished nodes will be marked with ASPECT_PENDING_FIX_ACL. +# Pending nodes will be processed by FixedAclUpdater, programmatically called but also configured as a scheduled job. +system.fixedACLs.maxTransactionTime=10000 +# fixedACLsUpdater - lock time to live +system.fixedACLsUpdater.lockTTL=10000 +# fixedACLsUpdater - maximum number of nodes to process per execution +system.fixedACLsUpdater.maxItemBatchSize=100 +# fixedACLsUpdater - the number of threads to use +system.fixedACLsUpdater.numThreads=4 +# fixedACLsUpdater - Force shared ACL to propagate through children even if there is an unexpected ACL +system.fixedACLsUpdater.forceSharedACL=false +# fixedACLsUpdater cron expression - fire at midnight every day +system.fixedACLsUpdater.cronExpression=0 0 0 * * ? + +cmis.disable.hidden.leading.period.files=false + +#Smart Folders Config Properties +smart.folders.enabled=false + +#Smart reference config +smart.reference.classpath.hash=${smart.folders.config.vanilla.processor.classpath}->1,${smart.folders.config.system.templates.classpath}->2 + +#Smart store config + +#Company home relative download associations of smart entries +smart.download.associations.folder=${spaces.dictionary.childname}/${spaces.smartdownloads.childname} + +#Generic virtualization methods config + +#Vanilla JSON templates javascript processor classpath. A java script processor used to +#covert JSON templates to internal smart folder definitions. + +smart.folders.config.vanilla.processor.classpath=/org/alfresco/repo/virtual/node/vanilla.js + +#System virtualization method config + +#System virtualization method aspect. +smart.folders.config.system.aspect=smf:systemConfigSmartFolder +#System virtualization method aspect defined template location property. +smart.folders.config.system.aspect.template.location.property=smf:system-template-location +#Classpath to be explored for *.json entries defining system templates. +smart.folders.config.system.templates.classpath=/org/alfresco/repo/virtual/node +#A company home relative name or qname path location of repository system templates. +smart.folders.config.system.templates.path=${spaces.dictionary.childname}/${spaces.smartfolders.childname} +#Content sub type of repository system templates. +smart.folders.config.system.templates.template.type=smf:smartFolderTemplate + +#Custom virtualization method config + +#Custom virtualization method aspect. +smart.folders.config.custom.aspect=smf:customConfigSmartFolder +#Custom virtualization method aspect template content association. +smart.folders.config.custom.aspect.template.association=smf:custom-template-association + + +#Type virtualization method config + +#A company home relative name or qname path location of the type mapped templates. +smart.folders.config.type.templates.path=${spaces.dictionary.childname}/${spaces.smartfolders.childname} +#Type and aspect qname regular expression filter. +smart.folders.config.type.templates.qname.filter=none + +# Preferred password encoding, md4, sha256, bcrypt10 +system.preferred.password.encoding=md4 + +# Upgrade Password Hash Job +system.upgradePasswordHash.jobBatchSize=100 +system.upgradePasswordHash.jobQueryRange=10000 +system.upgradePasswordHash.jobThreadCount=4 +system.upgradePasswordHash.jobCronExpression=* * * * * ? 2099 + +system.api.discovery.enabled=true + +# Maximum query size for category/tag fetch when not explicitly set by paging parameters +category.queryFetchSize=5000 + +# Brute force protection +authentication.protection.enabled=true +authentication.protection.limit=10 +authentication.protection.periodSeconds=6 + +system.email.sender.default=noreply@alfresco.com +# reset password workflow will expire in an hour +system.reset-password.endTimer=PT1H +system.reset-password.sendEmailAsynchronously=true + +# HeartBeat +heartbeat.target.url= +heartbeat.enabled=true + +# CSRF filter overrides +csrf.filter.enabled=true +csrf.filter.referer= +csrf.filter.referer.always=false +csrf.filter.origin= +csrf.filter.origin.always=false + +# CORS settings +cors.enabled=false +cors.allowed.origins= +cors.allowed.methods=GET,POST,HEAD,OPTIONS,PUT,DELETE +cors.allowed.headers=Authorization,Content-Type,Cache-Control,X-Requested-With,accept,Origin,Access-Control-Request-Method,Access-Control-Request-Headers,X-CSRF-Token +cors.exposed.headers=Access-Control-Allow-Origin,Access-Control-Allow-Credentials +cors.support.credentials=true +cors.preflight.maxage=10 + +# Alfresco Rest Api-Explorer +api-explorer.url= + +# Events subsystem +events.subsystem.autoStart=false +# Messaging subsystem +messaging.subsystem.autoStart=true + + +# Raw events +acs.repo.rendition.events.endpoint=jms:acs-repo-rendition-events?jmsMessageType=Text + +# Transform request events +acs.repo.transform.request.endpoint=jms:acs-repo-transform-request?jmsMessageType=Text + +# If enabled doesn't allow to set content properties via NodeService +contentPropertyRestrictions.enabled=true +contentPropertyRestrictions.whitelist= + +# Repo events2 +# Type and aspect filters which should be excluded +# Note: System folders node types are added by default +repo.event2.filter.nodeTypes=sys:*, fm:*, cm:thumbnail, cm:failedThumbnail, cm:rating, rma:rmsite include_subtypes +repo.event2.filter.nodeAspects=sys:* +repo.event2.filter.childAssocTypes=rn:rendition +# Comma separated list of users which should be excluded +# Note: username's case-sensitivity depends on the {user.name.caseSensitive} setting +repo.event2.filter.users= +# Topic name +repo.event2.topic.endpoint=amqp:topic:alfresco.repo.event2 +# Thread pool for async enqueue of repo events +repo.event2.queue.enqueueThreadPool.priority=1 +repo.event2.queue.enqueueThreadPool.coreSize=8 +repo.event2.queue.enqueueThreadPool.maximumSize=10 +# Thread pool for async dequeue and delivery of repo events +repo.event2.queue.dequeueThreadPool.priority=1 +repo.event2.queue.dequeueThreadPool.coreSize=1 +repo.event2.queue.dequeueThreadPool.maximumSize=1 + + +# MNT-21083 +# --DELETE_NOT_EXISTS - default settings +system.delete_not_exists.batchsize=100000 +system.delete_not_exists.delete_batchsize=1000 +system.delete_not_exists.read_only=false +system.delete_not_exists.timeout_seconds=-1 +system.prop_table_cleaner.algorithm=V2 + +# Configure the system-wide (ACS) settings for direct access urls. +# +# For Direct Access URLs to be usable on the service-layer, the feature must be enabled both system-wide and on the +# content-store(s). For the feature to be usable through REST (outside the JVM) the rest-api configuration must also be +# enabled. +# +# The system.directAccessUrl.enabled property is the main switch of the feature. If this is set to false ALL +# Direct Access URLs are disabled. +# +# The next configuration that controls specific Direct Access URLs is the content store one. +# The connector.s3.directAccessUrl.enabled property controls whether Direct Access URLs are enabled for that specific store. +# +# Whether or not a client can request a Direct Access URL by using a REST endpoint is controlled by the +# restApi.directAccessUrl.enabled property. If the REST endpoint is disabled, but the feature is enabled +# system-wide and on the content-store, then the direct access URLs will only be usable by Java clients (only +# service-level requests will be possible). + +# Controls whether this feature is available, system wide. +# For direct access urls to work, the feature needs to be enabled both system-wide and on the individual content-store. +system.directAccessUrl.enabled=false +# Sets the default expiry time for the direct access url across all Content Stores. +# Its value cannot exceed the system-wide max expiry time, it can only be equal or lower (all DAUs disabled otherwise). +# This property is mandatory if direct access urls are enabled system-wide - (all DAUs disabled otherwise). +system.directAccessUrl.defaultExpiryTimeInSec=30 +# Sets the upper limit for the direct access urls expiry time, meaning a Content Store will be able to override this +# value but not exceed it, and the same goes for the clients. +# A service (Java Interface) client will be able to request a direct access url for a custom expiry time but that time +# can’t exceed this value. +# If the requested time exceeds the max value, the expiry time reverts to the default configured one. +# This property is mandatory if direct access urls are enabled system-wide - (all DAUs disabled otherwise). +system.directAccessUrl.maxExpiryTimeInSec=300 + +# Configure the common S3 storage connector content store settings for direct access urls. +# +# Note: When multiple S3 buckets are used for storage in Alfresco, each S3 Content Stores can be configured with either +# the default (common) S3 Connector-specific properties (i.e. connector.s3.directAccessUrl.enabled etc.) OR new separate +# properties could be defined for each and every store (e.g. +# connector.s3store1.directAccessUrl.enabled, +# connector.s3store2.directAccessUrl.enabled etc.). + +# Controls whether DAUs are enabled on this specific content store. +# For direct access urls to work, the feature needs to be enabled both system-wide and on the individual content-store. +connector.s3.directAccessUrl.enabled=false +# Sets the expiry time for the direct access url in this store, by overriding the system-wide config. +# If this value exceeds the content store upper limit or the system-wide default it will fallback to the system-wide +# default configuration. +# Its value cannot exceed the system-wide max expiry time, it can only be equal or lower (DAUs for the specific content +# store disabled otherwise). +# If not set, the default system-wide setting will be used. +connector.s3.directAccessUrl.defaultExpiryTimeInSec=30 +# The maximum expiry time interval that can be requested by clients - content-store specific setting. +# Its value cannot exceed the system-wide configuration, it can only be equal or lower (DAUs for the specific content +# store disabled otherwise). +# If not set, the default system-wide setting will be used. +connector.s3.directAccessUrl.maxExpiryTimeInSec=300 + +# Configure the REST API configuration settings for direct access urls. +# +# Controls whether direct access url requests via the REST API are enabled. +restApi.directAccessUrl.enabled=false +# Sets the expiry time for all the direct access urls requested via a REST call. +# Its value cannot exceed the system-wide max expiry time configuration, it can only be equal or lower (REST API DAUs +# disabled otherwise). +# If not set, the default system-wide setting will be used. +# Direct Access Url REST API calls cannot request an explicit expiry time. +restApi.directAccessUrl.defaultExpiryTimeInSec=30 + +# Creates additional indexes on alf_node and alf_transaction. Recommended for large repositories. +system.new-node-transaction-indexes.ignored=true + +# Allows the configuration of maximum limits of the temp files to be deleted or the maximum time allowed to run for the job +system.tempFileCleaner.maxFilesToDelete= +system.tempFileCleaner.maxTimeToRun= + +# Property to long running migration to remove alf_server in v7+ patch.db-V7.1.0-remove-alf_server-table +system.remove-alf_server-table-from-db.ignored=true + +# When using JSONP, allows unsecure usage of "callback" functions. Disabled by default for security reasons +allow.unsecure.callback.jsonp=false diff --git a/repository/src/main/resources/alfresco/subsystems/Search/common-search-context.xml b/repository/src/main/resources/alfresco/subsystems/Search/common-search-context.xml index 7410e27f2b..932c4a06ef 100644 --- a/repository/src/main/resources/alfresco/subsystems/Search/common-search-context.xml +++ b/repository/src/main/resources/alfresco/subsystems/Search/common-search-context.xml @@ -105,8 +105,35 @@ - - + + + + search.dbQueryEngineImpl.#bean.dialect# + + + org.alfresco.repo.search.impl.querymodel.QueryEngine + + + org.alfresco.repo.domain.dialect.Dialect + + + + + + + + + + + + + + + + + diff --git a/repository/src/test/java/org/alfresco/AllDBTestsTestSuite.java b/repository/src/test/java/org/alfresco/AllDBTestsTestSuite.java index 6dddc58bbe..1737976f0e 100644 --- a/repository/src/test/java/org/alfresco/AllDBTestsTestSuite.java +++ b/repository/src/test/java/org/alfresco/AllDBTestsTestSuite.java @@ -2,7 +2,7 @@ * #%L * Alfresco Repository * %% - * Copyright (C) 2005 - 2017 Alfresco Software Limited + * 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 @@ -77,7 +77,10 @@ import org.junit.runners.Suite; // From MiscContextTestSuite org.alfresco.repo.domain.query.CannedQueryDAOTest.class, - // REPO-2963 : Tests causing a cascade of failures in AllDBTestsTestSuite on PostgreSQL/MySQL + // ACS-1907 + org.alfresco.repo.search.impl.querymodel.impl.db.ACS1907Test.class, + + // REPO-2963 : Tests causing a cascade of failures in AllDBTestsTestSuite on PostgreSQL/MySQL // Moved at the bottom of the suite because DbNodeServiceImplTest.testNodeCleanupRegistry() takes a long time on a clean DB. org.alfresco.repo.node.db.DbNodeServiceImplTest.class, diff --git a/repository/src/test/java/org/alfresco/AllUnitTestsSuite.java b/repository/src/test/java/org/alfresco/AllUnitTestsSuite.java index ba6cc05354..26b5908cf8 100644 --- a/repository/src/test/java/org/alfresco/AllUnitTestsSuite.java +++ b/repository/src/test/java/org/alfresco/AllUnitTestsSuite.java @@ -180,6 +180,9 @@ import org.junit.runners.Suite; org.alfresco.repo.audit.AuditableAnnotationTest.class, org.alfresco.repo.audit.PropertyAuditFilterTest.class, org.alfresco.repo.audit.access.NodeChangeTest.class, + org.alfresco.repo.content.ContentServiceImplUnitTest.class, + org.alfresco.repo.content.directurl.SystemWideDirectUrlConfigUnitTest.class, + org.alfresco.repo.content.directurl.ContentStoreDirectUrlConfigUnitTest.class, org.alfresco.repo.content.LimitedStreamCopierTest.class, org.alfresco.repo.content.filestore.FileIOTest.class, org.alfresco.repo.content.filestore.SpoofedTextContentReaderTest.class, @@ -233,7 +236,6 @@ import org.junit.runners.Suite; org.alfresco.repo.events.ClientUtilTest.class, org.alfresco.repo.rendition2.RenditionService2Test.class, org.alfresco.repo.rendition2.TransformationOptionsConverterTest.class, - org.alfresco.transform.client.registry.TransformServiceRegistryConfigTest.class, org.alfresco.repo.event2.RepoEvent2UnitSuite.class, diff --git a/repository/src/test/java/org/alfresco/repo/content/ContentServiceImplUnitTest.java b/repository/src/test/java/org/alfresco/repo/content/ContentServiceImplUnitTest.java new file mode 100644 index 0000000000..63e6530715 --- /dev/null +++ b/repository/src/test/java/org/alfresco/repo/content/ContentServiceImplUnitTest.java @@ -0,0 +1,160 @@ +/* + * #%L + * Alfresco Repository + * %% + * 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 . + * #L% + */ +package org.alfresco.repo.content; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.openMocks; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.content.directurl.DirectAccessUrlDisabledException; +import org.alfresco.repo.content.directurl.SystemWideDirectUrlConfig; +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.DirectAccessUrl; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; + +/** + * Unit tests for content service implementation. + * + * @author Sara Aspery + */ +public class ContentServiceImplUnitTest +{ + private static final Boolean ENABLED = Boolean.TRUE; + private static final Boolean DISABLED = Boolean.FALSE; + + private static final Long SYS_DEFAULT_EXPIRY_TIME_IN_SECS = 30L; + private static final Long SYS_MAX_EXPIRY_TIME_IN_SECS = 300L; + + private static final NodeRef NODE_REF = new NodeRef("content://Node/Ref"); + + @InjectMocks + private ContentServiceImpl contentService; + + @Mock + private ContentStore mockContentStore; + + @Mock + private NodeService mockNodeService; + + @Mock + private ContentData mockContentData; + + @Before + public void setup() + { + openMocks(this); + when(mockNodeService.getProperty(NODE_REF, ContentModel.PROP_CONTENT)).thenReturn(mockContentData); + when(mockContentData.getContentUrl()).thenReturn("someContentUrl"); + when(mockNodeService.getProperty(NODE_REF, ContentModel.PROP_NAME)).thenReturn("someFilename"); + } + + @Test + public void testIsContentDirectUrlEnabled_SystemWideIsDisabled() + { + setupSystemWideDirectAccessConfig(DISABLED); + assertFalse("Expected contentDirectUrl to be disabled", contentService.isContentDirectUrlEnabled()); + verify(mockContentStore, never()).isContentDirectUrlEnabled(); + } + + @Test + public void testIsContentDirectUrlEnabled_SystemWideIsEnabledButStoreIsDisabled() + { + setupSystemWideDirectAccessConfig(ENABLED); + when(mockContentStore.isContentDirectUrlEnabled()).thenReturn(DISABLED); + assertFalse("Expected contentDirectUrl to be disabled", contentService.isContentDirectUrlEnabled()); + } + + @Test + public void testIsContentDirectUrlEnabled_SystemWideIsEnabledAndStoreIsEnabled() + { + setupSystemWideDirectAccessConfig(ENABLED); + when(mockContentStore.isContentDirectUrlEnabled()).thenReturn(ENABLED); + assertTrue("Expected contentDirectUrl to be enabled", contentService.isContentDirectUrlEnabled()); + } + + @Test + public void testRequestContentDirectUrl_SystemWideIsDisabled() + { + setupSystemWideDirectAccessConfig(DISABLED); + try + { + contentService.requestContentDirectUrl(NODE_REF, true, 20L); + fail("Expected DirectAccessUrlDisabledException"); + } + catch (DirectAccessUrlDisabledException ex) + { + verify(mockContentStore, never()).isContentDirectUrlEnabled(); + } + } + + @Test + public void testRequestContentDirectUrl_SystemWideIsEnabledButStoreIsDisabled() + { + setupSystemWideDirectAccessConfig(ENABLED); + when(mockContentStore.isContentDirectUrlEnabled()).thenReturn(DISABLED); + + DirectAccessUrl directAccessUrl = contentService.requestContentDirectUrl(NODE_REF, true, 20L); + assertNull(directAccessUrl); + verify(mockContentStore, never()).requestContentDirectUrl(anyString(), eq(true), anyString(), anyLong()); + } + + @Test + public void testRequestContentDirectUrl_StoreIsEnabledButNotImplemented() + { + setupSystemWideDirectAccessConfig(ENABLED); + when(mockContentStore.isContentDirectUrlEnabled()).thenReturn(ENABLED); + + DirectAccessUrl directAccessUrl = contentService.requestContentDirectUrl(NODE_REF, true, 20L); + assertNull(directAccessUrl); + verify(mockContentStore, times(1)).requestContentDirectUrl(anyString(), eq(true), anyString(), anyLong()); + } + + /* Helper method to set system-wide direct access url configuration settings */ + private void setupSystemWideDirectAccessConfig(Boolean isEnabled) + { + SystemWideDirectUrlConfig sysConfig = new SystemWideDirectUrlConfig(); + sysConfig.setEnabled(isEnabled); + sysConfig.setDefaultExpiryTimeInSec(SYS_DEFAULT_EXPIRY_TIME_IN_SECS); + sysConfig.setMaxExpiryTimeInSec(SYS_MAX_EXPIRY_TIME_IN_SECS); + sysConfig.validate(); + contentService.setSystemWideDirectUrlConfig(sysConfig); + } +} diff --git a/repository/src/test/java/org/alfresco/repo/content/caching/CachingContentStoreTest.java b/repository/src/test/java/org/alfresco/repo/content/caching/CachingContentStoreTest.java index d379db4b10..47b2fa14c2 100644 --- a/repository/src/test/java/org/alfresco/repo/content/caching/CachingContentStoreTest.java +++ b/repository/src/test/java/org/alfresco/repo/content/caching/CachingContentStoreTest.java @@ -36,6 +36,7 @@ import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; @@ -494,21 +495,21 @@ public class CachingContentStoreTest } @Test - public void isDirectAccessSupported() + public void isContentDirectUrlSupported() { - assertFalse(cachingStore.isDirectAccessSupported()); + assertFalse(cachingStore.isContentDirectUrlEnabled()); - when(backingStore.isDirectAccessSupported()).thenReturn(true); - assertTrue(cachingStore.isDirectAccessSupported()); + when(backingStore.isContentDirectUrlEnabled()).thenReturn(true); + assertTrue(cachingStore.isContentDirectUrlEnabled()); } @Test - public void getDirectAccessUrlUnsupported() + public void getRequestContentDirectUrlUnsupported() { try { - when(backingStore.getDirectAccessUrl(anyString(), any())).thenThrow(new UnsupportedOperationException()); - cachingStore.getDirectAccessUrl("url", null); + when(backingStore.requestContentDirectUrl(anyString(), eq(true), anyString(), anyLong())).thenThrow(new UnsupportedOperationException()); + cachingStore.requestContentDirectUrl("url", true,"someFile", 30L); fail(); } catch (UnsupportedOperationException e) @@ -518,10 +519,10 @@ public class CachingContentStoreTest } @Test - public void getDirectAccessUrl() + public void getRequestContentDirectUrl() { - when(backingStore.getDirectAccessUrl(anyString(), any())).thenReturn(new DirectAccessUrl()); - cachingStore.getDirectAccessUrl("url", null); + when(backingStore.requestContentDirectUrl(anyString(), eq(true), anyString(), anyLong())).thenReturn(new DirectAccessUrl()); + cachingStore.requestContentDirectUrl("url", true,"someFile", 30L); } @Test diff --git a/repository/src/test/java/org/alfresco/repo/content/directurl/ContentStoreDirectUrlConfigUnitTest.java b/repository/src/test/java/org/alfresco/repo/content/directurl/ContentStoreDirectUrlConfigUnitTest.java new file mode 100644 index 0000000000..b32ed63be8 --- /dev/null +++ b/repository/src/test/java/org/alfresco/repo/content/directurl/ContentStoreDirectUrlConfigUnitTest.java @@ -0,0 +1,234 @@ +/* + * #%L + * Alfresco Repository + * %% + * 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 . + * #L% + */ +package org.alfresco.repo.content.directurl; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Before; +import org.junit.Test; + +/** + * Tests for content store direct access URL configuration settings. + * + * @author Sara Aspery + */ +public class ContentStoreDirectUrlConfigUnitTest +{ + private static final Boolean ENABLED = Boolean.TRUE; + private static final Boolean DISABLED = Boolean.FALSE; + + private static final Long DEFAULT_EXPIRY_TIME_IN_SECS = 10L; + private static final Long MAX_EXPIRY_TIME_IN_SECS = 20L; + + private static final Long SYS_DEFAULT_EXPIRY_TIME_IN_SECS = 30L; + private static final Long SYS_MAX_EXPIRY_TIME_IN_SECS = 300L; + + private ContentStoreDirectUrlConfig contentStoreDirectUrlConfig; + + @Before + public void setup() + { + this.contentStoreDirectUrlConfig = new ContentStoreDirectUrlConfig(); + setupSystemWideDirectAccessConfig(); + } + + @Test + public void testValidConfig_RemainsEnabled() + { + setupDirectAccessConfig(ENABLED, DEFAULT_EXPIRY_TIME_IN_SECS, MAX_EXPIRY_TIME_IN_SECS); + + assertTrue("Expected content store direct URLs to be enabled", contentStoreDirectUrlConfig.isEnabled()); + contentStoreDirectUrlConfig.validate(); + assertTrue("Expected REST API direct URLs to be enabled", contentStoreDirectUrlConfig.isEnabled()); + } + + @Test + public void testValidConfig_RemainsDisabled() + { + setupDirectAccessConfig(DISABLED, DEFAULT_EXPIRY_TIME_IN_SECS, MAX_EXPIRY_TIME_IN_SECS); + + assertFalse("Expected content store direct URLs to be disabled", contentStoreDirectUrlConfig.isEnabled()); + contentStoreDirectUrlConfig.validate(); + assertFalse("Expected content store direct URLs to be disabled", contentStoreDirectUrlConfig.isEnabled()); + } + + @Test + public void testInvalidConfig_DefaultExpiryTimeMissing_ValidReplacement() + { + Long maxExpiryTimeInSecs = SYS_DEFAULT_EXPIRY_TIME_IN_SECS + 1; + setupDirectAccessConfig(ENABLED, null, maxExpiryTimeInSecs); + + verifyDirectAccessConfig(ENABLED, null, maxExpiryTimeInSecs); + contentStoreDirectUrlConfig.validate(); + verifyDirectAccessConfig(ENABLED, SYS_DEFAULT_EXPIRY_TIME_IN_SECS, maxExpiryTimeInSecs); + } + + @Test + public void testInvalidConfig_DefaultExpiryTimeMissing_ReplacementExceedsMax() + { + setupDirectAccessConfig(ENABLED, null, MAX_EXPIRY_TIME_IN_SECS); + + verifyDirectAccessConfig(ENABLED, null, MAX_EXPIRY_TIME_IN_SECS); + contentStoreDirectUrlConfig.validate(); + verifyDirectAccessConfig(DISABLED, SYS_DEFAULT_EXPIRY_TIME_IN_SECS, MAX_EXPIRY_TIME_IN_SECS); + } + + @Test + public void testInvalidConfig_DefaultExpiryTimeZero() + { + setupDirectAccessConfig(ENABLED, 0L, MAX_EXPIRY_TIME_IN_SECS); + + assertTrue("Expected content store direct URLs to be enabled", contentStoreDirectUrlConfig.isEnabled()); + contentStoreDirectUrlConfig.validate(); + assertFalse("Expected content store direct URLs to be disabled", contentStoreDirectUrlConfig.isEnabled()); + } + + @Test + public void testInvalidConfig_DefaultExpiryTimeNegative() + { + setupDirectAccessConfig(ENABLED, -1L, MAX_EXPIRY_TIME_IN_SECS); + + assertTrue("Expected content store direct URLs to be enabled", contentStoreDirectUrlConfig.isEnabled()); + contentStoreDirectUrlConfig.validate(); + assertFalse("Expected content store direct URLs to be disabled", contentStoreDirectUrlConfig.isEnabled()); + } + + @Test + public void testInvalidConfig_DefaultExpiryTimeExceedsSystemMax() + { + Long defaultExpiryTimeInSecs = SYS_MAX_EXPIRY_TIME_IN_SECS + 1; + setupDirectAccessConfig(ENABLED, defaultExpiryTimeInSecs, MAX_EXPIRY_TIME_IN_SECS); + + assertTrue("Expected content store direct URLs to be enabled", contentStoreDirectUrlConfig.isEnabled()); + contentStoreDirectUrlConfig.validate(); + assertFalse("Expected content store direct URLs to be disabled", contentStoreDirectUrlConfig.isEnabled()); + } + + @Test + public void testInvalidConfig_DefaultExpiryTimeExceedsStoreMax_ValidReplacement() + { + Long maxExpiryTimeInSecs = SYS_DEFAULT_EXPIRY_TIME_IN_SECS + 1; + Long defaultExpiryTimeInSecs = maxExpiryTimeInSecs + 1; + setupDirectAccessConfig(ENABLED, defaultExpiryTimeInSecs, maxExpiryTimeInSecs); + + verifyDirectAccessConfig(ENABLED, defaultExpiryTimeInSecs, maxExpiryTimeInSecs); + contentStoreDirectUrlConfig.validate(); + verifyDirectAccessConfig(ENABLED, SYS_DEFAULT_EXPIRY_TIME_IN_SECS, maxExpiryTimeInSecs); + } + + @Test + public void testInvalidConfig_DefaultExpiryTimeExceedsStoreMax_ReplacementExceedsStoreMax() + { + Long defaultExpiryTimeInSecs = MAX_EXPIRY_TIME_IN_SECS + 1; + setupDirectAccessConfig(ENABLED, defaultExpiryTimeInSecs, MAX_EXPIRY_TIME_IN_SECS); + + verifyDirectAccessConfig(ENABLED, defaultExpiryTimeInSecs, MAX_EXPIRY_TIME_IN_SECS); + contentStoreDirectUrlConfig.validate(); + verifyDirectAccessConfig(DISABLED, SYS_DEFAULT_EXPIRY_TIME_IN_SECS, MAX_EXPIRY_TIME_IN_SECS); + } + @Test + public void testInvalidConfig_DefaultExpiryTimeExceedsSystemDefault_ValidReplacement() + { + Long defaultExpiryTimeInSecs = SYS_DEFAULT_EXPIRY_TIME_IN_SECS + 1; + Long maxExpiryTimeInSecs = SYS_MAX_EXPIRY_TIME_IN_SECS; + setupDirectAccessConfig(ENABLED, defaultExpiryTimeInSecs, maxExpiryTimeInSecs); + + verifyDirectAccessConfig(ENABLED, defaultExpiryTimeInSecs, maxExpiryTimeInSecs); + contentStoreDirectUrlConfig.validate(); + verifyDirectAccessConfig(ENABLED, SYS_DEFAULT_EXPIRY_TIME_IN_SECS, maxExpiryTimeInSecs); + } + + @Test + public void testInvalidConfig_DefaultExpiryTimeExceedsSystemDefault_ReplacementExceedsStoreMax() + { + Long defaultExpiryTimeInSecs = SYS_DEFAULT_EXPIRY_TIME_IN_SECS + 1; + setupDirectAccessConfig(ENABLED, defaultExpiryTimeInSecs, MAX_EXPIRY_TIME_IN_SECS); + + verifyDirectAccessConfig(ENABLED, defaultExpiryTimeInSecs, MAX_EXPIRY_TIME_IN_SECS); + contentStoreDirectUrlConfig.validate(); + verifyDirectAccessConfig(DISABLED, SYS_DEFAULT_EXPIRY_TIME_IN_SECS, MAX_EXPIRY_TIME_IN_SECS); + } + + @Test + public void testInvalidConfig_MaxExpiryTimeZero() + { + setupDirectAccessConfig(ENABLED, DEFAULT_EXPIRY_TIME_IN_SECS, 0L); + + assertTrue("Expected content store direct URLs to be enabled", contentStoreDirectUrlConfig.isEnabled()); + contentStoreDirectUrlConfig.validate(); + assertFalse("Expected content store direct URLs to be disabled", contentStoreDirectUrlConfig.isEnabled()); + } + + @Test + public void testInvalidConfig_MaxExpiryTimeNegative() + { + setupDirectAccessConfig(ENABLED, DEFAULT_EXPIRY_TIME_IN_SECS, -1L); + + assertTrue("Expected content store direct URLs to be enabled", contentStoreDirectUrlConfig.isEnabled()); + contentStoreDirectUrlConfig.validate(); + assertFalse("Expected content store direct URLs to be disabled", contentStoreDirectUrlConfig.isEnabled()); + } + + @Test + public void testInvalidConfig_MaxExpiryTimeExceedsSystemMax() + { + Long maxExpiryTimeInSec = contentStoreDirectUrlConfig.getSysWideMaxExpiryTimeInSec() + 1; + setupDirectAccessConfig(ENABLED, DEFAULT_EXPIRY_TIME_IN_SECS, maxExpiryTimeInSec); + + assertTrue("Expected content store direct URLs to be enabled", contentStoreDirectUrlConfig.isEnabled()); + contentStoreDirectUrlConfig.validate(); + assertFalse("Expected content store direct URLs to be disabled", contentStoreDirectUrlConfig.isEnabled()); + } + + /* Helper method to set content store direct access url configuration settings */ + private void setupDirectAccessConfig(Boolean isEnabled, Long defaultExpiryTime, Long maxExpiryTime) + { + contentStoreDirectUrlConfig.setEnabled(isEnabled); + contentStoreDirectUrlConfig.setDefaultExpiryTimeInSec(defaultExpiryTime); + contentStoreDirectUrlConfig.setMaxExpiryTimeInSec(maxExpiryTime); + } + + /* Helper method to verify content store direct access url configuration settings */ + private void verifyDirectAccessConfig(Boolean isEnabled, Long defaultExpiryTime, Long maxExpiryTime) + { + assertEquals("Expected content store direct URLs to be enabled = " + isEnabled, isEnabled, contentStoreDirectUrlConfig.isEnabled()); + assertEquals("Expected default expiry time to match " + defaultExpiryTime, defaultExpiryTime, contentStoreDirectUrlConfig.getDefaultExpiryTimeInSec()); + assertEquals("Expected maximum expiry time to match " + maxExpiryTime, maxExpiryTime, contentStoreDirectUrlConfig.getMaxExpiryTimeInSec()); + } + + /* Helper method to set system-wide direct access url configuration settings */ + private void setupSystemWideDirectAccessConfig() + { + SystemWideDirectUrlConfig sysConfig = new SystemWideDirectUrlConfig(); + sysConfig.setEnabled(ENABLED); + sysConfig.setDefaultExpiryTimeInSec(SYS_DEFAULT_EXPIRY_TIME_IN_SECS); + sysConfig.setMaxExpiryTimeInSec(SYS_MAX_EXPIRY_TIME_IN_SECS); + sysConfig.validate(); + contentStoreDirectUrlConfig.setSystemWideDirectUrlConfig(sysConfig); + } +} diff --git a/repository/src/test/java/org/alfresco/repo/content/directurl/SystemWideDirectUrlConfigUnitTest.java b/repository/src/test/java/org/alfresco/repo/content/directurl/SystemWideDirectUrlConfigUnitTest.java new file mode 100644 index 0000000000..d498fac30b --- /dev/null +++ b/repository/src/test/java/org/alfresco/repo/content/directurl/SystemWideDirectUrlConfigUnitTest.java @@ -0,0 +1,153 @@ +/* + * #%L + * Alfresco Repository + * %% + * 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 . + * #L% + */ +package org.alfresco.repo.content.directurl; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Before; +import org.junit.Test; + +/** + * Tests for system-wide direct access URL configuration settings. + * + * @author Sara Aspery + */ +public class SystemWideDirectUrlConfigUnitTest +{ + private static final Boolean ENABLED = Boolean.TRUE; + private static final Boolean DISABLED = Boolean.FALSE; + + private static final Long DEFAULT_EXPIRY_TIME_IN_SECS = 30L; + private static final Long MAX_EXPIRY_TIME_IN_SECS = 300L; + + private SystemWideDirectUrlConfig systemWideDirectUrlConfig; + + @Before + public void setup() + { + this.systemWideDirectUrlConfig = new SystemWideDirectUrlConfig(); + } + + @Test + public void testValidConfig_RemainsEnabled() + { + setupDirectAccessConfig(ENABLED, DEFAULT_EXPIRY_TIME_IN_SECS, MAX_EXPIRY_TIME_IN_SECS); + + assertTrue("Expected system-wide direct URLs to be enabled", systemWideDirectUrlConfig.isEnabled()); + systemWideDirectUrlConfig.validate(); + assertTrue("Expected system-wide direct URLs to be enabled", systemWideDirectUrlConfig.isEnabled()); + } + + @Test + public void testValidConfig_RemainsDisabled() + { + setupDirectAccessConfig(DISABLED, DEFAULT_EXPIRY_TIME_IN_SECS, MAX_EXPIRY_TIME_IN_SECS); + + assertFalse("Expected system-wide direct URLs to be disabled", systemWideDirectUrlConfig.isEnabled()); + systemWideDirectUrlConfig.validate(); + assertFalse("Expected system-wide direct URLs to be disabled", systemWideDirectUrlConfig.isEnabled()); + } + + @Test + public void testInvalidConfig_DefaultExpiryTimeMissing() + { + setupDirectAccessConfig(ENABLED, null, MAX_EXPIRY_TIME_IN_SECS); + + assertTrue("Expected system-wide direct URLs to be enabled", systemWideDirectUrlConfig.isEnabled()); + systemWideDirectUrlConfig.validate(); + assertFalse("Expected system-wide direct URLs to be disabled", systemWideDirectUrlConfig.isEnabled()); + } + + @Test + public void testInvalidConfig_DefaultExpiryTimeZero() + { + setupDirectAccessConfig(ENABLED, 0L, MAX_EXPIRY_TIME_IN_SECS); + + assertTrue("Expected system-wide direct URLs to be enabled", systemWideDirectUrlConfig.isEnabled()); + systemWideDirectUrlConfig.validate(); + assertFalse("Expected system-wide direct URLs to be disabled", systemWideDirectUrlConfig.isEnabled()); + } + + @Test + public void testInvalidConfig_DefaultExpiryTimeNegative() + { + setupDirectAccessConfig(ENABLED, -1L, MAX_EXPIRY_TIME_IN_SECS); + + assertTrue("Expected system-wide direct URLs to be enabled", systemWideDirectUrlConfig.isEnabled()); + systemWideDirectUrlConfig.validate(); + assertFalse("Expected system-wide direct URLs to be disabled", systemWideDirectUrlConfig.isEnabled()); + } + + @Test + public void testInvalidConfig_MaxExpiryTimeMissing() + { + setupDirectAccessConfig(ENABLED, DEFAULT_EXPIRY_TIME_IN_SECS, null); + + assertTrue("Expected system-wide direct URLs to be enabled", systemWideDirectUrlConfig.isEnabled()); + systemWideDirectUrlConfig.validate(); + assertFalse("Expected system-wide direct URLs to be disabled", systemWideDirectUrlConfig.isEnabled()); + } + + @Test + public void testInvalidConfig_MaxExpiryTimeZero() + { + setupDirectAccessConfig(ENABLED, DEFAULT_EXPIRY_TIME_IN_SECS, 0L); + + assertTrue("Expected system-wide direct URLs to be enabled", systemWideDirectUrlConfig.isEnabled()); + systemWideDirectUrlConfig.validate(); + assertFalse("Expected system-wide direct URLs to be disabled", systemWideDirectUrlConfig.isEnabled()); + } + + @Test + public void testInvalidConfig_MaxExpiryTimeNegative() + { + setupDirectAccessConfig(ENABLED, DEFAULT_EXPIRY_TIME_IN_SECS, -1L); + + assertTrue("Expected system-wide direct URLs to be enabled", systemWideDirectUrlConfig.isEnabled()); + systemWideDirectUrlConfig.validate(); + assertFalse("Expected system-wide direct URLs to be disabled", systemWideDirectUrlConfig.isEnabled()); + } + + @Test + public void testInvalidConfig_DefaultExpiryTimeExceedsMax() + { + setupDirectAccessConfig(ENABLED, MAX_EXPIRY_TIME_IN_SECS + 1, MAX_EXPIRY_TIME_IN_SECS); + + assertTrue("Expected system-wide direct URLs to be enabled", systemWideDirectUrlConfig.isEnabled()); + systemWideDirectUrlConfig.validate(); + assertFalse("Expected system-wide direct URLs to be disabled", systemWideDirectUrlConfig.isEnabled()); + } + + /* Helper method to set system-wide direct access url configuration settings */ + private void setupDirectAccessConfig(Boolean isEnabled, Long defaultExpiryTime, Long maxExpiryTime) + { + systemWideDirectUrlConfig.setEnabled(isEnabled); + systemWideDirectUrlConfig.setDefaultExpiryTimeInSec(defaultExpiryTime); + systemWideDirectUrlConfig.setMaxExpiryTimeInSec(maxExpiryTime); + } +} + diff --git a/repository/src/test/java/org/alfresco/repo/content/replication/AggregatingContentStoreTest.java b/repository/src/test/java/org/alfresco/repo/content/replication/AggregatingContentStoreTest.java index 72f0307c95..2a3224851a 100644 --- a/repository/src/test/java/org/alfresco/repo/content/replication/AggregatingContentStoreTest.java +++ b/repository/src/test/java/org/alfresco/repo/content/replication/AggregatingContentStoreTest.java @@ -2,7 +2,7 @@ * #%L * Alfresco Repository * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited + * 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 @@ -63,6 +63,18 @@ import org.junit.Test; import org.junit.experimental.categories.Category; import org.junit.runner.RunWith; import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; import org.mockito.junit.MockitoJUnitRunner; /** @@ -88,9 +100,12 @@ public class AggregatingContentStoreTest extends AbstractWritableContentStoreTes ContentStore primaryStoreMock; @Mock ContentStore secondaryStoreMock; - + @Mock AggregatingContentStore aggregatingContentStoreMock; + @Rule + public MockitoRule rule = MockitoJUnit.rule(); + @Before public void before() throws Exception { @@ -201,51 +216,53 @@ public class AggregatingContentStoreTest extends AbstractWritableContentStoreTes } @Test - public void testDirectAccessUnsupportedByDefault() + public void testIsContentDirectUrlEnabled() { + // Create the aggregating store + AggregatingContentStore aggStore = new AggregatingContentStore(); + aggStore.setPrimaryStore(primaryStoreMock); + aggStore.setSecondaryStores(List.of(secondaryStoreMock)); + // By default it is unsupported - assertFalse(aggregatingContentStoreMock.isDirectAccessSupported()); - verify(primaryStoreMock, times(1)).isDirectAccessSupported(); - verify(secondaryStoreMock, times(1)).isDirectAccessSupported(); + assertFalse(aggStore.isContentDirectUrlEnabled()); + + // Supported if at least one store supports direct access + { + when(primaryStoreMock.isContentDirectUrlEnabled()).thenReturn(false); + when(secondaryStoreMock.isContentDirectUrlEnabled()).thenReturn(true); + assertTrue(aggStore.isContentDirectUrlEnabled()); + + when(primaryStoreMock.isContentDirectUrlEnabled()).thenReturn(true); + when(secondaryStoreMock.isContentDirectUrlEnabled()).thenReturn(true); + assertTrue(aggStore.isContentDirectUrlEnabled()); + + when(primaryStoreMock.isContentDirectUrlEnabled()).thenReturn(true); + when(secondaryStoreMock.isContentDirectUrlEnabled()).thenReturn(false); + assertTrue(aggStore.isContentDirectUrlEnabled()); + } } @Test - public void testIsDirectAccessSupportedByPrimaryStore() + public void testRequestContentDirectUrl() { - when(primaryStoreMock.isDirectAccessSupported()).thenReturn(false); - when(secondaryStoreMock.isDirectAccessSupported()).thenReturn(true); + // Create the aggregating store + AggregatingContentStore aggStore = new AggregatingContentStore(); + aggStore.setPrimaryStore(primaryStoreMock); + aggStore.setSecondaryStores(List.of(secondaryStoreMock)); - assertTrue(aggregatingContentStoreMock.isDirectAccessSupported()); - verify(primaryStoreMock, times(1)).isDirectAccessSupported(); - verify(secondaryStoreMock, times(1)).isDirectAccessSupported(); - } - - @Test - public void testIsDirectAccessSupportedBySecondaryStore() - { - when(primaryStoreMock.isDirectAccessSupported()).thenReturn(true); - - assertTrue(aggregatingContentStoreMock.isDirectAccessSupported()); - verify(primaryStoreMock, times(1)).isDirectAccessSupported(); - verifyNoInteractions(secondaryStoreMock); - } - - @Test - public void testGetDirectAccessUrl() - { UnsupportedOperationException unsupportedExc = new UnsupportedOperationException(); - UnsupportedContentUrlException unsupportedContentUrlExc = new UnsupportedContentUrlException(aggregatingContentStoreMock, ""); + UnsupportedContentUrlException unsupportedContentUrlExc = new UnsupportedContentUrlException(aggStore, ""); // By default it is unsupported - DirectAccessUrl directAccessUrl = aggregatingContentStoreMock.getDirectAccessUrl("url", null); + DirectAccessUrl directAccessUrl = aggStore.requestContentDirectUrl("url", true, "anyfilename", 30L); assertNull(directAccessUrl); // Direct access not supported try { - when(primaryStoreMock.getDirectAccessUrl(eq("urlDANotSupported"), any())).thenThrow(unsupportedExc); - when(secondaryStoreMock.getDirectAccessUrl(eq("urlDANotSupported"), any())).thenThrow(unsupportedExc); - aggregatingContentStoreMock.getDirectAccessUrl("urlDANotSupported", null); + when(primaryStoreMock.requestContentDirectUrl(eq("urlDANotSupported"), any(), any(), any())).thenThrow(unsupportedExc); + when(secondaryStoreMock.requestContentDirectUrl(eq("urlDANotSupported"), any(), any(), any())).thenThrow(unsupportedExc); + aggStore.requestContentDirectUrl(eq("urlDANotSupported"), true, "anyfilename", 30L); fail(); } catch (UnsupportedOperationException e) @@ -255,9 +272,9 @@ public class AggregatingContentStoreTest extends AbstractWritableContentStoreTes try { - when(primaryStoreMock.getDirectAccessUrl(eq("urlDANotSupported"), any())).thenThrow(unsupportedContentUrlExc); - when(secondaryStoreMock.getDirectAccessUrl(eq("urlDANotSupported"), any())).thenThrow(unsupportedExc); - aggregatingContentStoreMock.getDirectAccessUrl("urlDANotSupported", null); + when(primaryStoreMock.requestContentDirectUrl(eq("urlDANotSupported"), any(), any(), any())).thenThrow(unsupportedContentUrlExc); + when(secondaryStoreMock.requestContentDirectUrl(eq("urlDANotSupported"), any(), any(), any())).thenThrow(unsupportedExc); + aggStore.requestContentDirectUrl("urlDANotSupported", true, "anyfilename", 30L); fail(); } catch (UnsupportedOperationException e) @@ -267,9 +284,9 @@ public class AggregatingContentStoreTest extends AbstractWritableContentStoreTes try { - when(primaryStoreMock.getDirectAccessUrl(eq("urlDANotSupported"), any())).thenThrow(unsupportedExc); - when(secondaryStoreMock.getDirectAccessUrl(eq("urlDANotSupported"), any())).thenThrow(unsupportedContentUrlExc); - aggregatingContentStoreMock.getDirectAccessUrl("urlDANotSupported", null); + when(primaryStoreMock.requestContentDirectUrl(eq("urlDANotSupported"), any(), any(), any())).thenThrow(unsupportedExc); + when(secondaryStoreMock.requestContentDirectUrl(eq("urlDANotSupported"), any(), any(), any())).thenThrow(unsupportedContentUrlExc); + aggStore.requestContentDirectUrl("urlDANotSupported", true, "anyfilename", 30L); fail(); } catch (UnsupportedOperationException e) @@ -280,9 +297,9 @@ public class AggregatingContentStoreTest extends AbstractWritableContentStoreTes // Content url not supported try { - when(primaryStoreMock.getDirectAccessUrl(eq("urlNotSupported"), any())).thenThrow(unsupportedContentUrlExc); - when(secondaryStoreMock.getDirectAccessUrl(eq("urlNotSupported"), any())).thenThrow(unsupportedContentUrlExc); - aggregatingContentStoreMock.getDirectAccessUrl("urlNotSupported", null); + when(primaryStoreMock.requestContentDirectUrl(eq("urlNotSupported"), any(), any(), any())).thenThrow(unsupportedContentUrlExc); + when(secondaryStoreMock.requestContentDirectUrl(eq("urlNotSupported"), any(), any(), any())).thenThrow(unsupportedContentUrlExc); + aggStore.requestContentDirectUrl("urlNotSupported", true, "anyfilename", 30L); fail(); } catch (UnsupportedContentUrlException e) @@ -290,29 +307,31 @@ public class AggregatingContentStoreTest extends AbstractWritableContentStoreTes // Expected } - when(primaryStoreMock.getDirectAccessUrl(eq("urlPriSupported"), any())).thenReturn(new DirectAccessUrl()); - directAccessUrl = aggregatingContentStoreMock.getDirectAccessUrl("urlPriSupported", null); + when(primaryStoreMock.requestContentDirectUrl(eq("urlPriSupported"), any(), any(), any())).thenReturn(new DirectAccessUrl()); + when(secondaryStoreMock.requestContentDirectUrl(eq("urlPriSupported"), any(), any(), any())).thenThrow(unsupportedExc); + directAccessUrl = aggStore.requestContentDirectUrl("urlPriSupported", true, "anyfilename", 30L); assertNotNull(directAccessUrl); - when(primaryStoreMock.getDirectAccessUrl(eq("urlPriSupported"), any())).thenReturn(new DirectAccessUrl()); - directAccessUrl = aggregatingContentStoreMock.getDirectAccessUrl("urlPriSupported", null); + when(primaryStoreMock.requestContentDirectUrl(eq("urlPriSupported"), any(), any(), any())).thenReturn(new DirectAccessUrl()); + when(secondaryStoreMock.requestContentDirectUrl(eq("urlPriSupported"), any(), any(), any())).thenThrow(unsupportedContentUrlExc); + directAccessUrl = aggStore.requestContentDirectUrl("urlPriSupported", true, "anyfilename", 30L); assertNotNull(directAccessUrl); - when(primaryStoreMock.getDirectAccessUrl(eq("urlSecSupported"), any())).thenThrow(unsupportedExc); - when(secondaryStoreMock.getDirectAccessUrl(eq("urlSecSupported"), any())).thenReturn(new DirectAccessUrl()); - directAccessUrl = aggregatingContentStoreMock.getDirectAccessUrl("urlSecSupported", null); + when(primaryStoreMock.requestContentDirectUrl(eq("urlSecSupported"), any(), any(), any())).thenThrow(unsupportedExc); + when(secondaryStoreMock.requestContentDirectUrl(eq("urlSecSupported"), any(), any(), any())).thenReturn(new DirectAccessUrl()); + directAccessUrl = aggStore.requestContentDirectUrl("urlSecSupported", true, "anyfilename", 30L); assertNotNull(directAccessUrl); - when(primaryStoreMock.getDirectAccessUrl(eq("urlSecSupported"), any())).thenThrow(unsupportedContentUrlExc); - when(secondaryStoreMock.getDirectAccessUrl(eq("urlSecSupported"), any())).thenReturn(new DirectAccessUrl()); - directAccessUrl = aggregatingContentStoreMock.getDirectAccessUrl("urlSecSupported", null); + when(primaryStoreMock.requestContentDirectUrl(eq("urlSecSupported"), any(), any(), any())).thenThrow(unsupportedContentUrlExc); + when(secondaryStoreMock.requestContentDirectUrl(eq("urlSecSupported"), any(), any(), any())).thenReturn(new DirectAccessUrl()); + directAccessUrl = aggStore.requestContentDirectUrl("urlSecSupported", true, "anyfilename", 30L); assertNotNull(directAccessUrl); - when(primaryStoreMock.getDirectAccessUrl(eq("urlPriSupported"), any())).thenReturn(new DirectAccessUrl()); - when(secondaryStoreMock.getDirectAccessUrl(eq("urlSecSupported"), any())).thenReturn(new DirectAccessUrl()); - directAccessUrl = aggregatingContentStoreMock.getDirectAccessUrl("urlPriSupported", null); + when(primaryStoreMock.requestContentDirectUrl(eq("urlPriSupported"), any(), any(), any())).thenReturn(new DirectAccessUrl()); + when(secondaryStoreMock.requestContentDirectUrl(eq("urlSecSupported"), any(), any(), any())).thenReturn(new DirectAccessUrl()); + directAccessUrl = aggStore.requestContentDirectUrl("urlPriSupported", true, "anyfilename", 30L); assertNotNull(directAccessUrl); - directAccessUrl = aggregatingContentStoreMock.getDirectAccessUrl("urlSecSupported", null); + directAccessUrl = aggStore.requestContentDirectUrl("urlSecSupported", true, "anyfilename", 30L); assertNotNull(directAccessUrl); } @@ -321,7 +340,7 @@ public class AggregatingContentStoreTest extends AbstractWritableContentStoreTes { final StorageClassSet sc = new StorageClassSet("a-certain-storage-class"); when(primaryStoreMock.isStorageClassesSupported(sc)).thenReturn(true); - + assertTrue(aggregatingContentStoreMock.isStorageClassesSupported(sc)); verify(primaryStoreMock, times(1)).isStorageClassesSupported(sc); verifyNoInteractions(secondaryStoreMock); @@ -332,7 +351,7 @@ public class AggregatingContentStoreTest extends AbstractWritableContentStoreTes { final StorageClassSet sc = new StorageClassSet("a-certain-storage-class"); when(primaryStoreMock.isStorageClassesSupported(sc)).thenReturn(false); - + assertFalse(aggregatingContentStoreMock.isStorageClassesSupported(sc)); verify(primaryStoreMock, times(1)).isStorageClassesSupported(sc); verifyNoInteractions(secondaryStoreMock); @@ -428,7 +447,7 @@ public class AggregatingContentStoreTest extends AbstractWritableContentStoreTes .thenThrow(new UnsupportedContentUrlException(aggregatingContentStoreMock, "")); aggregatingContentStoreMock.findStorageClassesTransitions("contentUrl"); - + verify(primaryStoreMock, times(1)).findStorageClassesTransitions("contentUrl"); verifyNoInteractions(secondaryStoreMock); } diff --git a/repository/src/test/java/org/alfresco/repo/event2/AbstractContextAwareRepoEvent.java b/repository/src/test/java/org/alfresco/repo/event2/AbstractContextAwareRepoEvent.java index c5f3f9248f..995bec4cfe 100644 --- a/repository/src/test/java/org/alfresco/repo/event2/AbstractContextAwareRepoEvent.java +++ b/repository/src/test/java/org/alfresco/repo/event2/AbstractContextAwareRepoEvent.java @@ -36,6 +36,7 @@ import java.util.List; import javax.jms.ConnectionFactory; import org.alfresco.model.ContentModel; +import org.alfresco.repo.dictionary.NamespaceDAO; import org.alfresco.repo.event.v1.model.ChildAssociationResource; import org.alfresco.repo.event.v1.model.DataAttributes; import org.alfresco.repo.event.v1.model.EventType; @@ -114,6 +115,9 @@ public abstract class AbstractContextAwareRepoEvent extends BaseSpringTest @Qualifier("eventGeneratorQueue") protected EventGeneratorQueue eventQueue; + @Autowired + private NamespaceDAO namespaceDAO; + protected NodeRef rootNodeRef; @BeforeClass @@ -152,9 +156,19 @@ public abstract class AbstractContextAwareRepoEvent extends BaseSpringTest return nodeService.getRootNode(storeRef); }); + initTestNamespacePrefixMapping(); + flushSpuriousEvents(); } + protected void initTestNamespacePrefixMapping() { + if(namespaceDAO.getNamespaceURI("ce") == null) + { + namespaceDAO.addURI(TEST_NAMESPACE); + namespaceDAO.addPrefix("ce", TEST_NAMESPACE); + } + } + /* * When running with an empty database some events related to the creation may * creep up here making the test fails. After attempting several other @@ -207,11 +221,16 @@ public abstract class AbstractContextAwareRepoEvent extends BaseSpringTest } protected NodeRef createNode(QName contentType, PropertyMap propertyMap) + { + return createNode(contentType, GUID.generate(), propertyMap); + } + + protected NodeRef createNode(QName contentType, String localName, PropertyMap propertyMap) { return retryingTransactionHelper.doInTransaction(() -> nodeService.createNode( rootNodeRef, ContentModel.ASSOC_CHILDREN, - QName.createQName(TEST_NAMESPACE, GUID.generate()), + QName.createQName(TEST_NAMESPACE, localName), contentType, propertyMap).getChildRef()); } diff --git a/repository/src/test/java/org/alfresco/repo/event2/ChildAssociationRepoEventIT.java b/repository/src/test/java/org/alfresco/repo/event2/ChildAssociationRepoEventIT.java index d7db0e007a..11b6bcf77d 100644 --- a/repository/src/test/java/org/alfresco/repo/event2/ChildAssociationRepoEventIT.java +++ b/repository/src/test/java/org/alfresco/repo/event2/ChildAssociationRepoEventIT.java @@ -50,6 +50,7 @@ public class ChildAssociationRepoEventIT extends AbstractContextAwareRepoEvent @Test public void testAddChildAssociation() { + String assocLocalName = GUID.generate(); final NodeRef parentNodeRef = createNode(ContentModel.TYPE_FOLDER); final NodeRef childNodeRef = createNode(ContentModel.TYPE_CONTENT); @@ -64,11 +65,11 @@ public class ChildAssociationRepoEventIT extends AbstractContextAwareRepoEvent resultRepoEvent.getType()); retryingTransactionHelper.doInTransaction(() -> - nodeService.addChild( - parentNodeRef, - childNodeRef, - ContentModel.ASSOC_CONTAINS, - QName.createQName(TEST_NAMESPACE, GUID.generate()))); + nodeService.addChild( + parentNodeRef, + childNodeRef, + ContentModel.ASSOC_CONTAINS, + QName.createQName(TEST_NAMESPACE, assocLocalName))); List childAssociationRefs = retryingTransactionHelper.doInTransaction(() -> nodeService.getChildAssocs(parentNodeRef)); @@ -101,6 +102,7 @@ public class ChildAssociationRepoEventIT extends AbstractContextAwareRepoEvent assertEquals("Wrong parent", parentNodeRef.getId(), childAssociationResource.getParent().getId()); assertEquals("Wrong child", childNodeRef.getId(), childAssociationResource.getChild().getId()); assertEquals("Wrong assoc type", "cm:contains", childAssociationResource.getAssocType()); + assertEquals("Wrong assoc name", "ce:" + assocLocalName, childAssociationResource.getAssocQName()); } @Test diff --git a/repository/src/test/java/org/alfresco/repo/event2/CreateRepoEventIT.java b/repository/src/test/java/org/alfresco/repo/event2/CreateRepoEventIT.java index db6b592c70..1e801ad206 100644 --- a/repository/src/test/java/org/alfresco/repo/event2/CreateRepoEventIT.java +++ b/repository/src/test/java/org/alfresco/repo/event2/CreateRepoEventIT.java @@ -53,16 +53,17 @@ public class CreateRepoEventIT extends AbstractContextAwareRepoEvent @Autowired private NodeDAO nodeDAO; - + @Test public void testCreateEvent() { // Create a node without content final String name = "TestFile-" + System.currentTimeMillis() + ".txt"; + String localName = GUID.generate(); PropertyMap propertyMap = new PropertyMap(); propertyMap.put(ContentModel.PROP_TITLE, "test title"); propertyMap.put(ContentModel.PROP_NAME, name); - final NodeRef nodeRef = createNode(ContentModel.TYPE_CONTENT, propertyMap); + final NodeRef nodeRef = createNode(ContentModel.TYPE_CONTENT, localName, propertyMap); final RepoEvent> resultRepoEvent = getRepoEvent(1); // Repo event attributes @@ -103,6 +104,7 @@ public class CreateRepoEventIT extends AbstractContextAwareRepoEvent assertEquals("Wrong node modifier display name.", "Administrator", nodeResource.getModifiedByUser().getDisplayName()); assertNotNull("Missing modifiedAt property.", nodeResource.getModifiedAt()); + assertEquals("Wrong primaryAssocQName prefix.", "ce:" + localName, nodeResource.getPrimaryAssocQName()); } @Test diff --git a/repository/src/test/java/org/alfresco/repo/event2/DeleteRepoEventIT.java b/repository/src/test/java/org/alfresco/repo/event2/DeleteRepoEventIT.java index 52a69434af..d7115c6677 100644 --- a/repository/src/test/java/org/alfresco/repo/event2/DeleteRepoEventIT.java +++ b/repository/src/test/java/org/alfresco/repo/event2/DeleteRepoEventIT.java @@ -45,9 +45,10 @@ public class DeleteRepoEventIT extends AbstractContextAwareRepoEvent @Test public void testDeleteContent() { + String localName = GUID.generate(); PropertyMap propertyMap = new PropertyMap(); propertyMap.put(ContentModel.PROP_TITLE, "test title"); - NodeRef nodeRef = createNode(ContentModel.TYPE_CONTENT, propertyMap); + NodeRef nodeRef = createNode(ContentModel.TYPE_CONTENT, localName, propertyMap); NodeResource createdResource = getNodeResource(1); @@ -64,6 +65,7 @@ public class DeleteRepoEventIT extends AbstractContextAwareRepoEvent assertEquals("Repo event type:", EventType.NODE_DELETED.getType(), resultRepoEvent.getType()); assertEquals(createdResource.getId(), getNodeResource(resultRepoEvent).getId()); + assertEquals("Wrong primaryAssocQName prefix.", "ce:" + localName, createdResource.getPrimaryAssocQName()); // There should be no resourceBefore EventData eventData = getEventData(resultRepoEvent); diff --git a/repository/src/test/java/org/alfresco/repo/event2/UpdateRepoEventIT.java b/repository/src/test/java/org/alfresco/repo/event2/UpdateRepoEventIT.java index 413fa7d395..6075c2fd0c 100644 --- a/repository/src/test/java/org/alfresco/repo/event2/UpdateRepoEventIT.java +++ b/repository/src/test/java/org/alfresco/repo/event2/UpdateRepoEventIT.java @@ -33,7 +33,6 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; - import org.alfresco.model.ContentModel; import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.dictionary.M2Model; @@ -59,6 +58,7 @@ import org.junit.Test; */ public class UpdateRepoEventIT extends AbstractContextAwareRepoEvent { + @Test public void testUpdateNodeResourceContent() { @@ -138,6 +138,7 @@ public class UpdateRepoEventIT extends AbstractContextAwareRepoEvent assertNull(resourceBefore.getProperties()); assertNull(resourceBefore.getAspectNames()); assertNull(resourceBefore.getPrimaryHierarchy()); + assertNull(resourceBefore.getPrimaryAssocQName()); } @Test @@ -198,6 +199,7 @@ public class UpdateRepoEventIT extends AbstractContextAwareRepoEvent assertNull(resourceBefore.getProperties()); assertNull(resourceBefore.getAspectNames()); assertNull(resourceBefore.getPrimaryHierarchy()); + assertNull(resourceBefore.getPrimaryAssocQName()); } @Test @@ -274,6 +276,7 @@ public class UpdateRepoEventIT extends AbstractContextAwareRepoEvent assertNull(resourceBefore.getProperties()); assertNull(resourceBefore.getAspectNames()); assertNull(resourceBefore.getPrimaryHierarchy()); + assertNull(resourceBefore.getPrimaryAssocQName()); } @Test @@ -539,6 +542,7 @@ public class UpdateRepoEventIT extends AbstractContextAwareRepoEvent assertNull(resourceBefore.getProperties()); assertNull(resourceBefore.getAspectNames()); assertNull(resourceBefore.getPrimaryHierarchy()); + assertNull(resourceBefore.getPrimaryAssocQName()); } @Test @@ -574,6 +578,7 @@ public class UpdateRepoEventIT extends AbstractContextAwareRepoEvent NodeResource nodeResource = getNodeResource(resultRepoEvent); assertEquals("Incorrect node type was found", "cm:dictionaryModel", nodeResource.getNodeType()); + initTestNamespacePrefixMapping(); final NodeRef nodeRef = createNode(ContentModel.TYPE_CONTENT); // old node's type assertEquals(ContentModel.TYPE_CONTENT, nodeService.getType(nodeRef)); @@ -613,7 +618,7 @@ public class UpdateRepoEventIT extends AbstractContextAwareRepoEvent assertNull(resourceBefore.getProperties()); assertNull(resourceBefore.getAspectNames()); assertNull(resourceBefore.getPrimaryHierarchy()); - + assertNull(resourceBefore.getPrimaryAssocQName()); } @Test @@ -660,6 +665,7 @@ public class UpdateRepoEventIT extends AbstractContextAwareRepoEvent assertNull(resourceBefore.getProperties()); assertNull(resourceBefore.getAspectNames()); assertNull(resourceBefore.getPrimaryHierarchy()); + assertNull(resourceBefore.getPrimaryAssocQName()); } @Test diff --git a/repository/src/test/java/org/alfresco/repo/search/impl/querymodel/impl/db/ACS1907Test.java b/repository/src/test/java/org/alfresco/repo/search/impl/querymodel/impl/db/ACS1907Test.java new file mode 100644 index 0000000000..50232978e9 --- /dev/null +++ b/repository/src/test/java/org/alfresco/repo/search/impl/querymodel/impl/db/ACS1907Test.java @@ -0,0 +1,260 @@ +/* + * #%L + * Alfresco Repository + * %% + * 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 . + * #L% + */ +package org.alfresco.repo.search.impl.querymodel.impl.db; + +import junit.framework.TestCase; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.cache.TransactionalCache; +import org.alfresco.repo.management.subsystems.SwitchableApplicationContextFactory; +import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.MutableAuthenticationDao; +import org.alfresco.repo.security.permissions.AccessControlList; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.search.*; +import org.alfresco.service.cmr.security.MutableAuthenticationService; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.test_category.OwnJVMTestsCategory; +import org.alfresco.util.ApplicationContextHelper; +import org.alfresco.util.testing.category.DBTests; +import org.junit.experimental.categories.Category; +import org.springframework.context.ApplicationContext; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; + +@Category({OwnJVMTestsCategory.class, DBTests.class}) +public class ACS1907Test extends TestCase +{ + + private ApplicationContext ctx; + + private NodeService nodeService; + private AuthenticationComponent authenticationComponent; + private MutableAuthenticationService authenticationService; + private MutableAuthenticationDao authenticationDAO; + private SearchService pubSearchService; + private PermissionService pubPermissionService; + private TransactionService transactionService; + private RetryingTransactionHelper txnHelper; + private DBQueryEngine queryEngine; + + private TransactionalCache aclCache; + private TransactionalCache aclEntityCache; + private TransactionalCache permissionEntityCache; + + private NodeRef rootNodeRef; + + @Override + public void setUp() throws Exception + { + setupServices(); + this.authenticationComponent.setSystemUserAsCurrentUser(); + rootNodeRef = nodeService.getRootNode(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE); + setupTestUsers(); + setupTestContent(); + dropCaches(); + } + + @Override + protected void tearDown() throws Exception + { + authenticationComponent.clearCurrentSecurityContext(); + } + + private void setupServices() + { + ctx = ApplicationContextHelper.getApplicationContext(); + nodeService = (NodeService) ctx.getBean("dbNodeService"); + authenticationComponent = (AuthenticationComponent) ctx.getBean("authenticationComponent"); + authenticationService = (MutableAuthenticationService) ctx.getBean("authenticationService"); + authenticationDAO = (MutableAuthenticationDao) ctx.getBean("authenticationDao"); + pubSearchService = (SearchService) ctx.getBean("SearchService"); + pubPermissionService = (PermissionService) ctx.getBean("PermissionService"); + transactionService = (TransactionService) ctx.getBean("TransactionService"); + aclCache = (TransactionalCache) ctx.getBean("aclCache"); + aclEntityCache = (TransactionalCache) ctx.getBean("aclEntityCache"); + permissionEntityCache = (TransactionalCache) ctx.getBean("permissionEntityCache"); + SwitchableApplicationContextFactory searchContextFactory = (SwitchableApplicationContextFactory) ctx.getBean("Search"); + ApplicationContext searchCtx = searchContextFactory.getApplicationContext(); + queryEngine = (DBQueryEngine) searchCtx.getBean("search.dbQueryEngineImpl"); + txnHelper = new RetryingTransactionHelper(); + txnHelper.setTransactionService(transactionService); + txnHelper.setReadOnly(false); + txnHelper.setMaxRetries(1); + txnHelper.setMinRetryWaitMs(1); + txnHelper.setMaxRetryWaitMs(10); + txnHelper.setRetryWaitIncrementMs(1); + } + + private void setupTestUser(String userName) + { + if (!authenticationDAO.userExists(userName)) + { + authenticationService.createAuthentication(userName, userName.toCharArray()); + } + } + + private void setupTestUsers() + { + txnHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() { + @Override + public Object execute() throws Throwable { + setupTestUser("userA"); + setupTestUser("userB"); + setupTestUser(AuthenticationUtil.getAdminUserName()); + return null; + } + }, false, false); + } + + private void setupTestContent() + { + for(int f = 0; f < 5; f++) + { + final int ff = f; + txnHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() { + @Override + public Object execute() throws Throwable { + Map testFolderProps = new HashMap<>(); + testFolderProps.put(ContentModel.PROP_NAME, "folder"+ff); + NodeRef testFolder = nodeService.createNode( + rootNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName("https://example.com/test", "folder"+ff), + ContentModel.TYPE_FOLDER, + testFolderProps + ).getChildRef(); + for(int c = 0; c < 5; c++) + { + Map testContentProps = new HashMap<>(); + testContentProps.put(ContentModel.PROP_NAME, "content"+c); + NodeRef testContent = nodeService.createNode( + testFolder, + ContentModel.ASSOC_CONTAINS, + QName.createQName("https://example.com/test", "content"+c), + ContentModel.TYPE_CONTENT, + testContentProps + ).getChildRef(); + String user = c % 2 == 0 ? "userA" : "userB"; + pubPermissionService.setPermission(testContent, user, "Read", true); + } + return null; + } + }, false, false); + } + } + + private void dropCaches() + { + aclCache.clear(); + aclEntityCache.clear(); + permissionEntityCache.clear(); + } + + public void testACS1907() + { + txnHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() { + @Override + public Object execute() throws Throwable { + AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() { + @Override + public Object doWork() throws Exception { + SearchParameters sp = new SearchParameters(); + sp.addStore(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE); + sp.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO); + sp.setQueryConsistency(QueryConsistency.TRANSACTIONAL); + sp.setQuery("TYPE:\"cm:content\""); + ResultSet rs = pubSearchService.query(sp); + int cnt = 0; + for (ResultSetRow row : rs) + { + assertNotNull(row.getValue(ContentModel.PROP_NAME)); + cnt++; + } + return null; + } + }, "userA"); + return null; + } + }, false, false); + } + + public void testPaging() + { + HashSet resultPageSize2 = queryNodes(2); + HashSet resultPageSize5 = queryNodes(5); + HashSet resultPageSize10 = queryNodes(10); + HashSet resultPageSizeAll = queryNodes(10000); + // all result sets must be equal, independent of page size used to retrieve them + assertTrue(resultPageSize2.size() >= 25); + assertTrue(resultPageSize5.size() >= 25); + assertTrue(resultPageSize10.size() >= 25); + assertTrue(resultPageSizeAll.size() >= 25); + assertTrue(resultPageSize2.containsAll(resultPageSize5)); + assertTrue(resultPageSize2.containsAll(resultPageSize10)); + assertTrue(resultPageSize2.containsAll(resultPageSizeAll)); + assertTrue(resultPageSize5.containsAll(resultPageSize2)); + assertTrue(resultPageSize5.containsAll(resultPageSize10)); + assertTrue(resultPageSize5.containsAll(resultPageSizeAll)); + assertTrue(resultPageSize10.containsAll(resultPageSize2)); + assertTrue(resultPageSize10.containsAll(resultPageSize5)); + assertTrue(resultPageSize10.containsAll(resultPageSizeAll)); + assertTrue(resultPageSizeAll.containsAll(resultPageSize2)); + assertTrue(resultPageSizeAll.containsAll(resultPageSize5)); + assertTrue(resultPageSizeAll.containsAll(resultPageSize10)); + // reset + queryEngine.setMinPagingBatchSize(2500); + queryEngine.setMaxPagingBatchSize(10000); + } + + HashSet queryNodes(int pageSize) + { + queryEngine.setMinPagingBatchSize(pageSize); + queryEngine.setMaxPagingBatchSize(pageSize); + HashSet result = new HashSet<>(); + SearchParameters sp = new SearchParameters(); + sp.addStore(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE); + sp.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO); + sp.setQueryConsistency(QueryConsistency.TRANSACTIONAL); + sp.setQuery("TYPE:\"cm:content\""); + ResultSet rs = pubSearchService.query(sp); + int cnt = 0; + for (ResultSetRow row : rs) + { + result.add(row.getNodeRef()); + } + return result; + } + +} diff --git a/repository/src/test/java/org/alfresco/repo/security/authority/AuthorityServiceTest.java b/repository/src/test/java/org/alfresco/repo/security/authority/AuthorityServiceTest.java index 568a9dd526..adbaa5f8c1 100644 --- a/repository/src/test/java/org/alfresco/repo/security/authority/AuthorityServiceTest.java +++ b/repository/src/test/java/org/alfresco/repo/security/authority/AuthorityServiceTest.java @@ -25,6 +25,7 @@ */ package org.alfresco.repo.security.authority; +import static org.alfresco.repo.security.authority.AuthorityServiceImpl.GROUP_ALFRESCO_SYSTEM_ADMINISTRATORS_AUTHORITY; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -117,9 +118,10 @@ public class AuthorityServiceTest extends TestCase private static final int DEFAULT_SITE_GRP_CNT = 5; // default number of groups per site private static final int DEFAULT_SITE_ROOT_GRP_CNT = 1; // default number of root groups per site - private static final int DEFAULT_GRP_CNT = 5; // default (non-site) bootstrap groups - + private static final int DEFAULT_GRP_CNT = 6; // default (non-site) bootstrap groups - // eg. GROUP_ALFRESCO_ADMINISTRATORS, GROUP_EMAIL_CONTRIBUTORS, GROUP_SITE_ADMINISTRATORS, - // GROUP_ALFRESCO_SEARCH_ADMINISTRATORS, GROUP_ALFRESCO_MODEL_ADMINISTRATORS + // GROUP_ALFRESCO_SEARCH_ADMINISTRATORS, GROUP_ALFRESCO_MODEL_ADMINISTRATORS, + // GROUP_ALFRESCO_SYSTEM_ADMINISTRATORS private int SITE_CNT = 0; private int GRP_CNT = 0; @@ -467,8 +469,8 @@ public class AuthorityServiceTest extends TestCase assertTrue(authorityService.hasAdminAuthority()); assertTrue(pubAuthorityService.hasAdminAuthority()); Set authorities = authorityService.getAuthorities(); - // 6 => [GROUP_ALFRESCO_ADMINISTRATORS, GROUP_EMAIL_CONTRIBUTORS, GROUP_EVERYONE, GROUP_SITE_ADMINISTRATORS, ROLE_ADMINISTRATOR, GROUP_ALFRESCO_SEARCH_ADMINISTRATORS, GROUP_ALFRESCO_MODEL_ADMINISTRATORS] - assertEquals("Unexpected result: " + authorities, 7 + (SITE_CNT*2), authorityService.getAuthorities().size()); + // 8 => [GROUP_ALFRESCO_ADMINISTRATORS, GROUP_EMAIL_CONTRIBUTORS, GROUP_EVERYONE, GROUP_SITE_ADMINISTRATORS, ROLE_ADMINISTRATOR, GROUP_ALFRESCO_SEARCH_ADMINISTRATORS, GROUP_ALFRESCO_MODEL_ADMINISTRATORS, GROUP_ALFRESCO_SYSTEM_ADMINISTRATORS] + assertEquals("Unexpected result: " + authorities, 8 + (SITE_CNT*2), authorityService.getAuthorities().size()); } public void testNoUser() @@ -1773,6 +1775,46 @@ public class AuthorityServiceTest extends TestCase personService.deletePerson(username); } + public void testAdminHasSysAdminAuthority() + { + authenticationComponent.setCurrentUser(AuthenticationUtil.getAdminUserName()); + assertTrue(authorityService.hasAdminAuthority()); + assertTrue("By default, Admin should be member of Alfresco_System_Administrators group.", + pubAuthorityService.hasSysAdminAuthority()); + } + + public void testSysAdminGroup() + { + personService.getPerson("andy"); + // Make sure Andy is not part of ALFRESCO_ADMINISTRATORS group + String adminGroup = authorityService.getName(AuthorityType.GROUP, "ALFRESCO_ADMINISTRATORS"); + authorityService.removeAuthority(adminGroup, "andy"); + assertFalse(authorityService.isAdminAuthority("andy")); + + // Set the current authentication to Andy, so we can check the runAsUser + authenticationComponent.setCurrentUser("andy"); + assertFalse("Andy hasn't been added to the Alfresco_System_Administrators group yet.", + pubAuthorityService.hasSysAdminAuthority()); + + // Set the current authentication to admin in order to add Andy to the group + authenticationComponent.setCurrentUser(AuthenticationUtil.getAdminUserName()); + pubAuthorityService.addAuthority(GROUP_ALFRESCO_SYSTEM_ADMINISTRATORS_AUTHORITY, "andy"); + + // Set the current authentication to Andy, so we can check the runAsUser + authenticationComponent.setCurrentUser("andy"); + assertTrue("Andy is a member of the Alfresco_System_Administrators group", + pubAuthorityService.hasSysAdminAuthority()); + + // Set the current authentication to admin in order to remove Andy from the group + authenticationComponent.setCurrentUser(AuthenticationUtil.getAdminUserName()); + pubAuthorityService.removeAuthority(GROUP_ALFRESCO_SYSTEM_ADMINISTRATORS_AUTHORITY, "andy"); + + // Set the current authentication to Andy, so we can check the runAsUser + authenticationComponent.setCurrentUser("andy"); + assertFalse("Andy has been removed from the Alfresco_System_Administrators group.", + pubAuthorityService.hasSysAdminAuthority()); + } + private T createClassPolicy(Class policyInterface, QName policyQName, QName triggerOnClass) { T policy = mock(policyInterface); diff --git a/repository/src/test/java/org/alfresco/repo/version/ContentServiceImplTest.java b/repository/src/test/java/org/alfresco/repo/version/ContentServiceImplTest.java index fdaa0f353f..3296d98227 100644 --- a/repository/src/test/java/org/alfresco/repo/version/ContentServiceImplTest.java +++ b/repository/src/test/java/org/alfresco/repo/version/ContentServiceImplTest.java @@ -2,7 +2,7 @@ * #%L * Alfresco Repository * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited + * 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 @@ -28,8 +28,12 @@ package org.alfresco.repo.version; import java.util.Date; import java.util.Set; +import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.openMocks; + import org.alfresco.model.ContentModel; import org.alfresco.repo.content.ContentStore; +import org.alfresco.repo.content.directurl.SystemWideDirectUrlConfig; import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.ContentWriter; @@ -40,6 +44,9 @@ import org.alfresco.test_category.OwnJVMTestsCategory; import org.junit.Before; import org.junit.Test; import org.junit.experimental.categories.Category; + +import org.mockito.InjectMocks; +import org.mockito.Mock; import org.springframework.test.util.ReflectionTestUtils; import org.springframework.transaction.annotation.Transactional; @@ -51,7 +58,9 @@ import org.springframework.transaction.annotation.Transactional; @Category(OwnJVMTestsCategory.class) @Transactional public class ContentServiceImplTest extends BaseVersionStoreTest -{ +{ + private static final Boolean ENABLED = Boolean.TRUE; + /** * Test content data */ @@ -60,9 +69,14 @@ public class ContentServiceImplTest extends BaseVersionStoreTest /** * The version content store */ + @InjectMocks private ContentService contentService; + private ContentStore contentStore; + @Mock + private SystemWideDirectUrlConfig mockSystemWideDirectUrlConfig; + @Before public void before() throws Exception { @@ -133,15 +147,17 @@ public class ContentServiceImplTest extends BaseVersionStoreTest } @Test - public void testWhenGetDirectAccessUrlIsNotSupported() + public void testWhenRequestContentDirectUrlIsNotSupported() { - assertFalse(contentStore.isDirectAccessSupported()); + openMocks(this); + when(mockSystemWideDirectUrlConfig.isEnabled()).thenReturn(ENABLED); + when(mockSystemWideDirectUrlConfig.getDefaultExpiryTimeInSec()).thenReturn(30L); + when(mockSystemWideDirectUrlConfig.getMaxExpiryTimeInSec()).thenReturn(300L); + + assertFalse(contentStore.isContentDirectUrlEnabled()); // Set the presigned URL to expire after one minute. - Date expiresAt = new Date(); - long expTimeMillis = expiresAt.getTime(); - expTimeMillis += 1000 * 60; - expiresAt.setTime(expTimeMillis); + Long validFor = 60L; try { @@ -149,7 +165,7 @@ public class ContentServiceImplTest extends BaseVersionStoreTest NodeRef nodeRef = this.dbNodeService .createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("{test}MyNoContentNode"), TEST_TYPE_QNAME, this.nodeProperties).getChildRef(); - assertEquals(null, contentService.getDirectAccessUrl(nodeRef, expiresAt)); + assertNull(contentService.requestContentDirectUrl(nodeRef, true, validFor)); fail("nodeRef has no content"); } catch (IllegalArgumentException e) @@ -159,7 +175,7 @@ public class ContentServiceImplTest extends BaseVersionStoreTest try { - assertEquals(null, contentService.getDirectAccessUrl(null, null)); + assertNull(contentService.requestContentDirectUrl(null, true, null)); fail("nodeRef is null"); } catch (IllegalArgumentException e) @@ -170,8 +186,8 @@ public class ContentServiceImplTest extends BaseVersionStoreTest // Create a node with content NodeRef nodeRef = createNewVersionableNode(); - assertEquals(null, contentService.getDirectAccessUrl(nodeRef, null)); - assertEquals(null, contentService.getDirectAccessUrl(nodeRef, expiresAt)); + assertNull(contentService.requestContentDirectUrl(nodeRef, true, null)); + assertNull(contentService.requestContentDirectUrl(nodeRef, true, validFor)); } @Test diff --git a/repository/src/test/java/org/alfresco/transform/client/registry/LocalTransformServiceRegistryConfigTest.java b/repository/src/test/java/org/alfresco/transform/client/registry/LocalTransformServiceRegistryConfigTest.java index a30f2e1399..866afecb63 100644 --- a/repository/src/test/java/org/alfresco/transform/client/registry/LocalTransformServiceRegistryConfigTest.java +++ b/repository/src/test/java/org/alfresco/transform/client/registry/LocalTransformServiceRegistryConfigTest.java @@ -28,16 +28,19 @@ package org.alfresco.transform.client.registry; import com.fasterxml.jackson.databind.ObjectMapper; import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.content.transform.AbstractLocalTransform; -import org.alfresco.repo.content.transform.LocalCombinedConfig; import org.alfresco.repo.content.transform.LocalPipelineTransform; import org.alfresco.repo.content.transform.LocalTransformImpl; import org.alfresco.repo.content.transform.LocalTransformServiceRegistry; import org.alfresco.repo.content.transform.TransformerDebug; import org.alfresco.transform.client.model.config.SupportedSourceAndTarget; import org.alfresco.transform.client.model.config.TransformOption; +import org.alfresco.transform.client.model.config.TransformOptionGroup; +import org.alfresco.transform.client.model.config.TransformOptionValue; import org.alfresco.transform.client.model.config.Transformer; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.apache.log4j.Level; +import org.apache.log4j.LogManager; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; @@ -48,6 +51,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; @@ -55,7 +59,9 @@ import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; +import static java.util.Collections.emptyMap; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; @@ -63,11 +69,9 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; /** - * Extends the {@link TransformServiceRegistryConfigTest} (used to test the config received from the Transform Service) - * so that configuration for the local transformations may be tested. This includes pipelines and options specific - * transform steps. + * Testing LocalTransformServiceRegistry. */ -public class LocalTransformServiceRegistryConfigTest extends TransformServiceRegistryConfigTest +public class LocalTransformServiceRegistryConfigTest extends TransformRegistryTest { public static final String HARD_CODED_VALUE = "hard coded value"; @@ -135,6 +139,12 @@ public class LocalTransformServiceRegistryConfigTest extends TransformServiceReg super.logError(msg); } + @Override + protected void logWarn(String msg) + { + logError(msg); + } + public Data assertDataChanged(Data prevData, String msg) { // If the data changes, there has been a read @@ -169,10 +179,14 @@ public class LocalTransformServiceRegistryConfigTest extends TransformServiceReg private static Log log = LogFactory.getLog(LocalTransformServiceRegistry.class); + public static final String PNG = "image/png"; + public static final String TIFF = "image/tiff"; + + public static final ObjectMapper JSON_OBJECT_MAPPER = new ObjectMapper(); + private static final String LOCAL_TRANSFORM_SERVICE_CONFIG = "alfresco/local-transform-service-config-test.json"; private static final String LOCAL_TRANSFORM_SERVICE_CONFIG_PIPELINE = "alfresco/local-transform-service-config-pipeline-test.json"; - private static final ObjectMapper JSON_OBJECT_MAPPER = new ObjectMapper(); private static final String LOCAL_TRANSFORM = "localTransform."; private static final String URL = ".url"; @@ -202,8 +216,10 @@ public class LocalTransformServiceRegistryConfigTest extends TransformServiceReg initTestData(); super.setUp(); + LogManager.getLogger(LocalTransformServiceRegistryConfigTest.class).setLevel(Level.DEBUG); } + @Override protected LocalTransformServiceRegistry buildTransformServiceRegistryImpl() throws Exception { registry = new TestLocalTransformServiceRegistry(); @@ -222,19 +238,6 @@ public class LocalTransformServiceRegistryConfigTest extends TransformServiceReg return (System.currentTimeMillis() - startMs) + "ms: "; } - @Override - protected String getTransformServiceConfig() - { - return LOCAL_TRANSFORM_SERVICE_CONFIG; - } - - @Override - protected String getTransformServiceConfigPipeline() - { - return LOCAL_TRANSFORM_SERVICE_CONFIG_PIPELINE; - } - - @Override protected int getExpectedTransformsForTestJsonPipeline() { // imagemagick @@ -259,7 +262,7 @@ public class LocalTransformServiceRegistryConfigTest extends TransformServiceReg */ private void retrieveLocalTransformList(String path) { - CombinedConfig combinedConfig = new LocalCombinedConfig(log); + CombinedConfig combinedConfig = new CombinedConfig(log); combinedConfig.addLocalConfig(path); combinedConfig.register(registry); mapOfTransformOptions = combinedConfig.combinedTransformOptions; @@ -379,11 +382,166 @@ public class LocalTransformServiceRegistryConfigTest extends TransformServiceReg return i; } + private void register(String path) throws IOException + { + CombinedConfig combinedConfig = new CombinedConfig(log); + combinedConfig.addLocalConfig(path); + combinedConfig.register((TransformServiceRegistryImpl)registry); + } + @Test - @Override public void testJsonConfig() throws IOException { - internalTestJsonConfig(64, 70); + register(LOCAL_TRANSFORM_SERVICE_CONFIG); + + // Check the count of transforms supported + assertEquals("The number of UNIQUE source to target mimetypes transforms has changed. Config change?", + 64, countSupportedTransforms(true)); + assertEquals("The number of source to target mimetypes transforms has changed. " + + "There may be multiple transformers for the same combination. Config change?", + 70, countSupportedTransforms(false)); + + // Check a supported transform for each transformer. + assertSupported(DOC, 1234, PDF, emptyMap(), null, ""); // libreoffice + assertSupported(DOC, 1234, PDF, emptyMap(), null, ""); // libreoffice + assertSupported(PDF, 1234, PNG, emptyMap(), null, ""); // pdfrenderer + assertSupported(JPEG,1234, GIF, emptyMap(), null, ""); // imagemagick + assertSupported(MSG, 1234, TXT, emptyMap(), null, ""); // tika + assertSupported(MSG, 1234, GIF, emptyMap(), null, ""); // officeToImageViaPdf + + Map invalidPdfOptions = new HashMap<>(); + invalidPdfOptions.put("allowEnlargement", "false"); + assertSupported(DOC, 1234, PDF, invalidPdfOptions, null, "Invalid as there is a extra option"); + } + + @Test + public void testJsonPipeline() throws IOException + { + register(LOCAL_TRANSFORM_SERVICE_CONFIG_PIPELINE); + + // Check the count of transforms supported + int expectedTransforms = getExpectedTransformsForTestJsonPipeline(); + assertEquals("The number of UNIQUE source to target mimetypes transforms has changed. Config change?", + expectedTransforms, countSupportedTransforms(true)); + assertEquals("The number of source to target mimetypes transforms has changed. " + + "There may be multiple transformers for the same combination. Config change?", + expectedTransforms, countSupportedTransforms(false)); + + // Check required and optional default correctly + Map> transformsToWord = + registry.getData().getTransforms().get(DOC); + List supportedTransforms = transformsToWord.get(GIF); + SupportedTransform supportedTransform = supportedTransforms.get(0); + + Set transformOptionsSet = supportedTransform.getTransformOptions().getTransformOptions(); + + Iterator iterator = transformOptionsSet.iterator(); + assertTrue("Expected transform values", iterator.hasNext()); + // Because Set is unordered we don't know which TransformOptionGroup we retrieve + TransformOptionGroup transformOptions1 = (TransformOptionGroup)iterator.next(); + + assertTrue("Expected transform values", iterator.hasNext()); + TransformOptionGroup transformOptions2 = (TransformOptionGroup)iterator.next(); + + TransformOptionGroup imagemagick; + TransformOptionGroup pdf; + + if(containsTransformOptionValueName(transformOptions1, "alphaRemove")) + { + imagemagick = transformOptions1; + pdf = transformOptions2; + } + else + { + imagemagick = transformOptions2; + pdf = transformOptions1; + } + + TransformOptionValue alphaRemove = (TransformOptionValue)retrieveTransformOptionByPropertyName(imagemagick, "alphaRemove", "TransformOptionValue"); + TransformOptionGroup crop = (TransformOptionGroup)retrieveTransformOptionByPropertyName(imagemagick, "crop", "TransformOptionGroup"); + TransformOptionValue cropGravity = (TransformOptionValue)retrieveTransformOptionByPropertyName(crop, "cropGravity", "TransformOptionValue"); + TransformOptionValue cropWidth = (TransformOptionValue)retrieveTransformOptionByPropertyName(crop, "cropWidth", "TransformOptionValue"); + + assertTrue("The holding group should be required", supportedTransform.getTransformOptions().isRequired()); + assertFalse("imagemagick should be optional as it is not set", imagemagick.isRequired()); + assertFalse("pdf should be optional as required is not set", pdf.isRequired()); + assertEquals("alphaRemove", alphaRemove.getName()); + assertEquals("cropGravity", cropGravity.getName()); + assertEquals("cropWidth", cropWidth.getName()); + assertFalse("alphaRemove should be optional as required is not set", alphaRemove.isRequired()); + assertFalse("crop should be optional as required is not set", crop.isRequired()); + assertTrue("cropGravity should be required as it is set", cropGravity.isRequired()); + assertFalse("cropWidth should be optional as required is not set", cropWidth.isRequired()); + + // Check a supported transform for each transformer. + assertSupported(DOC,1234, GIF, emptyMap(), null, ""); + assertSupported(DOC,1234, PNG, emptyMap(), null, ""); + assertSupported(DOC,1234, JPEG, emptyMap(), null, ""); + assertSupported(DOC,1234, TIFF, emptyMap(), null, ""); + + Map actualOptions = new HashMap<>(); + actualOptions.put("thumbnail", "true"); + actualOptions.put("resizeWidth", "100"); + actualOptions.put("resizeHeight", "100"); + actualOptions.put("allowEnlargement", "false"); + actualOptions.put("maintainAspectRatio", "true"); + assertSupported(DOC,1234, PNG, actualOptions, null, ""); + } + + private TransformOption retrieveTransformOptionByPropertyName (TransformOptionGroup transformOptionGroup, String propertyName, String propertyType) + { + Iterator iterator = transformOptionGroup.getTransformOptions().iterator(); + + List transformOptionsList = new ArrayList<>(); + while(iterator.hasNext()) + { + transformOptionsList.add(iterator.next()); + } + + for (TransformOption t : transformOptionsList) + { + if (t instanceof TransformOptionValue) + { + TransformOptionValue value = (TransformOptionValue) t; + if (propertyType.equalsIgnoreCase("TransformOptionValue")) + { + if (value.getName().equalsIgnoreCase(propertyName)) + return value; + } + else + { + if (value.getName().contains(propertyName)) + return transformOptionGroup; + } + } + else + { + TransformOption result = retrieveTransformOptionByPropertyName((TransformOptionGroup)t, propertyName, propertyType); + if (result != null) + return result; + } + } + return null; + } + + private boolean containsTransformOptionValueName (TransformOptionGroup transformOptionGroup, String propertyName) + { + return retrieveTransformOptionByPropertyName(transformOptionGroup, propertyName, "TransformOptionValue") != null; + } + + private int countSupportedTransforms(boolean unique) + { + int count = 0; + int uniqueCount = 0; + for (Map> targetMap : registry.getData().getTransforms().values()) + { + for (List supportedTransforms : targetMap.values()) + { + uniqueCount++; + count += supportedTransforms.size(); + } + } + return unique ? uniqueCount : count; } @Test @@ -635,7 +793,7 @@ public class LocalTransformServiceRegistryConfigTest extends TransformServiceReg public void testNoName() { retrieveLocalTransformList("alfresco/local-transform-service-config-no-name-test.json"); - registry.assertErrorLogged("Local transformer names may not be null.*no-name-test.*"); + registry.assertErrorLogged("Transformer names may not be null.*no-name-test.*"); } @Test @@ -649,7 +807,7 @@ public class LocalTransformServiceRegistryConfigTest extends TransformServiceReg public void testTEngineDuplicateNames() { retrieveLocalTransformList("alfresco/local-transform-service-config-dup-name-test.json"); - registry.assertErrorLogged("Local T-Engine transformer .* must be a unique name.*dup-name.*"); + registry.assertErrorLogged("Transformer \"pdfrenderer\" must be a unique name.*dup-name.*"); } @Test @@ -657,21 +815,22 @@ public class LocalTransformServiceRegistryConfigTest extends TransformServiceReg { registry.setResetBaseUrl(false); retrieveLocalTransformList("alfresco/local-transform-service-config-no-base-url-test.json"); - registry.assertErrorLogged("Local T-Engine transformer .* must have its baseUrl set .*no-base-url.*"); + registry.assertErrorLogged("Single step transformers \\(such as \"pdfrenderer\"\\) must be defined in a " + + "T-Engine rather than in a pipeline file, unless they are overriding an existing single step definition.*no-base-url.*"); } @Test public void testPipelineMissingStepTransform() { retrieveLocalTransformList("alfresco/transform-service-config-pipeline-missing-step-test.json"); - registry.assertErrorLogged("Transformer .* ignored as step transforms do not exist.*pipeline-missing-step.*"); + registry.assertErrorLogged("Transformer \"missingPdfrenderer\" ignored as step transforms \\(\"pdfrenderer\"\\) do not exist.*pipeline-missing-step-test.*"); } @Test public void testFailoverMissingStepTransform() { retrieveLocalTransformList("alfresco/transform-service-config-failover-missing-step-test.json"); - registry.assertErrorLogged("Transformer .* ignored as step transforms do not exist.*failover-missing-step.*"); + registry.assertErrorLogged("Transformer \"missingPdfrenderer\" ignored as step transforms \\(\"pdfrenderer\"\\) do not exist.*failover-missing-step-test.*"); } @Test diff --git a/repository/src/test/java/org/alfresco/transform/client/registry/TransformServiceRegistryConfigTest.java b/repository/src/test/java/org/alfresco/transform/client/registry/TransformServiceRegistryConfigTest.java deleted file mode 100644 index 2ccccada69..0000000000 --- a/repository/src/test/java/org/alfresco/transform/client/registry/TransformServiceRegistryConfigTest.java +++ /dev/null @@ -1,295 +0,0 @@ -/* - * #%L - * Alfresco Repository - * %% - * Copyright (C) 2005 - 2019 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 . - * #L% - */ -package org.alfresco.transform.client.registry; - -import static java.util.Collections.emptyMap; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.alfresco.transform.client.model.config.TransformOption; -import org.alfresco.transform.client.model.config.TransformOptionGroup; -import org.alfresco.transform.client.model.config.TransformOptionValue; -import org.alfresco.transform.client.registry.SupportedTransform; -import org.alfresco.transform.client.registry.TransformRegistryTest; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.apache.log4j.Level; -import org.apache.log4j.LogManager; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import com.fasterxml.jackson.databind.ObjectMapper; - -/** - * Test the config received from the Transform Service about what it supports. - * - * @author adavis - */ -public class TransformServiceRegistryConfigTest extends TransformRegistryTest -{ - private static Log log = LogFactory.getLog(TransformServiceRegistryConfigTest.class); - - public static final String PNG = "image/png"; - public static final String TIFF = "image/tiff"; - - private static final String TRANSFORM_SERVICE_CONFIG = "alfresco/transform-service-config-test.json"; - private static final String TRANSFORM_SERVICE_CONFIG_PIPELINE = "alfresco/transform-service-config-pipeline-test.json"; - - public static final ObjectMapper JSON_OBJECT_MAPPER = new ObjectMapper(); - - @Before - @Override - public void setUp() throws Exception - { - super.setUp(); - LogManager.getLogger(TransformServiceRegistryConfigTest.class).setLevel(Level.DEBUG); - } - - @Override - protected TransformServiceRegistryImpl buildTransformServiceRegistryImpl() throws Exception - { - TransformServiceRegistryImpl registry = new TransformServiceRegistryImpl() - { - @Override - public boolean readConfig() throws IOException - { - return true; - } - - @Override - protected Log getLog() - { - return log; - } - }; - registry.setJsonObjectMapper(JSON_OBJECT_MAPPER); - registry.setCronExpression(null); // just read once - registry.afterPropertiesSet(); - return registry; - } - - @After - public void tearDown() - { - // shut down - } - - protected String getTransformServiceConfig() - { - return TRANSFORM_SERVICE_CONFIG; - } - - protected String getTransformServiceConfigPipeline() - { - return TRANSFORM_SERVICE_CONFIG_PIPELINE; - } - - private void register(String path) throws IOException - { - CombinedConfig combinedConfig = new CombinedConfig(log); - combinedConfig.addLocalConfig(path); - combinedConfig.register((TransformServiceRegistryImpl)registry); - } - - @Test - public void testJsonConfig() throws IOException - { - internalTestJsonConfig(60, 60); - } - - protected void internalTestJsonConfig(int expectedSourceTargetUniqueCount, int expectedSourceTargetCount) throws IOException - { - register(getTransformServiceConfig()); - - // Check the count of transforms supported - assertEquals("The number of UNIQUE source to target mimetypes transforms has changed. Config change?", - expectedSourceTargetUniqueCount, countSupportedTransforms(true)); - assertEquals("The number of source to target mimetypes transforms has changed. " + - "There may be multiple transformers for the same combination. Config change?", - expectedSourceTargetCount, countSupportedTransforms(false)); - - // Check a supported transform for each transformer. - assertSupported(DOC, 1234, PDF, emptyMap(), null, ""); // libreoffice - assertSupported(DOC, 1234, PDF, emptyMap(), null, ""); // libreoffice - assertSupported(PDF, 1234, PNG, emptyMap(), null, ""); // pdfrenderer - assertSupported(JPEG,1234, GIF, emptyMap(), null, ""); // imagemagick - assertSupported(MSG, 1234, TXT, emptyMap(), null, ""); // tika - assertSupported(MSG, 1234, GIF, emptyMap(), null, ""); // officeToImageViaPdf - - Map invalidPdfOptions = new HashMap<>(); - invalidPdfOptions.put("allowEnlargement", "false"); - assertSupported(DOC, 1234, PDF, invalidPdfOptions, null, "Invalid as there is a extra option"); - } - - @Test - public void testJsonPipeline() throws IOException - { - register(getTransformServiceConfigPipeline()); - - // Check the count of transforms supported - int expectedTransforms = getExpectedTransformsForTestJsonPipeline(); - assertEquals("The number of UNIQUE source to target mimetypes transforms has changed. Config change?", - expectedTransforms, countSupportedTransforms(true)); - assertEquals("The number of source to target mimetypes transforms has changed. " + - "There may be multiple transformers for the same combination. Config change?", - expectedTransforms, countSupportedTransforms(false)); - - // Check required and optional default correctly - Map> transformsToWord = - registry.getData().getTransforms().get(DOC); - List supportedTransforms = transformsToWord.get(GIF); - SupportedTransform supportedTransform = supportedTransforms.get(0); - - Set transformOptionsSet = supportedTransform.getTransformOptions().getTransformOptions(); - - Iterator iterator = transformOptionsSet.iterator(); - assertTrue("Expected transform values", iterator.hasNext()); - // Because Set is unordered we don't know which TransformOptionGroup we retrieve - TransformOptionGroup transformOptions1 = (TransformOptionGroup)iterator.next(); - - assertTrue("Expected transform values", iterator.hasNext()); - TransformOptionGroup transformOptions2 = (TransformOptionGroup)iterator.next(); - - TransformOptionGroup imagemagick; - TransformOptionGroup pdf; - - if(containsTransformOptionValueName(transformOptions1, "alphaRemove")) - { - imagemagick = transformOptions1; - pdf = transformOptions2; - } - else - { - imagemagick = transformOptions2; - pdf = transformOptions1; - } - - TransformOptionValue alphaRemove = (TransformOptionValue)retrieveTransformOptionByPropertyName(imagemagick, "alphaRemove", "TransformOptionValue"); - TransformOptionGroup crop = (TransformOptionGroup)retrieveTransformOptionByPropertyName(imagemagick, "crop", "TransformOptionGroup"); - TransformOptionValue cropGravity = (TransformOptionValue)retrieveTransformOptionByPropertyName(crop, "cropGravity", "TransformOptionValue"); - TransformOptionValue cropWidth = (TransformOptionValue)retrieveTransformOptionByPropertyName(crop, "cropWidth", "TransformOptionValue"); - - assertTrue("The holding group should be required", supportedTransform.getTransformOptions().isRequired()); - assertFalse("imagemagick should be optional as it is not set", imagemagick.isRequired()); - assertFalse("pdf should be optional as required is not set", pdf.isRequired()); - assertEquals("alphaRemove", alphaRemove.getName()); - assertEquals("cropGravity", cropGravity.getName()); - assertEquals("cropWidth", cropWidth.getName()); - assertFalse("alphaRemove should be optional as required is not set", alphaRemove.isRequired()); - assertFalse("crop should be optional as required is not set", crop.isRequired()); - assertTrue("cropGravity should be required as it is set", cropGravity.isRequired()); - assertFalse("cropWidth should be optional as required is not set", cropWidth.isRequired()); - - // Check a supported transform for each transformer. - assertSupported(DOC,1234, GIF, emptyMap(), null, ""); - assertSupported(DOC,1234, PNG, emptyMap(), null, ""); - assertSupported(DOC,1234, JPEG, emptyMap(), null, ""); - assertSupported(DOC,1234, TIFF, emptyMap(), null, ""); - - Map actualOptions = new HashMap<>(); - actualOptions.put("thumbnail", "true"); - actualOptions.put("resizeWidth", "100"); - actualOptions.put("resizeHeight", "100"); - actualOptions.put("allowEnlargement", "false"); - actualOptions.put("maintainAspectRatio", "true"); - assertSupported(DOC,1234, PNG, actualOptions, null, ""); - } - - private TransformOption retrieveTransformOptionByPropertyName (TransformOptionGroup transformOptionGroup, String propertyName, String propertyType) - { - Iterator iterator = transformOptionGroup.getTransformOptions().iterator(); - - List transformOptionsList = new ArrayList<>(); - while(iterator.hasNext()) - { - transformOptionsList.add(iterator.next()); - } - - for (TransformOption t : transformOptionsList) - { - if (t instanceof TransformOptionValue) - { - TransformOptionValue value = (TransformOptionValue) t; - if (propertyType.equalsIgnoreCase("TransformOptionValue")) - { - if (value.getName().equalsIgnoreCase(propertyName)) - return value; - } - else - { - if (value.getName().contains(propertyName)) - return transformOptionGroup; - } - } - else - { - TransformOption result = retrieveTransformOptionByPropertyName((TransformOptionGroup)t, propertyName, propertyType); - if (result != null) - return result; - } - } - return null; - } - - private boolean containsTransformOptionValueName (TransformOptionGroup transformOptionGroup, String propertyName) - { - return retrieveTransformOptionByPropertyName(transformOptionGroup, propertyName, "TransformOptionValue") != null; - } - - protected int getExpectedTransformsForTestJsonPipeline() - { -// {"sourceMediaType": "application/msword", "targetMediaType": "image/gif" }, -// {"sourceMediaType": "application/msword", "targetMediaType": "image/jpeg"}, -// {"sourceMediaType": "application/msword", "targetMediaType": "image/png" }, -// {"sourceMediaType": "application/msword", "targetMediaType": "image/tiff"} - return 4; - } - - private int countSupportedTransforms(boolean unique) - { - int count = 0; - int uniqueCount = 0; - for (Map> targetMap : registry.getData().getTransforms().values()) - { - for (List supportedTransforms : targetMap.values()) - { - uniqueCount++; - count += supportedTransforms.size(); - } - } - return unique ? uniqueCount : count; - } -} \ No newline at end of file diff --git a/repository/src/test/resources/alfresco/transform-service-config-pipeline-test.json b/repository/src/test/resources/alfresco/transform-service-config-pipeline-test.json deleted file mode 100644 index 5160301fd9..0000000000 --- a/repository/src/test/resources/alfresco/transform-service-config-pipeline-test.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "transformOptions": { - "imageMagickOptions": [ - {"value": {"name": "alphaRemove"}}, - {"value": {"name": "autoOrient"}}, - {"value": {"name": "startPage"}}, - {"value": {"name": "endPage"}}, - {"group": {"transformOptions": [ - {"value": {"required": "true", "name": "cropGravity"}}, - {"value": {"name": "cropWidth"}}, - {"value": {"name": "cropHeight"}}, - {"value": {"name": "cropPercentage"}}, - {"value": {"name": "cropXOffset"}}, - {"value": {"name": "cropYOffset"}} - ]}}, - {"group": {"transformOptions": [ - {"value": {"name": "thumbnail"}}, - {"value": {"name": "resizeHeight"}}, - {"value": {"name": "resizeWidth"}}, - {"value": {"name": "resizePercentage"}}, - {"value": {"name": "allowEnlargement"}}, - {"value": {"name": "maintainAspectRatio"}} - ]}} - ], - "pdfRendererOptions": [ - {"value": {"name": "page"}}, - {"value": {"name": "width"}}, - {"value": {"name": "height"}}, - {"value": {"name": "allowPdfEnlargement"}}, - {"value": {"name": "maintainPdfAspectRatio"}} - ] - }, - "transformers": [ - { - "supportedSourceAndTargetList": [ - {"sourceMediaType": "application/msword", "targetMediaType": "image/gif" }, - {"sourceMediaType": "application/msword", "targetMediaType": "image/jpeg"}, - {"sourceMediaType": "application/msword", "targetMediaType": "image/png" }, - {"sourceMediaType": "application/msword", "targetMediaType": "image/tiff"} - ], - "transformOptions": [ - "pdfRendererOptions", - "imageMagickOptions" - ] - } - ] -} \ No newline at end of file diff --git a/repository/src/test/resources/alfresco/transform-service-config-test.json b/repository/src/test/resources/alfresco/transform-service-config-test.json deleted file mode 100644 index 9b021fddf0..0000000000 --- a/repository/src/test/resources/alfresco/transform-service-config-test.json +++ /dev/null @@ -1,144 +0,0 @@ -{ - "transformOptions": { - "imageMagickOptions": [ - {"value": {"name": "alphaRemove"}}, - {"value": {"name": "autoOrient"}}, - {"value": {"name": "startPage"}}, - {"value": {"name": "endPage"}}, - {"group": {"transformOptions": [ - {"value": {"name": "cropGravity"}}, - {"value": {"name": "cropWidth"}}, - {"value": {"name": "cropHeight"}}, - {"value": {"name": "cropPercentage"}}, - {"value": {"name": "cropXOffset"}}, - {"value": {"name": "cropYOffset"}} - ]}}, - {"group": {"transformOptions": [ - {"value": {"name": "thumbnail"}}, - {"value": {"name": "resizeHeight"}}, - {"value": {"name": "resizeWidth"}}, - {"value": {"name": "resizePercentage"}}, - {"value": {"name": "allowEnlargement"}}, - {"value": {"name": "maintainAspectRatio"}} - ]}} - ], - "tikaOptions": [ - {"value": {"name": "transform"}}, - {"value": {"name": "includeContents"}}, - {"value": {"name": "notExtractBookmarksText"}}, - {"value": {"name": "targetMimetype"}}, - {"value": {"name": "targetEncoding"}} - ], - "pdfRendererOptions": [ - {"value": {"name": "page"}}, - {"value": {"name": "width"}}, - {"value": {"name": "height"}}, - {"value": {"name": "allowPdfEnlargement"}}, - {"value": {"name": "maintainPdfAspectRatio"}} - ] - }, - "transformers": [ - { - "supportedSourceAndTargetList": [ - {"sourceMediaType": "image/gif", "targetMediaType": "image/gif" }, - {"sourceMediaType": "image/gif", "targetMediaType": "image/jpeg"}, - {"sourceMediaType": "image/gif", "targetMediaType": "image/png" }, - {"sourceMediaType": "image/gif", "targetMediaType": "image/tiff"}, - - {"sourceMediaType": "image/jpeg", "targetMediaType": "image/gif" }, - {"sourceMediaType": "image/jpeg", "targetMediaType": "image/jpeg"}, - {"sourceMediaType": "image/jpeg", "targetMediaType": "image/png" }, - {"sourceMediaType": "image/jpeg", "targetMediaType": "image/tiff"}, - - {"sourceMediaType": "image/png", "targetMediaType": "image/gif" }, - {"sourceMediaType": "image/png", "targetMediaType": "image/jpeg"}, - {"sourceMediaType": "image/png", "targetMediaType": "image/png" }, - {"sourceMediaType": "image/png", "targetMediaType": "image/tiff"}, - - {"sourceMediaType": "image/tiff", "targetMediaType": "image/gif" }, - {"sourceMediaType": "image/tiff", "targetMediaType": "image/tiff"} - ], - "transformOptions": [ - "imageMagickOptions" - ] - }, - { - "supportedSourceAndTargetList": [ - {"sourceMediaType": "application/pdf", "maxSourceSizeBytes": 26214400, "targetMediaType": "text/plain" }, - {"sourceMediaType": "application/msword", "targetMediaType": "text/plain"}, - {"sourceMediaType": "application/vnd.ms-excel", "targetMediaType": "text/plain" }, - {"sourceMediaType": "application/vnd.ms-powerpoint", "targetMediaType": "text/plain" }, - {"sourceMediaType": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "targetMediaType": "text/plain"}, - {"sourceMediaType": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "targetMediaType": "text/plain" }, - {"sourceMediaType": "application/vnd.openxmlformats-officedocument.presentationml.presentation", "targetMediaType": "text/plain" }, - {"sourceMediaType": "application/vnd.ms-outlook", "targetMediaType": "text/plain"} - ], - "transformOptions": [ - "tikaOptions" - ] - }, - { - "supportedSourceAndTargetList": [ - {"sourceMediaType": "application/pdf", "targetMediaType": "image/png" } - ], - "transformOptions": [ - "pdfRendererOptions" - ] - }, - { - "supportedSourceAndTargetList": [ - {"sourceMediaType": "application/msword", "targetMediaType": "application/msword" }, - {"sourceMediaType": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "targetMediaType": "application/msword" }, - {"sourceMediaType": "application/msword", "maxSourceSizeBytes": 10485760, "targetMediaType": "application/pdf" }, - {"sourceMediaType": "application/vnd.ms-excel", "maxSourceSizeBytes": 10485760, "targetMediaType": "application/pdf"}, - {"sourceMediaType": "application/vnd.ms-powerpoint", "maxSourceSizeBytes": 6291456, "targetMediaType": "application/pdf" }, - {"sourceMediaType": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "maxSourceSizeBytes": 786432, "targetMediaType": "application/pdf" }, - {"sourceMediaType": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "maxSourceSizeBytes": 1572864, "targetMediaType": "application/pdf"}, - {"sourceMediaType": "application/vnd.openxmlformats-officedocument.presentationml.presentation", "maxSourceSizeBytes": 4194304, "targetMediaType": "application/pdf" }, - {"sourceMediaType": "application/vnd.ms-outlook", "targetMediaType": "application/pdf"} - ] - }, - { - "supportedSourceAndTargetList": [ - {"sourceMediaType": "application/msword", "targetMediaType": "image/gif" }, - {"sourceMediaType": "application/msword", "targetMediaType": "image/jpeg"}, - {"sourceMediaType": "application/msword", "targetMediaType": "image/png" }, - {"sourceMediaType": "application/msword", "targetMediaType": "image/tiff"}, - - {"sourceMediaType": "application/vnd.ms-excel", "targetMediaType": "image/gif" }, - {"sourceMediaType": "application/vnd.ms-excel", "targetMediaType": "image/jpeg"}, - {"sourceMediaType": "application/vnd.ms-excel", "targetMediaType": "image/png" }, - {"sourceMediaType": "application/vnd.ms-excel", "targetMediaType": "image/tiff"}, - - {"sourceMediaType": "application/vnd.ms-powerpoint", "targetMediaType": "image/gif" }, - {"sourceMediaType": "application/vnd.ms-powerpoint", "targetMediaType": "image/jpeg"}, - {"sourceMediaType": "application/vnd.ms-powerpoint", "targetMediaType": "image/png" }, - {"sourceMediaType": "application/vnd.ms-powerpoint", "targetMediaType": "image/tiff"}, - - {"sourceMediaType": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "targetMediaType": "image/gif" }, - {"sourceMediaType": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "targetMediaType": "image/jpeg"}, - {"sourceMediaType": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "targetMediaType": "image/png" }, - {"sourceMediaType": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "targetMediaType": "image/tiff"}, - - {"sourceMediaType": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "targetMediaType": "image/gif" }, - {"sourceMediaType": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "targetMediaType": "image/jpeg"}, - {"sourceMediaType": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "targetMediaType": "image/png" }, - {"sourceMediaType": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "targetMediaType": "image/tiff"}, - - {"sourceMediaType": "application/vnd.openxmlformats-officedocument.presentationml.presentation", "targetMediaType": "image/gif" }, - {"sourceMediaType": "application/vnd.openxmlformats-officedocument.presentationml.presentation", "targetMediaType": "image/jpeg"}, - {"sourceMediaType": "application/vnd.openxmlformats-officedocument.presentationml.presentation", "targetMediaType": "image/png" }, - {"sourceMediaType": "application/vnd.openxmlformats-officedocument.presentationml.presentation", "targetMediaType": "image/tiff"}, - - {"sourceMediaType": "application/vnd.ms-outlook", "targetMediaType": "image/gif" }, - {"sourceMediaType": "application/vnd.ms-outlook", "targetMediaType": "image/jpeg"}, - {"sourceMediaType": "application/vnd.ms-outlook", "targetMediaType": "image/png" }, - {"sourceMediaType": "application/vnd.ms-outlook", "targetMediaType": "image/tiff"} - ], - "transformOptions": [ - "pdfRendererOptions", - "imageMagickOptions" - ] - } - ] -} \ No newline at end of file