Merge branch 'master' into feature/ACS-1383_StorageClasses

# Conflicts:
#	data-model/src/main/java/org/alfresco/repo/content/ContentStore.java
#	repository/src/main/java/org/alfresco/repo/content/ContentServiceImpl.java
#	repository/src/main/java/org/alfresco/repo/content/caching/CachingContentStore.java
#	repository/src/main/java/org/alfresco/repo/content/replication/AggregatingContentStore.java
#	repository/src/main/java/org/alfresco/service/cmr/repository/ContentService.java
#	repository/src/main/resources/alfresco/public-services-security-context.xml
#	repository/src/test/java/org/alfresco/repo/content/replication/AggregatingContentStoreTest.java
#	repository/src/test/java/org/alfresco/repo/version/ContentServiceImplTest.java
This commit is contained in:
mpichura
2021-10-06 12:12:42 +02:00
190 changed files with 8339 additions and 4438 deletions

181
.github/dependabot.yml vendored Normal file
View File

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

View File

@@ -55,7 +55,7 @@ jobs:
before_script: 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 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 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 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" - name: "Repository - AppContext02TestSuite"
@@ -70,7 +70,7 @@ jobs:
before_script: 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 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 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 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" - name: "Repository - AppContext04TestSuite"
@@ -78,7 +78,7 @@ jobs:
before_script: 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 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 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 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" - name: "Repository - AppContext05TestSuite"
@@ -97,7 +97,7 @@ jobs:
before_script: 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 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 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 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" - name: "Repository - AppContextExtraTestSuite"
@@ -105,7 +105,7 @@ jobs:
before_script: 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 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 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 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" - name: "Repository - MiscContextTestSuite"
@@ -113,7 +113,7 @@ jobs:
before_script: 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 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 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 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" - 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 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" - 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: 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 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 - 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 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" - 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: 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 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 - 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" - name: "Repository - PostgreSQL 13.3 tests"
# We only run DB tests on the latest version of PostgreSQL on feature branches # 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: 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 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 61616:61616 -p 5672:5672 alfresco/alfresco-activemq:5.16.1
@@ -234,7 +234,7 @@ jobs:
before_script: 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 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 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 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" - name: "Remote-api - AppContext03TestSuite"
@@ -242,7 +242,7 @@ jobs:
before_script: 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 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 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 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" - name: "Remote-api - AppContext04TestSuite"
@@ -250,7 +250,7 @@ jobs:
before_script: 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 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 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 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" - name: "Remote-api - AppContextExtraTestSuite"

View File

@@ -7,7 +7,7 @@
<parent> <parent>
<groupId>org.alfresco</groupId> <groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-amps</artifactId> <artifactId>alfresco-community-repo-amps</artifactId>
<version>11.94-SNAPSHOT</version> <version>14.7-SNAPSHOT</version>
</parent> </parent>
<modules> <modules>

View File

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

View File

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

View File

@@ -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_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_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_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_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_INSTRUCTIONS;
import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentFields.PROPERTIES_RECORD_SEARCH_DISPOSITION_PERIOD; 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) @JsonProperty (PROPERTIES_RECORD_SEARCH_DISPOSITION_EVENTS_ELIGIBLE)
private Boolean recordSearchDispositionEventsEligible; private Boolean recordSearchDispositionEventsEligible;
@JsonProperty (PROPERTIES_RECORD_SEARCH_DISPOSITION_EVENTS)
private List<String> recordSearchDispositionEvents;
@JsonProperty (PROPERTIES_RECORD_SEARCH_DISPOSITION_INSTRUCTIONS) @JsonProperty (PROPERTIES_RECORD_SEARCH_DISPOSITION_INSTRUCTIONS)
private String recordSearchDispositionInstructions; private String recordSearchDispositionInstructions;

View File

@@ -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 static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentFields.PROPERTIES_VITAL_RECORD_INDICATOR;
import java.util.Date; import java.util.Date;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize;
@@ -134,7 +135,7 @@ public class RecordCategoryChildProperties extends TestModel
private String recordSearchDispositionInstructions; private String recordSearchDispositionInstructions;
@JsonProperty (PROPERTIES_RECORD_SEARCH_DISPOSITION_EVENTS) @JsonProperty (PROPERTIES_RECORD_SEARCH_DISPOSITION_EVENTS)
private Boolean recordSearchDispositionEvents; private List<String> recordSearchDispositionEvents;
@JsonProperty (PROPERTIES_OWNER) @JsonProperty (PROPERTIES_OWNER)
private Owner owner; private Owner owner;

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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<String> nodeIDList)
{
List<String> 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<String> nodeRefs)
{
final JSONObject requestParams = new JSONObject();
requestParams.put("nodeRefs", new JSONArray(nodeRefs));
return doPostJsonRequest(user, password, expectedStatusCode, requestParams, EXPORT_API);
}
}

View File

@@ -619,11 +619,27 @@ public class BaseRMRestTest extends RestTest
* @return * @return
*/ */
public List<String> searchForContentAsUser(UserModel user, String term) public List<String> 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<String> searchForContentAsUser(UserModel user, String q, String queryLanguage)
{ {
getRestAPIFactory().getRmRestWrapper().authenticateUser(user); getRestAPIFactory().getRmRestWrapper().authenticateUser(user);
RestRequestQueryModel queryReq = new RestRequestQueryModel(); RestRequestQueryModel queryReq = new RestRequestQueryModel();
SearchRequest query = new SearchRequest(queryReq); SearchRequest query = new SearchRequest(queryReq);
queryReq.setQuery("cm:name:*" + term + "*"); queryReq.setQuery(q);
queryReq.setLanguage(queryLanguage);
List<String> names = new ArrayList<>(); List<String> names = new ArrayList<>();
// wait for solr indexing // wait for solr indexing

View File

@@ -67,7 +67,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.testng.annotations.AfterClass; import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass; import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test; import org.testng.annotations.Test;
import static org.apache.commons.httpclient.HttpStatus.SC_INTERNAL_SERVER_ERROR;
/** /**
* API tests to check actions on frozen content * API tests to check actions on frozen content
* *
@@ -309,11 +309,11 @@ public class PreventActionsOnFrozenContentTests extends BaseRMRestTest
STEP("Execute the retain action"); STEP("Execute the retain action");
rmRolesAndActionsAPI.executeAction(getAdminUser().getUsername(), getAdminUser().getPassword(), record.getName(), 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"); STEP("Check the record search disposition properties");
Record recordUpdated = getRestAPIFactory().getRecordsAPI().getRecord(record.getId()); 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")); assertTrue(recordUpdated.getProperties().getRecordSearchDispositionPeriod().contains("immediately"));
} }

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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;
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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());
}
}

View File

@@ -41,6 +41,7 @@ import static org.testng.Assert.assertTrue;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.OutputStreamWriter; 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.Record;
import org.alfresco.rest.rm.community.model.record.RecordProperties; import org.alfresco.rest.rm.community.model.record.RecordProperties;
@@ -66,13 +67,19 @@ public class FilePlanComponentsUtil
// Intentionally blank // 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"; 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 "; 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"; 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 * @param nodeType The node type
* @return The {@link Record} with for the given 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) private static Record createRecordModel(String nodeType)
{ {
return Record.builder() return Record.builder()
.name("Record " + getRandomAlphanumeric()) .name("Record " + getRandomAlphanumeric())
.nodeType(nodeType) .nodeType(nodeType)
.build(); .build();
} }
/** /**
@@ -133,22 +140,22 @@ public class FilePlanComponentsUtil
/** /**
* Creates an unfiled records container child record model with the given name and type * 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 * @param nodeType The type of the record category child
* @return The {@link UnfiledContainerChild} with the given details * @return The {@link UnfiledContainerChild} with the given details
*/ */
public static UnfiledContainerChild createUnfiledContainerChildRecordModel(String name, String nodeType) public static UnfiledContainerChild createUnfiledContainerChildRecordModel(String name, String nodeType)
{ {
return UnfiledContainerChild.builder() return UnfiledContainerChild.builder()
.name(name) .name(name)
.nodeType(nodeType) .nodeType(nodeType)
.build(); .build();
} }
/** /**
* Creates a nonElectronic container child record model with all available properties for the non electronic records * 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 * @param nodeType The type of the record category child
* @return The {@link UnfiledContainerChild} with the given details * @return The {@link UnfiledContainerChild} with the given details
*/ */
@@ -156,19 +163,19 @@ public class FilePlanComponentsUtil
String shelf, String storageLocation, Integer numberOfCopies, Integer physicalSize) String shelf, String storageLocation, Integer numberOfCopies, Integer physicalSize)
{ {
return UnfiledContainerChild.builder() return UnfiledContainerChild.builder()
.name(name) .name(name)
.nodeType(NON_ELECTRONIC_RECORD_TYPE) .nodeType(NON_ELECTRONIC_RECORD_TYPE)
.properties(UnfiledContainerChildProperties.builder() .properties(UnfiledContainerChildProperties.builder()
.title(title) .title(title)
.description(description) .description(description)
.box(box) .box(box)
.file(file) .file(file)
.shelf(shelf) .shelf(shelf)
.storageLocation(storageLocation) .storageLocation(storageLocation)
.numberOfCopies(numberOfCopies) .numberOfCopies(numberOfCopies)
.physicalSize(physicalSize) .physicalSize(physicalSize)
.build()) .build())
.build(); .build();
} }
/** /**
@@ -190,110 +197,110 @@ public class FilePlanComponentsUtil
String shelf, String storageLocation, Integer numberOfCopies, Integer physicalSize) String shelf, String storageLocation, Integer numberOfCopies, Integer physicalSize)
{ {
return Record.builder() return Record.builder()
.name(name) .name(name)
.nodeType(NON_ELECTRONIC_RECORD_TYPE) .nodeType(NON_ELECTRONIC_RECORD_TYPE)
.properties(RecordProperties.builder() .properties(RecordProperties.builder()
.title(title) .title(title)
.description(description) .description(description)
.box(box) .box(box)
.file(file) .file(file)
.shelf(shelf) .shelf(shelf)
.storageLocation(storageLocation) .storageLocation(storageLocation)
.numberOfCopies(numberOfCopies) .numberOfCopies(numberOfCopies)
.physicalSize(physicalSize) .physicalSize(physicalSize)
.build()) .build())
.build(); .build();
} }
/** /**
* Creates a record model with the given name, description and title * 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 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 * @return The {@link Record} with the given details
*/ */
public static Record createRecordModel(String name, String description, String title) public static Record createRecordModel(String name, String description, String title)
{ {
return Record.builder() return Record.builder()
.name(name) .name(name)
.properties(RecordProperties.builder() .properties(RecordProperties.builder()
.description(description) .description(description)
.title(title) .title(title)
.build()) .build())
.build(); .build();
} }
/** /**
* Creates a record category child model with the given name and type * 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 * @param nodeType The type of the record category child
* @return The {@link RecordCategoryChild} with the given details * @return The {@link RecordCategoryChild} with the given details
*/ */
public static RecordCategoryChild createRecordCategoryChildModel(String name, String nodeType) public static RecordCategoryChild createRecordCategoryChildModel(String name, String nodeType)
{ {
return RecordCategoryChild.builder() return RecordCategoryChild.builder()
.name(name) .name(name)
.nodeType(nodeType) .nodeType(nodeType)
.properties(RecordCategoryChildProperties.builder() .properties(RecordCategoryChildProperties.builder()
.title(TITLE_PREFIX + name) .title(TITLE_PREFIX + name)
.build()) .build())
.build(); .build();
} }
/** /**
* Creates a record category model with the given name and title * 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 * @param title The title of the record category
* @return The {@link RecordCategory} with the given details * @return The {@link RecordCategory} with the given details
*/ */
public static RecordCategory createRecordCategoryModel(String name, String title) public static RecordCategory createRecordCategoryModel(String name, String title)
{ {
return RecordCategory.builder() return RecordCategory.builder()
.name(name) .name(name)
.nodeType(RECORD_CATEGORY_TYPE) .nodeType(RECORD_CATEGORY_TYPE)
.properties(RecordCategoryProperties.builder() .properties(RecordCategoryProperties.builder()
.title(title) .title(title)
.build()) .build())
.build(); .build();
} }
/** /**
* Creates a record folder model with the given name and title * 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 * @param title The title of the record folder
* @return The {@link RecordFolder} with the given details * @return The {@link RecordFolder} with the given details
*/ */
public static RecordFolder createRecordFolderModel(String name, String title) public static RecordFolder createRecordFolderModel(String name, String title)
{ {
return RecordFolder.builder() return RecordFolder.builder()
.name(name) .name(name)
.nodeType(RECORD_FOLDER_TYPE) .nodeType(RECORD_FOLDER_TYPE)
.properties(RecordFolderProperties.builder() .properties(RecordFolderProperties.builder()
.title(title) .title(title)
.build()) .build())
.build(); .build();
} }
/** /**
* Creates an unfiled records container child model with the given name and type * 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 * @param nodeType The type of the record category child
* @return The {@link UnfiledContainerChild} with the given details * @return The {@link UnfiledContainerChild} with the given details
*/ */
public static UnfiledContainerChild createUnfiledContainerChildModel(String name, String nodeType) public static UnfiledContainerChild createUnfiledContainerChildModel(String name, String nodeType)
{ {
return UnfiledContainerChild.builder() return UnfiledContainerChild.builder()
.name(name) .name(name)
.nodeType(nodeType) .nodeType(nodeType)
.properties(UnfiledContainerChildProperties.builder() .properties(UnfiledContainerChildProperties.builder()
.title(TITLE_PREFIX + name) .title(TITLE_PREFIX + name)
.build()) .build())
.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 * Helper method to verify all properties of a nonElectronic record
* *

View File

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

View File

@@ -1,4 +1,4 @@
TRANSFORMERS_TAG=2.5.2 TRANSFORMERS_TAG=2.5.3
SOLR6_TAG=2.0.2-RC1 SOLR6_TAG=2.0.2-RC1
POSTGRES_TAG=13.3 POSTGRES_TAG=13.3
ACTIVEMQ_TAG=5.16.1 ACTIVEMQ_TAG=5.16.1

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -80,9 +80,7 @@
<property name="searchService" ref="searchService" /> <property name="searchService" ref="searchService" />
<property name="personService" ref="personService" /> <property name="personService" ref="personService" />
<property name="recordsManagementActionService" ref="recordsManagementActionService" /> <property name="recordsManagementActionService" ref="recordsManagementActionService" />
<property name="recordFolderService" ref="RecordFolderService" /> <property name="freezeService" ref="freezeService"/>
<property name="recordService" ref="RecordService" />
<property name="freezeService" ref="FreezeService" />
<property name="batchSize" value="${rm.dispositionlifecycletrigger.batchsize}"/> <property name="batchSize" value="${rm.dispositionlifecycletrigger.batchsize}"/>
</bean> </bean>

View File

@@ -102,9 +102,11 @@
<bean id="rma.unfiledRecordFolder" class="org.alfresco.module.org_alfresco_module_rm.model.rma.type.UnfiledRecordFolderType" parent="rm.baseBehaviour" /> <bean id="rma.unfiledRecordFolder" class="org.alfresco.module.org_alfresco_module_rm.model.rma.type.UnfiledRecordFolderType" parent="rm.baseBehaviour" />
<bean id="rma.recordCategory" class="org.alfresco.module.org_alfresco_module_rm.model.rma.type.RecordCategoryType" parent="rm.baseBehaviour"> <bean id="rma.recordCategory" class="org.alfresco.module.org_alfresco_module_rm.model.rma.type.RecordCategoryType" parent="rm.baseBehaviour">
<property name="recordService" ref="RecordService" />
<property name="vitalRecordService" ref="VitalRecordService" /> <property name="vitalRecordService" ref="VitalRecordService" />
<property name="filePlanPermissionService" ref="FilePlanPermissionService" /> <property name="filePlanPermissionService" ref="FilePlanPermissionService" />
<property name="recordFolderService" ref="RecordFolderService" /> <property name="recordFolderService" ref="RecordFolderService" />
<property name="dispositionService" ref="DispositionService" />
</bean> </bean>
<bean id="rma.recordFolder" class="org.alfresco.module.org_alfresco_module_rm.model.rma.type.RecordFolderType" parent="rm.baseBehaviour"> <bean id="rma.recordFolder" class="org.alfresco.module.org_alfresco_module_rm.model.rma.type.RecordFolderType" parent="rm.baseBehaviour">

View File

@@ -695,6 +695,13 @@
init-method="init" depends-on="org_alfresco_module_rm_resourceBundles"> init-method="init" depends-on="org_alfresco_module_rm_resourceBundles">
<property name="policyComponent" ref="policyComponent"/> <property name="policyComponent" ref="policyComponent"/>
<property name="nodeService" ref="nodeService"/> <property name="nodeService" ref="nodeService"/>
<!-- list of disposition actions to automatically execute when eligible -->
<property name="retentionActions">
<list>
<value>retain</value>
</list>
</property>
<property name="freezeService" ref="freezeService"/>
</bean> </bean>
<bean id="RecordsManagementActionService" class="org.springframework.aop.framework.ProxyFactoryBean"> <bean id="RecordsManagementActionService" class="org.springframework.aop.framework.ProxyFactoryBean">
@@ -899,6 +906,8 @@
<bean id="freezeService" class="org.alfresco.module.org_alfresco_module_rm.freeze.FreezeServiceImpl" parent="baseService"> <bean id="freezeService" class="org.alfresco.module.org_alfresco_module_rm.freeze.FreezeServiceImpl" parent="baseService">
<property name="filePlanService" ref="FilePlanService" /> <property name="filePlanService" ref="FilePlanService" />
<property name="holdService" ref="HoldService" /> <property name="holdService" ref="HoldService" />
<property name="recordFolderService" ref="RecordFolderService"/>
<property name="recordService" ref="RecordService"/>
</bean> </bean>
<bean id="FreezeService" class="org.springframework.aop.framework.ProxyFactoryBean"> <bean id="FreezeService" class="org.springframework.aop.framework.ProxyFactoryBean">
@@ -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.hasFrozenChildren=RM_ALLOW
org.alfresco.module.org_alfresco_module_rm.freeze.FreezeService.getFreezeDate=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.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 org.alfresco.module.org_alfresco_module_rm.freeze.FreezeService.*=RM_DENY
]]> ]]>
</value> </value>

View File

@@ -5,7 +5,7 @@ version: "3"
services: services:
alfresco: alfresco:
# acs repo community image with ags repo community amp applied # 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: environment:
CATALINA_OPTS : " CATALINA_OPTS : "
-agentlib:jdwp=transport=dt_socket,address=8000,server=y,suspend=n -agentlib:jdwp=transport=dt_socket,address=8000,server=y,suspend=n

View File

@@ -8,13 +8,13 @@
<parent> <parent>
<groupId>org.alfresco</groupId> <groupId>org.alfresco</groupId>
<artifactId>alfresco-governance-services-community-repo-parent</artifactId> <artifactId>alfresco-governance-services-community-repo-parent</artifactId>
<version>11.94-SNAPSHOT</version> <version>14.7-SNAPSHOT</version>
</parent> </parent>
<properties> <properties>
<app.amp.client.war.folder>${project.build.directory}/${project.build.finalName}-war</app.amp.client.war.folder> <app.amp.client.war.folder>${project.build.directory}/${project.build.finalName}-war</app.amp.client.war.folder>
<image.name>alfresco/alfresco-governance-repository-community</image.name> <image.name>alfresco/alfresco-governance-repository-community-base</image.name>
</properties> </properties>
<dependencies> <dependencies>
@@ -141,6 +141,11 @@
<artifactId>spring-test</artifactId> <artifactId>spring-test</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@@ -35,17 +35,17 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import lombok.extern.slf4j.Slf4j;
import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.module.org_alfresco_module_rm.RecordsManagementPolicies.BeforeRMActionExecution; 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.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.module.org_alfresco_module_rm.util.PoliciesUtil;
import org.alfresco.repo.policy.ClassPolicyDelegate; import org.alfresco.repo.policy.ClassPolicyDelegate;
import org.alfresco.repo.policy.PolicyComponent; import org.alfresco.repo.policy.PolicyComponent;
import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.namespace.QName; 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; import org.springframework.extensions.surf.util.I18NUtil;
/** /**
@@ -53,14 +53,13 @@ import org.springframework.extensions.surf.util.I18NUtil;
* *
* @author Roy Wetherall * @author Roy Wetherall
*/ */
@Slf4j
public class RecordsManagementActionServiceImpl implements RecordsManagementActionService public class RecordsManagementActionServiceImpl implements RecordsManagementActionService
{ {
/** I18N */ /** I18N */
private static final String MSG_NOT_DEFINED = "rm.action.not-defined"; private static final String MSG_NOT_DEFINED = "rm.action.not-defined";
private static final String MSG_NO_IMPLICIT_NODEREF = "rm.action.no-implicit-noderef"; private static final String MSG_NO_IMPLICIT_NODEREF = "rm.action.no-implicit-noderef";
private static final String MSG_NODE_FROZEN = "rm.action.node.frozen.error-message";
/** Logger */
private static Log logger = LogFactory.getLog(RecordsManagementActionServiceImpl.class);
/** Registered records management actions */ /** Registered records management actions */
private Map<String, RecordsManagementAction> rmActions = new HashMap<>(13); private Map<String, RecordsManagementAction> rmActions = new HashMap<>(13);
@@ -78,6 +77,16 @@ public class RecordsManagementActionServiceImpl implements RecordsManagementActi
private ClassPolicyDelegate<BeforeRMActionExecution> beforeRMActionExecutionDelegate; private ClassPolicyDelegate<BeforeRMActionExecution> beforeRMActionExecutionDelegate;
private ClassPolicyDelegate<OnRMActionExecution> onRMActionExecutionDelegate; private ClassPolicyDelegate<OnRMActionExecution> onRMActionExecutionDelegate;
/**
* Freeze Service
*/
private FreezeService freezeService;
/**
* list of retention actions to automatically execute
*/
private List<String> retentionActions;
/** /**
* @return Policy component * @return Policy component
*/ */
@@ -94,6 +103,19 @@ public class RecordsManagementActionServiceImpl implements RecordsManagementActi
return this.nodeService; return this.nodeService;
} }
/**
* @param freezeService freeze service
*/
public void setFreezeService(FreezeService freezeService)
{
this.freezeService = freezeService;
}
public void setRetentionActions(List<String> retentionActions)
{
this.retentionActions = retentionActions;
}
/** /**
* Set the policy component * Set the policy component
* *
@@ -267,21 +289,23 @@ public class RecordsManagementActionServiceImpl implements RecordsManagementActi
*/ */
public RecordsManagementActionResult executeRecordsManagementAction(NodeRef nodeRef, String name, Map<String, Serializable> parameters) public RecordsManagementActionResult executeRecordsManagementAction(NodeRef nodeRef, String name, Map<String, Serializable> parameters)
{ {
if (logger.isDebugEnabled()) log.debug("Executing record management action on " + nodeRef);
{ log.debug(" actionName = " + name);
logger.debug("Executing record management action on " + nodeRef); log.debug(" parameters = " + parameters);
logger.debug(" actionName = " + name);
logger.debug(" parameters = " + parameters);
}
RecordsManagementAction rmAction = this.rmActions.get(name); RecordsManagementAction rmAction = this.rmActions.get(name);
if (rmAction == null) if (rmAction == null)
{ {
String msg = I18NUtil.getMessage(MSG_NOT_DEFINED, name); String msg = I18NUtil.getMessage(MSG_NOT_DEFINED, name);
if (logger.isWarnEnabled()) log.warn(msg);
{ throw new AlfrescoRuntimeException(msg);
logger.warn(msg); }
}
if (retentionActions.contains(name.toLowerCase()) && freezeService.isFrozenOrHasFrozenChildren(nodeRef))
{
String msg = I18NUtil.getMessage(MSG_NODE_FROZEN, name);
log.debug(msg);
throw new AlfrescoRuntimeException(msg); throw new AlfrescoRuntimeException(msg);
} }
@@ -307,10 +331,7 @@ public class RecordsManagementActionServiceImpl implements RecordsManagementActi
if (implicitTargetNode == null) if (implicitTargetNode == null)
{ {
String msg = I18NUtil.getMessage(MSG_NO_IMPLICIT_NODEREF, name); String msg = I18NUtil.getMessage(MSG_NO_IMPLICIT_NODEREF, name);
if (logger.isWarnEnabled()) log.warn(msg);
{
logger.warn(msg);
}
throw new AlfrescoRuntimeException(msg); throw new AlfrescoRuntimeException(msg);
} }
else else

View File

@@ -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); return !nodeService.hasAspect(nodeRef, RecordsManagementModel.ASPECT_FILE_PLAN_COMPONENT);

View File

@@ -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.event.RecordsManagementEvent;
import org.alfresco.module.org_alfresco_module_rm.fileplan.FilePlanComponentKind; 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.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.model.RecordsManagementModel;
import org.alfresco.module.org_alfresco_module_rm.record.RecordService; 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.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.security.authentication.AuthenticationUtil.RunAsWork;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ChildAssociationRef;

View File

@@ -150,4 +150,12 @@ public interface FreezeService
*/ */
@Deprecated @Deprecated
Set<NodeRef> getHolds(NodeRef filePlan); Set<NodeRef> 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);
} }

View File

@@ -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.fileplan.FilePlanService;
import org.alfresco.module.org_alfresco_module_rm.hold.HoldService; 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.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.module.org_alfresco_module_rm.util.ServiceBaseImpl;
import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
@@ -75,6 +77,32 @@ public class FreezeServiceImpl extends ServiceBaseImpl
/** Hold service */ /** Hold service */
private HoldService holdService; 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 * @return File plan service
*/ */
@@ -392,4 +420,24 @@ public class FreezeServiceImpl extends ServiceBaseImpl
// create hold // create hold
return getHoldService().createHold(filePlan, holdName, reason, null); 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 <code>true</code> if the node is frozen or has frozen children, <code>false</code> 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;
}
} }

View File

@@ -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 static org.alfresco.module.org_alfresco_module_rm.action.RMDispositionActionExecuterAbstractBase.PARAM_NO_ERROR_CHECK;
import java.io.Serializable; import java.io.Serializable;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.module.org_alfresco_module_rm.action.RecordsManagementActionService; 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.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.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.NodeRef; 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.SearchParameters;
import org.alfresco.service.cmr.search.SearchService; import org.alfresco.service.cmr.search.SearchService;
import org.alfresco.service.cmr.security.PersonService; import org.alfresco.service.cmr.security.PersonService;
import org.apache.commons.logging.Log; import org.springframework.extensions.surf.util.I18NUtil;
import org.apache.commons.logging.LogFactory;
/** /**
* The Disposition Lifecycle Job Finds all disposition action nodes which are for disposition actions specified Where * 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 mrogers
* @author Roy Wetherall * @author Roy Wetherall
*/ */
@Slf4j
public class DispositionLifecycleJobExecuter extends RecordsManagementJobExecuter public class DispositionLifecycleJobExecuter extends RecordsManagementJobExecuter
{ {
/** logger */
private static Log logger = LogFactory.getLog(DispositionLifecycleJobExecuter.class);
/** batching properties */ /** batching properties */
private int batchSize; private int batchSize;
public static final int DEFAULT_BATCH_SIZE = 500; 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 */ /** list of disposition actions to automatically execute */
private List<String> dispositionActions; private List<String> dispositionActions;
@@ -88,11 +87,13 @@ public class DispositionLifecycleJobExecuter extends RecordsManagementJobExecute
/** freeze service */ /** freeze service */
private FreezeService freezeService; private FreezeService freezeService;
/** record service */ /**
private RecordService recordService; * @param freezeService freeze service
*/
/** record folder service */ public void setFreezeService(FreezeService freezeService)
private RecordFolderService recordFolderService; {
this.freezeService = freezeService;
}
/** /**
* List of disposition actions to automatically execute when eligible. * List of disposition actions to automatically execute when eligible.
@@ -133,30 +134,6 @@ public class DispositionLifecycleJobExecuter extends RecordsManagementJobExecute
this.searchService = searchService; 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. * Get the search query string.
* *
@@ -207,11 +184,11 @@ public class DispositionLifecycleJobExecuter extends RecordsManagementJobExecute
{ {
try try
{ {
logger.debug("Job Starting"); log.debug("Job Starting");
if (dispositionActions == null || dispositionActions.isEmpty()) if (dispositionActions == null || dispositionActions.isEmpty())
{ {
logger.debug("Job Finished as disposition action is empty"); log.debug("Job Finished as disposition action is empty");
return; return;
} }
@@ -220,16 +197,11 @@ public class DispositionLifecycleJobExecuter extends RecordsManagementJobExecute
if (batchSize < 1) if (batchSize < 1)
{ {
if (logger.isDebugEnabled()) log.debug("Invalid value for batch size: " + batchSize + " default value used instead.");
{
logger.debug("Invalid value for batch size: " + batchSize + " default value used instead.");
}
batchSize = DEFAULT_BATCH_SIZE; batchSize = DEFAULT_BATCH_SIZE;
} }
if (logger.isTraceEnabled())
{ log.trace("Using batch size of " + batchSize);
logger.trace("Using batch size of " + batchSize);
}
while (hasMore) while (hasMore)
{ {
@@ -247,10 +219,7 @@ public class DispositionLifecycleJobExecuter extends RecordsManagementJobExecute
skipCount += resultNodes.size(); // increase by page size skipCount += resultNodes.size(); // increase by page size
results.close(); results.close();
if (logger.isDebugEnabled()) log.debug("Processing " + resultNodes.size() + " nodes");
{
logger.debug("Processing " + resultNodes.size() + " nodes");
}
// process search results // process search results
if (!resultNodes.isEmpty()) if (!resultNodes.isEmpty())
@@ -258,14 +227,11 @@ public class DispositionLifecycleJobExecuter extends RecordsManagementJobExecute
executeAction(resultNodes); executeAction(resultNodes);
} }
} }
logger.debug("Job Finished"); log.debug("Job Finished");
} }
catch (AlfrescoRuntimeException exception) catch (AlfrescoRuntimeException exception)
{ {
if (logger.isDebugEnabled()) log.debug(exception.getMessage());
{
logger.debug(exception);
}
} }
} }
@@ -299,12 +265,9 @@ public class DispositionLifecycleJobExecuter extends RecordsManagementJobExecute
} }
Map<String, Serializable> props = Map.of(PARAM_NO_ERROR_CHECK, false); Map<String, Serializable> props = Map.of(PARAM_NO_ERROR_CHECK, false);
if (isFrozenOrHasFrozenChildren(parent.getParentRef())) if (freezeService.isFrozenOrHasFrozenChildren(parent.getParentRef()))
{ {
if (logger.isDebugEnabled()) { log.debug(I18NUtil.getMessage(MSG_NODE_FROZEN, dispAction));
logger.debug("unable to perform action " + dispAction +
" because node is frozen or has frozen children");
}
continue; continue;
} }
@@ -314,17 +277,13 @@ public class DispositionLifecycleJobExecuter extends RecordsManagementJobExecute
recordsManagementActionService recordsManagementActionService
.executeRecordsManagementAction(parent.getParentRef(), dispAction, props); .executeRecordsManagementAction(parent.getParentRef(), dispAction, props);
if (logger.isDebugEnabled()) log.debug("Processed action: " + dispAction + "on" + parent);
{
logger.debug("Processed action: " + dispAction + "on" + parent);
}
} }
catch (AlfrescoRuntimeException exception) catch (AlfrescoRuntimeException exception)
{ {
if (logger.isDebugEnabled()) log.debug(exception.getMessage());
{
logger.debug(exception);
}
} }
} }
return Boolean.TRUE; return Boolean.TRUE;
@@ -332,25 +291,6 @@ public class DispositionLifecycleJobExecuter extends RecordsManagementJobExecute
retryingTransactionHelper.doInTransaction(processTranCB, false, true); 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 <code>true</code> if the node is frozen or has frozen children, <code>false</code> 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() public PersonService getPersonService()
{ {
return personService; return personService;

View File

@@ -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.DispositionAction;
import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService; 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.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.NodeRef;
import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.QName;
@@ -53,6 +57,12 @@ public abstract class AbstractDisposableItem extends BaseBehaviourBean
/** disposition service */ /** disposition service */
protected DispositionService dispositionService; protected DispositionService dispositionService;
/** record service */
protected RecordService recordService;
/** record folder service */
protected RecordFolderService recordFolderService;
/** /**
* @param dispositionService disposition service * @param dispositionService disposition service
*/ */
@@ -61,6 +71,22 @@ public abstract class AbstractDisposableItem extends BaseBehaviourBean
this.dispositionService = dispositionService; 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 * 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());
}
} }

View File

@@ -27,13 +27,14 @@
package org.alfresco.module.org_alfresco_module_rm.model.rma.type; 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.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import org.alfresco.model.ContentModel; import org.alfresco.model.ContentModel;
import org.alfresco.module.org_alfresco_module_rm.model.BaseBehaviourBean; import org.alfresco.module.org_alfresco_module_rm.model.behaviour.AbstractDisposableItem;
import org.alfresco.module.org_alfresco_module_rm.recordfolder.RecordFolderService;
import org.alfresco.module.org_alfresco_module_rm.security.FilePlanPermissionService; import org.alfresco.module.org_alfresco_module_rm.security.FilePlanPermissionService;
import org.alfresco.module.org_alfresco_module_rm.vital.VitalRecordService; import org.alfresco.module.org_alfresco_module_rm.vital.VitalRecordService;
import org.alfresco.repo.copy.CopyBehaviourCallback; 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.ChildAssociationRef;
import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.QName;
import org.alfresco.service.namespace.RegexQNamePattern;
/** /**
* rma:recordCategory behaviour bean * rma:recordCategory behaviour bean
@@ -60,9 +62,10 @@ import org.alfresco.service.namespace.QName;
( (
defaultType = "rma:recordCategory" defaultType = "rma:recordCategory"
) )
public class RecordCategoryType extends BaseBehaviourBean public class RecordCategoryType extends AbstractDisposableItem
implements NodeServicePolicies.OnCreateChildAssociationPolicy, implements NodeServicePolicies.OnCreateChildAssociationPolicy,
NodeServicePolicies.OnCreateNodePolicy NodeServicePolicies.OnCreateNodePolicy,
NodeServicePolicies.OnMoveNodePolicy
{ {
private final static List<QName> ACCEPTED_UNIQUE_CHILD_TYPES = new ArrayList<>(); private final static List<QName> ACCEPTED_UNIQUE_CHILD_TYPES = new ArrayList<>();
private final static List<QName> ACCEPTED_NON_UNIQUE_CHILD_TYPES = Arrays.asList(TYPE_RECORD_CATEGORY, TYPE_RECORD_FOLDER); private final static List<QName> 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 */ /** file plan permission service */
protected FilePlanPermissionService filePlanPermissionService; protected FilePlanPermissionService filePlanPermissionService;
/** record folder service */
private RecordFolderService recordFolderService;
/** /**
* @param vitalRecordService vital record service * @param vitalRecordService vital record service
*/ */
@@ -92,14 +92,6 @@ public class RecordCategoryType extends BaseBehaviourBean
this.filePlanPermissionService = filePlanPermissionService; this.filePlanPermissionService = filePlanPermissionService;
} }
/**
* @param recordFolderService record folder service
*/
public void setRecordFolderService(RecordFolderService recordFolderService)
{
this.recordFolderService = recordFolderService;
}
/** /**
* On every event * 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 * Copy callback for record category
*/ */

View File

@@ -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.identifier.IdentifierService;
import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel; 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.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.module.org_alfresco_module_rm.vital.VitalRecordService;
import org.alfresco.repo.copy.CopyBehaviourCallback; import org.alfresco.repo.copy.CopyBehaviourCallback;
import org.alfresco.repo.copy.CopyDetails; import org.alfresco.repo.copy.CopyDetails;
@@ -68,11 +66,6 @@ public class RecordFolderType extends AbstractDisposableItem
implements NodeServicePolicies.OnMoveNodePolicy, implements NodeServicePolicies.OnMoveNodePolicy,
NodeServicePolicies.OnCreateChildAssociationPolicy NodeServicePolicies.OnCreateChildAssociationPolicy
{ {
/** record service */
private RecordService recordService;
/** record folder service */
private RecordFolderService recordFolderService;
/** vital record service */ /** vital record service */
protected VitalRecordService vitalRecordService; 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"; 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 * @param vitalRecordService vital record service
*/ */
@@ -131,31 +108,7 @@ public class RecordFolderType extends AbstractDisposableItem
{ {
if (!oldChildAssocRef.getParentRef().equals(newChildAssocRef.getParentRef())) if (!oldChildAssocRef.getParentRef().equals(newChildAssocRef.getParentRef()))
{ {
final NodeRef newNodeRef = newChildAssocRef.getChildRef(); reinitializeRecordFolder(newChildAssocRef);
AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork<Object>()
{
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());
} }
} }
else else

View File

@@ -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_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_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 static org.alfresco.util.GUID.generate;
import java.io.Serializable; import java.io.Serializable;
@@ -37,6 +39,7 @@ import java.util.List;
import java.util.Map; 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.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.action.impl.RetainAction;
import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionAction; import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionAction;
import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionSchedule; 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
* <p>
* 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<String>) 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
* <p>
* 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<String>) nodeService.getProperty(record, PROP_RS_DISPOSITION_EVENTS));
assertEquals(((List<String>) ((List<String>) nodeService.getProperty(record,
PROP_RS_DISPOSITION_EVENTS))).size(), 1);
assertEquals(DEFAULT_EVENT_NAME, ((List<String>) ((List<String>) 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) private void createDispositionScheduleCutOff(NodeRef category, String action, String period)
{ {
DispositionSchedule ds = utils.createDispositionSchedule(category, DEFAULT_DISPOSITION_INSTRUCTIONS, DEFAULT_DISPOSITION_DESCRIPTION, true, false, false); 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); 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) private void createDispositionScheduleStep(DispositionSchedule ds, String action, String period)
{ {
Map<QName, Serializable> step = new HashMap<QName, Serializable>(3); Map<QName, Serializable> step = new HashMap<QName, Serializable>(3);

View File

@@ -265,8 +265,38 @@ public class FreezeServiceImplTest extends BaseRMTestCase
assertFalse(freezeService.isFrozen(recordFour)); assertFalse(freezeService.isFrozen(recordFour));
// assertFalse(freezeService.hasFrozenChildren(rmFolder)); // assertFalse(freezeService.hasFrozenChildren(rmFolder));
return null; return null;
} }
}); });
}
doTestInTransaction(new Test<Void>()
{
@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<Void>()
{
@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;
}
});
}
} }

View File

@@ -82,8 +82,10 @@ public class CommonRMTestUtils implements RecordsManagementModel
public static final String DEFAULT_DISPOSITION_INSTRUCTIONS = "disposition instructions"; public static final String DEFAULT_DISPOSITION_INSTRUCTIONS = "disposition instructions";
public static final String DEFAULT_DISPOSITION_DESCRIPTION = "disposition action description"; public static final String DEFAULT_DISPOSITION_DESCRIPTION = "disposition action description";
public static final String DEFAULT_EVENT_NAME = "case_closed"; 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_NONE = "none|0";
public static final String PERIOD_IMMEDIATELY = "immediately|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_FIVE_DAYS = "day|5";
public static final String PERIOD_TEN_DAYS = "day|10"; public static final String PERIOD_TEN_DAYS = "day|10";
public static final String PERIOD_ONE_WEEK = "week|1"; public static final String PERIOD_ONE_WEEK = "week|1";

View File

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

5
amps/module-info.java Normal file
View File

@@ -0,0 +1,5 @@
module simple.lombok {
requires static lombok;
requires java.logging;
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,7 +2,7 @@
* #%L * #%L
* Alfresco Data model classes * 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. * This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of * 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.ContentWriter;
import org.alfresco.service.cmr.repository.DirectAccessUrl; import org.alfresco.service.cmr.repository.DirectAccessUrl;
/** /**
* Provides low-level retrieval of content * Provides low-level retrieval of content
* {@link org.alfresco.service.cmr.repository.ContentReader readers} and * {@link org.alfresco.service.cmr.repository.ContentReader readers} and
@@ -259,30 +260,58 @@ public interface ContentStore
public boolean delete(String contentUrl); public boolean delete(String contentUrl);
/** /**
* Gets a presigned URL to directly access a binary content. It is up to the actual store * Checks if the store supports the retrieving of direct access URLs.
* implementation if it can fulfil this request with an expiry time or not.
* *
* @param contentUrl A content store URL * @return {@code true} if direct access URLs retrieving is supported, {@code false} otherwise
* @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
*/ */
default DirectAccessUrl getDirectAccessUrl(String contentUrl, Date expiresAt) default boolean isContentDirectUrlEnabled()
{ {
throw new UnsupportedOperationException( return false;
"Retrieving direct access URLs is not supported by this content store.");
} }
/** /**
* 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; 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 * 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) default boolean isStorageClassesSupported(StorageClassSet storageClassSet)
{ {
return storageClassSet == null || return storageClassSet == null ||
storageClassSet.isEmpty() || storageClassSet.isEmpty() ||
(1 == storageClassSet.size() && storageClassSet.equals(SCS_DEFAULT)); (1 == storageClassSet.size() && storageClassSet.equals(SCS_DEFAULT));
} }
/** /**

View File

@@ -2,7 +2,7 @@
* #%L * #%L
* Alfresco Data model classes * 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. * This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of * If the software was purchased under a paid Alfresco license, the terms of

View File

@@ -2,7 +2,7 @@
* #%L * #%L
* Alfresco Data model classes * 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. * This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of * 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.io.Serializable;
import java.util.Date; import java.util.Date;
import java.util.Objects;
import org.alfresco.api.AlfrescoPublicApi; import org.alfresco.api.AlfrescoPublicApi;
@@ -36,7 +37,8 @@ public class DirectAccessUrl implements Serializable
private static final long serialVersionUID = -881676208224414139L; private static final long serialVersionUID = -881676208224414139L;
private String contentUrl; private String contentUrl;
private Date expiresAt; private Date expiryTime;
private boolean attachment;
public String getContentUrl() public String getContentUrl()
{ {
@@ -48,13 +50,38 @@ public class DirectAccessUrl implements Serializable
this.contentUrl = contentUrl; 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);
} }
} }

View File

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

View File

@@ -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 <request_AT_jdom_DOT_org>.
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 <request_AT_jdom_DOT_org>.
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 <jhunter_AT_jdom_DOT_org> and
Brett McLaughlin <brett_AT_jdom_DOT_org>. For more information
on the JDOM Project, please see <http://www.jdom.org/>.
*/

View File

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

View File

@@ -1,6 +1,6 @@
# Fetch image based on Tomcat 9.0, Java 11 and Centos 8 # 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 # 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. # Set default docker_context.
ARG resource_path=target 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 # fontconfig is required by Activiti worflow diagram generator
# installing pinned dependencies as well # installing pinned dependencies as well
RUN yum install -y fontconfig-2.13.1-3.el8 \ RUN yum install -y fontconfig-2.13.0-4.3.el7 \
dejavu-fonts-common-2.35-7.el8 \ dejavu-fonts-common-2.33-6.el7 \
fontpackages-filesystem-1.44-22.el8 \ fontpackages-filesystem-1.44-8.el7 \
freetype-2.9.1-4.el8_3.1 \ freetype-2.8-14.el7_9.1 \
libpng-1.6.34-5.el8 \ libpng-1.5.13-8.el7 \
dejavu-sans-fonts-2.35-7.el8 && \ dejavu-sans-fonts-2.33-6.el7 && \
yum clean all 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, # The standard configuration is to have all Tomcat files owned by root with group GROUPNAME and whilst owner has read/write privileges,

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
TRANSFORMERS_TAG=2.5.2 TRANSFORMERS_TAG=2.5.3
SOLR6_TAG=2.0.2 SOLR6_TAG=2.0.2
POSTGRES_TAG=13.3 POSTGRES_TAG=13.3
ACTIVEMQ_TAG=5.16.1 ACTIVEMQ_TAG=5.16.1

View File

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

View File

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

View File

@@ -90,7 +90,7 @@ public class CancelCheckOutTests extends CmisTest
.cancelCheckOut(); .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, @TestRail(section = {"cmis-api"}, executionType= ExecutionType.REGRESSION,
description = "Verify that cancel check out on document created with Versioning State CHECKED OUT deletes the document") description = "Verify that cancel check out on document created with Versioning State CHECKED OUT deletes the document")
public void cancelCheckOutOnDocWithVersioningStateCheckedOut() throws Exception public void cancelCheckOutOnDocWithVersioningStateCheckedOut() throws Exception

View File

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

View File

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

View File

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

View File

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

View File

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

29
pom.xml
View File

@@ -2,7 +2,7 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>alfresco-community-repo</artifactId> <artifactId>alfresco-community-repo</artifactId>
<version>11.94-SNAPSHOT</version> <version>14.7-SNAPSHOT</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>Alfresco Community Repo Parent</name> <name>Alfresco Community Repo Parent</name>
@@ -23,7 +23,7 @@
<properties> <properties>
<acs.version.major>7</acs.version.major> <acs.version.major>7</acs.version.major>
<acs.version.minor>1</acs.version.minor> <acs.version.minor>2</acs.version.minor>
<acs.version.revision>0</acs.version.revision> <acs.version.revision>0</acs.version.revision>
<acs.version.label /> <acs.version.label />
<amp.min.version>${acs.version.major}.0.0</amp.min.version> <amp.min.version>${acs.version.major}.0.0</amp.min.version>
@@ -51,23 +51,23 @@
<dependency.alfresco-log-sanitizer.version>0.2</dependency.alfresco-log-sanitizer.version> <dependency.alfresco-log-sanitizer.version>0.2</dependency.alfresco-log-sanitizer.version>
<dependency.activiti-engine.version>5.23.0</dependency.activiti-engine.version> <dependency.activiti-engine.version>5.23.0</dependency.activiti-engine.version>
<dependency.activiti.version>5.23.0</dependency.activiti.version> <dependency.activiti.version>5.23.0</dependency.activiti.version>
<dependency.transform.model.version>1.3.1</dependency.transform.model.version> <dependency.alfresco-transform-model.version>1.4.0</dependency.alfresco-transform-model.version>
<dependency.alfresco-greenmail.version>6.2</dependency.alfresco-greenmail.version> <dependency.alfresco-greenmail.version>6.2</dependency.alfresco-greenmail.version>
<dependency.acs-event-model.version>0.0.12</dependency.acs-event-model.version> <dependency.acs-event-model.version>0.0.13</dependency.acs-event-model.version>
<dependency.spring.version>5.3.3</dependency.spring.version> <dependency.spring.version>5.3.9</dependency.spring.version>
<dependency.antlr.version>3.5.2</dependency.antlr.version> <dependency.antlr.version>3.5.2</dependency.antlr.version>
<dependency.jackson.version>2.12.3</dependency.jackson.version> <dependency.jackson.version>2.12.3</dependency.jackson.version>
<dependency.jackson-databind.version>2.12.4</dependency.jackson-databind.version> <dependency.jackson-databind.version>2.12.4</dependency.jackson-databind.version>
<dependency.cxf.version>3.4.4</dependency.cxf.version> <dependency.cxf.version>3.4.4</dependency.cxf.version>
<dependency.opencmis.version>1.0.0</dependency.opencmis.version> <dependency.opencmis.version>1.0.0</dependency.opencmis.version>
<dependency.webscripts.version>8.22</dependency.webscripts.version> <dependency.webscripts.version>8.23</dependency.webscripts.version>
<dependency.bouncycastle.version>1.69</dependency.bouncycastle.version> <dependency.bouncycastle.version>1.69</dependency.bouncycastle.version>
<dependency.mockito-core.version>3.11.2</dependency.mockito-core.version> <dependency.mockito-core.version>3.11.2</dependency.mockito-core.version>
<dependency.mockito-all.version>1.10.19</dependency.mockito-all.version> <dependency.mockito-all.version>1.10.19</dependency.mockito-all.version>
<dependency.org-json.version>20210307</dependency.org-json.version> <dependency.org-json.version>20210307</dependency.org-json.version>
<dependency.commons-dbcp.version>1.4-DBCP330</dependency.commons-dbcp.version> <dependency.commons-dbcp.version>1.4-DBCP330</dependency.commons-dbcp.version>
<dependency.commons-io.version>2.8.0</dependency.commons-io.version> <dependency.commons-io.version>2.11.0</dependency.commons-io.version>
<dependency.gson.version>2.8.5</dependency.gson.version> <dependency.gson.version>2.8.5</dependency.gson.version>
<dependency.httpclient.version>4.5.13</dependency.httpclient.version> <dependency.httpclient.version>4.5.13</dependency.httpclient.version>
<dependency.httpcore.version>4.4.14</dependency.httpcore.version> <dependency.httpcore.version>4.4.14</dependency.httpcore.version>
@@ -81,11 +81,11 @@
<dependency.truezip.version>7.7.10</dependency.truezip.version> <dependency.truezip.version>7.7.10</dependency.truezip.version>
<dependency.poi.version>4.1.2</dependency.poi.version> <dependency.poi.version>4.1.2</dependency.poi.version>
<dependency.ooxml-schemas.version>1.4</dependency.ooxml-schemas.version> <dependency.ooxml-schemas.version>1.4</dependency.ooxml-schemas.version>
<dependency.keycloak.version>13.0.1</dependency.keycloak.version> <dependency.keycloak.version>15.0.2</dependency.keycloak.version>
<dependency.jboss.logging.version>3.4.2.Final</dependency.jboss.logging.version> <dependency.jboss.logging.version>3.4.2.Final</dependency.jboss.logging.version>
<dependency.camel.version>3.7.4</dependency.camel.version> <dependency.camel.version>3.7.4</dependency.camel.version>
<dependency.activemq.version>5.16.1</dependency.activemq.version> <dependency.activemq.version>5.16.1</dependency.activemq.version>
<dependency.apache-compress.version>1.20</dependency.apache-compress.version> <dependency.apache-compress.version>1.21</dependency.apache-compress.version>
<dependency.apache.taglibs.version>1.2.5</dependency.apache.taglibs.version> <dependency.apache.taglibs.version>1.2.5</dependency.apache.taglibs.version>
<dependency.awaitility.version>4.1.0</dependency.awaitility.version> <dependency.awaitility.version>4.1.0</dependency.awaitility.version>
<dependency.swagger-ui.version>3.38.0</dependency.swagger-ui.version> <dependency.swagger-ui.version>3.38.0</dependency.swagger-ui.version>
@@ -105,16 +105,16 @@
<alfresco.googledrive.version>3.2.1.3</alfresco.googledrive.version> <alfresco.googledrive.version>3.2.1.3</alfresco.googledrive.version>
<alfresco.aos-module.version>1.4.0.1</alfresco.aos-module.version> <alfresco.aos-module.version>1.4.0.1</alfresco.aos-module.version>
<alfresco.api-explorer.version>7.0.0</alfresco.api-explorer.version> <alfresco.api-explorer.version>7.1.0.1</alfresco.api-explorer.version> <!-- Also in alfresco-enterprise-share -->
<alfresco.maven-plugin.version>2.2.0</alfresco.maven-plugin.version> <alfresco.maven-plugin.version>2.2.0</alfresco.maven-plugin.version>
<dependency.postgresql.version>42.2.20</dependency.postgresql.version> <dependency.postgresql.version>42.2.20</dependency.postgresql.version>
<dependency.mysql.version>8.0.25</dependency.mysql.version> <dependency.mysql.version>8.0.25</dependency.mysql.version>
<dependency.mysql-image.version>8</dependency.mysql-image.version> <dependency.mysql-image.version>8</dependency.mysql-image.version>
<dependency.mariadb.version>2.7.2</dependency.mariadb.version> <dependency.mariadb.version>2.7.2</dependency.mariadb.version>
<dependency.tas-utility.version>3.0.44</dependency.tas-utility.version> <dependency.tas-utility.version>3.0.45</dependency.tas-utility.version>
<dependency.rest-assured.version>3.3.0</dependency.rest-assured.version> <dependency.rest-assured.version>3.3.0</dependency.rest-assured.version>
<dependency.tas-restapi.version>1.61</dependency.tas-restapi.version> <dependency.tas-restapi.version>1.64</dependency.tas-restapi.version>
<dependency.tas-cmis.version>1.30</dependency.tas-cmis.version> <dependency.tas-cmis.version>1.30</dependency.tas-cmis.version>
<dependency.tas-email.version>1.8</dependency.tas-email.version> <dependency.tas-email.version>1.8</dependency.tas-email.version>
<dependency.tas-webdav.version>1.6</dependency.tas-webdav.version> <dependency.tas-webdav.version>1.6</dependency.tas-webdav.version>
@@ -638,7 +638,7 @@
<dependency> <dependency>
<groupId>org.jsoup</groupId> <groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId> <artifactId>jsoup</artifactId>
<version>1.13.1</version> <version>1.14.2</version>
</dependency> </dependency>
<!-- upgrade dependency from TIKA --> <!-- upgrade dependency from TIKA -->
<dependency> <dependency>
@@ -649,7 +649,7 @@
<dependency> <dependency>
<groupId>org.apache.commons</groupId> <groupId>org.apache.commons</groupId>
<artifactId>commons-csv</artifactId> <artifactId>commons-csv</artifactId>
<version>1.8</version> <version>1.9.0</version>
</dependency> </dependency>
<dependency> <dependency>
@@ -851,6 +851,7 @@
<groupId>org.projectlombok</groupId> <groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>
<version>1.18.20</version> <version>1.18.20</version>
<scope>provided</scope>
</dependency> </dependency>
</dependencies> </dependencies>
</dependencyManagement> </dependencyManagement>

View File

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

View File

@@ -27,17 +27,14 @@ package org.alfresco.opencmis;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.PrintWriter;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.EventListener; import java.util.EventListener;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@@ -48,19 +45,17 @@ import javax.servlet.Servlet;
import javax.servlet.ServletConfig; import javax.servlet.ServletConfig;
import javax.servlet.ServletContext; import javax.servlet.ServletContext;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRegistration; import javax.servlet.ServletRegistration;
import javax.servlet.SessionCookieConfig; import javax.servlet.SessionCookieConfig;
import javax.servlet.SessionTrackingMode; import javax.servlet.SessionTrackingMode;
import javax.servlet.descriptor.JspConfigDescriptor; import javax.servlet.descriptor.JspConfigDescriptor;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletResponse;
import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.opencmis.CMISDispatcherRegistry.Binding; import org.alfresco.opencmis.CMISDispatcherRegistry.Binding;
import org.alfresco.opencmis.CMISDispatcherRegistry.Endpoint; import org.alfresco.opencmis.CMISDispatcherRegistry.Endpoint;
import org.alfresco.repo.tenant.TenantAdminService; 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.Descriptor;
import org.alfresco.service.descriptor.DescriptorService; import org.alfresco.service.descriptor.DescriptorService;
import org.apache.chemistry.opencmis.commons.enums.CmisVersion; 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.apache.chemistry.opencmis.server.impl.atompub.CmisAtomPubServlet;
import org.springframework.extensions.webscripts.WebScriptRequest; import org.springframework.extensions.webscripts.WebScriptRequest;
import org.springframework.extensions.webscripts.WebScriptResponse; 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. * 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 CmisVersion cmisVersion;
protected TenantAdminService tenantAdminService; protected TenantAdminService tenantAdminService;
private boolean allowUnsecureCallbackJSONP;
private Set<String> nonAttachContentTypes = Collections.emptySet(); // pre-configured whitelist, eg. images & pdf private Set<String> nonAttachContentTypes = Collections.emptySet(); // pre-configured whitelist, eg. images & pdf
public void setTenantAdminService(TenantAdminService tenantAdminService) public void setTenantAdminService(TenantAdminService tenantAdminService)
@@ -152,6 +148,16 @@ public abstract class CMISServletDispatcher implements CMISDispatcher
return this.currentDescriptor; return this.currentDescriptor;
} }
public void setAllowUnsecureCallbackJSONP(boolean allowUnsecureCallbackJSONP)
{
this.allowUnsecureCallbackJSONP = allowUnsecureCallbackJSONP;
}
public boolean isAllowUnsecureCallbackJSONP()
{
return allowUnsecureCallbackJSONP;
}
public void init() public void init()
{ {
Endpoint endpoint = new Endpoint(getBinding(), version); Endpoint endpoint = new Endpoint(getBinding(), version);
@@ -219,12 +225,22 @@ public abstract class CMISServletDispatcher implements CMISDispatcher
CMISHttpServletResponse httpResWrapper = getHttpResponse(res); CMISHttpServletResponse httpResWrapper = getHttpResponse(res);
CMISHttpServletRequest httpReqWrapper = getHttpRequest(req); 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) catch(ServletException e)
{ {
throw new AlfrescoRuntimeException("", e); throw new AlfrescoRuntimeException("", e);
} }
catch (JsonpCallbackNotAllowedException e)
{
res.setStatus(403);
res.getWriter().append(e.getMessage());
}
} }
/** /**

View File

@@ -29,6 +29,7 @@ import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.util.function.Supplier;
import org.springframework.extensions.surf.util.Content; import org.springframework.extensions.surf.util.Content;
import org.springframework.extensions.webscripts.Description.FormatStyle; 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.extensions.webscripts.WrappingWebScriptRequest;
import org.springframework.util.FileCopyUtils; import org.springframework.util.FileCopyUtils;
public class BufferedRequest implements WrappingWebScriptRequest public class BufferedRequest implements WrappingWebScriptRequest, AutoCloseable
{ {
private TempOutputStreamFactory streamFactory; private final Supplier<TempOutputStream> streamFactory;
private WebScriptRequest req; private final WebScriptRequest req;
private TempOutputStream bufferStream; private TempOutputStream bufferStream;
private InputStream contentStream; private InputStream contentStream;
private BufferedReader contentReader; private BufferedReader contentReader;
public BufferedRequest(WebScriptRequest req, TempOutputStreamFactory streamFactory) public BufferedRequest(WebScriptRequest req, Supplier<TempOutputStream> streamFactory)
{ {
this.req = req; this.req = req;
this.streamFactory = streamFactory; this.streamFactory = streamFactory;
@@ -56,7 +57,7 @@ public class BufferedRequest implements WrappingWebScriptRequest
{ {
if (bufferStream == null) if (bufferStream == null)
{ {
bufferStream = streamFactory.createOutputStream(); bufferStream = streamFactory.get();
try try
{ {
@@ -81,7 +82,7 @@ public class BufferedRequest implements WrappingWebScriptRequest
} }
if (contentStream == null) if (contentStream == null)
{ {
contentStream = getBufferedBodyAsTempStream().getInputStream(); contentStream = getBufferedBodyAsTempStream().toNewInputStream();
} }
return contentStream; return contentStream;
@@ -95,7 +96,7 @@ public class BufferedRequest implements WrappingWebScriptRequest
{ {
contentStream.close(); contentStream.close();
} }
catch (Exception e) catch (Exception ignore)
{ {
} }
contentStream = null; contentStream = null;
@@ -106,13 +107,14 @@ public class BufferedRequest implements WrappingWebScriptRequest
{ {
contentReader.close(); contentReader.close();
} }
catch (Exception e) catch (Exception ignore)
{ {
} }
contentReader = null; contentReader = null;
} }
} }
@Override
public void close() public void close()
{ {
reset(); reset();
@@ -122,7 +124,7 @@ public class BufferedRequest implements WrappingWebScriptRequest
{ {
bufferStream.destroy(); bufferStream.destroy();
} }
catch (Exception e) catch (Exception ignore)
{ {
} }
bufferStream = null; bufferStream = null;

View File

@@ -28,6 +28,7 @@ package org.alfresco.repo.web.scripts;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.Writer; import java.io.Writer;
import java.util.function.Supplier;
import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.error.AlfrescoRuntimeException;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
@@ -42,25 +43,24 @@ import org.springframework.util.FileCopyUtils;
/** /**
* Transactional Buffered Response * Transactional Buffered Response
*/ */
public class BufferedResponse implements WrappingWebScriptResponse public class BufferedResponse implements WrappingWebScriptResponse, AutoCloseable
{ {
// Logger // Logger
protected static final Log logger = LogFactory.getLog(BufferedResponse.class); protected static final Log logger = LogFactory.getLog(BufferedResponse.class);
private TempOutputStreamFactory streamFactory; private final Supplier<TempOutputStream> streamFactory;
private WebScriptResponse res; private final WebScriptResponse res;
private int bufferSize; private final int bufferSize;
private TempOutputStream outputStream = null; private TempOutputStream outputStream;
private StringBuilderWriter outputWriter = null; private StringBuilderWriter outputWriter;
/** /**
* Construct * Construct
* *
* @param res WebScriptResponse * @param res WebScriptResponse
* @param bufferSize int * @param bufferSize int
*/ */
public BufferedResponse(WebScriptResponse res, int bufferSize, TempOutputStreamFactory streamFactory) public BufferedResponse(WebScriptResponse res, int bufferSize, Supplier<TempOutputStream> streamFactory)
{ {
this.res = res; this.res = res;
this.bufferSize = bufferSize; this.bufferSize = bufferSize;
@@ -71,6 +71,7 @@ public class BufferedResponse implements WrappingWebScriptResponse
* (non-Javadoc) * (non-Javadoc)
* @see org.alfresco.web.scripts.WrappingWebScriptResponse#getNext() * @see org.alfresco.web.scripts.WrappingWebScriptResponse#getNext()
*/ */
@Override
public WebScriptResponse getNext() public WebScriptResponse getNext()
{ {
return res; return res;
@@ -123,16 +124,18 @@ public class BufferedResponse implements WrappingWebScriptResponse
* (non-Javadoc) * (non-Javadoc)
* @see org.alfresco.web.scripts.WebScriptResponse#getOutputStream() * @see org.alfresco.web.scripts.WebScriptResponse#getOutputStream()
*/ */
@Override
public OutputStream getOutputStream() throws IOException public OutputStream getOutputStream() throws IOException
{ {
if (outputStream == null) if (outputStream != null)
{ {
if (outputWriter != null) return outputStream;
{
throw new AlfrescoRuntimeException("Already buffering output writer");
}
outputStream = streamFactory.createOutputStream();
} }
if (outputWriter != null)
{
throw new AlfrescoRuntimeException("Already buffering output writer");
}
outputStream = streamFactory.get();
return outputStream; return outputStream;
} }
@@ -151,14 +154,15 @@ public class BufferedResponse implements WrappingWebScriptResponse
*/ */
public Writer getWriter() throws IOException public Writer getWriter() throws IOException
{ {
if (outputWriter == null) if (outputWriter != null)
{ {
if (outputStream != null) return outputWriter;
{
throw new AlfrescoRuntimeException("Already buffering output stream");
}
outputWriter = new StringBuilderWriter(bufferSize);
} }
if (outputStream != null)
{
throw new AlfrescoRuntimeException("Already buffering output stream");
}
outputWriter = new StringBuilderWriter(bufferSize);
return outputWriter; return outputWriter;
} }
@@ -262,15 +266,7 @@ public class BufferedResponse implements WrappingWebScriptResponse
if (logger.isDebugEnabled()) if (logger.isDebugEnabled())
logger.debug("Writing Transactional response: size=" + outputStream.getLength()); logger.debug("Writing Transactional response: size=" + outputStream.getLength());
try FileCopyUtils.copy(outputStream.toNewInputStream(), res.getOutputStream());
{
outputStream.flush();
FileCopyUtils.copy(outputStream.getInputStream(), res.getOutputStream());
}
finally
{
outputStream.destroy();
}
} }
} }
catch (IOException e) catch (IOException e)
@@ -278,4 +274,20 @@ public class BufferedResponse implements WrappingWebScriptResponse
throw new AlfrescoRuntimeException("Failed to commit buffered response", e); throw new AlfrescoRuntimeException("Failed to commit buffered response", e);
} }
} }
@Override
public void close()
{
if (outputStream != null)
{
try
{
outputStream.destroy();
}
catch (Exception ignore)
{
}
outputStream = null;
}
}
} }

View File

@@ -2,7 +2,7 @@
* #%L * #%L
* Alfresco Remote API * 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. * This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of * If the software was purchased under a paid Alfresco license, the terms of
@@ -25,12 +25,13 @@
*/ */
package org.alfresco.repo.web.scripts; package org.alfresco.repo.web.scripts;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.SocketException; import java.net.SocketException;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.function.Supplier;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import javax.transaction.Status; import javax.transaction.Status;
@@ -40,7 +41,6 @@ import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.error.ExceptionStackUtil; import org.alfresco.error.ExceptionStackUtil;
import org.alfresco.repo.model.Repository; import org.alfresco.repo.model.Repository;
import org.alfresco.repo.security.authentication.AuthenticationUtil; 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;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState;
import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.transaction.RetryingTransactionHelper;
@@ -95,8 +95,7 @@ public class RepositoryContainer extends AbstractRuntimeContainer
private String tempDirectoryName = null; private String tempDirectoryName = null;
private int memoryThreshold = 4 * 1024 * 1024; // 4mb private int memoryThreshold = 4 * 1024 * 1024; // 4mb
private long maxContentSize = (long) 4 * 1024 * 1024 * 1024; // 4gb private long maxContentSize = (long) 4 * 1024 * 1024 * 1024; // 4gb
private TempOutputStreamFactory streamFactory = null; private Supplier<TempOutputStream> streamFactory = null;
private TempOutputStreamFactory responseStreamFactory = null;
private String preserveHeadersPattern = null; private String preserveHeadersPattern = null;
private Class<?>[] notPublicExceptions = new Class<?>[] {}; private Class<?>[] notPublicExceptions = new Class<?>[] {};
@@ -107,17 +106,16 @@ public class RepositoryContainer extends AbstractRuntimeContainer
*/ */
public void setup() public void setup()
{ {
File tempDirectory = TempFileProvider.getTempDir(tempDirectoryName); streamFactory = TempOutputStream.factory(
this.streamFactory = new TempOutputStreamFactory(tempDirectory, memoryThreshold, maxContentSize, encryptTempFiles, false); TempFileProvider.getTempDir(tempDirectoryName),
this.responseStreamFactory = new TempOutputStreamFactory(tempDirectory, memoryThreshold, maxContentSize, memoryThreshold, maxContentSize, encryptTempFiles);
encryptTempFiles, false);
} }
public void setEncryptTempFiles(Boolean encryptTempFiles) public void setEncryptTempFiles(Boolean encryptTempFiles)
{ {
if(encryptTempFiles != null) if(encryptTempFiles != null)
{ {
this.encryptTempFiles = encryptTempFiles.booleanValue(); this.encryptTempFiles = encryptTempFiles;
} }
} }
@@ -130,7 +128,7 @@ public class RepositoryContainer extends AbstractRuntimeContainer
{ {
if(memoryThreshold != null) if(memoryThreshold != null)
{ {
this.memoryThreshold = memoryThreshold.intValue(); this.memoryThreshold = memoryThreshold;
} }
} }
@@ -138,7 +136,7 @@ public class RepositoryContainer extends AbstractRuntimeContainer
{ {
if(maxContentSize != null) if(maxContentSize != null)
{ {
this.maxContentSize = maxContentSize.longValue(); this.maxContentSize = maxContentSize;
} }
} }
@@ -246,8 +244,7 @@ public class RepositoryContainer extends AbstractRuntimeContainer
*/ */
public Map<String, Object> getScriptParameters() public Map<String, Object> getScriptParameters()
{ {
Map<String, Object> params = new HashMap<String, Object>(); Map<String, Object> params = new HashMap<>(super.getScriptParameters());
params.putAll(super.getScriptParameters());
addRepoParameters(params); addRepoParameters(params);
return params; return params;
} }
@@ -259,16 +256,11 @@ public class RepositoryContainer extends AbstractRuntimeContainer
public Map<String, Object> getTemplateParameters() public Map<String, Object> getTemplateParameters()
{ {
// Ensure we have a transaction - we might be generating the status template after the main transaction failed // Ensure we have a transaction - we might be generating the status template after the main transaction failed
return fallbackTransactionHelper.doInTransaction(new RetryingTransactionCallback<Map<String, Object>>() return fallbackTransactionHelper.doInTransaction(() -> {
{ Map<String, Object> params = new HashMap<>(RepositoryContainer.super.getTemplateParameters());
public Map<String, Object> execute() throws Throwable params.put(TemplateService.KEY_IMAGE_RESOLVER, imageResolver.getImageResolver());
{ addRepoParameters(params);
Map<String, Object> params = new HashMap<String, Object>(); return params;
params.putAll(RepositoryContainer.super.getTemplateParameters());
params.put(TemplateService.KEY_IMAGE_RESOLVER, imageResolver.getImageResolver());
addRepoParameters(params);
return params;
}
}, true); }, true);
} }
@@ -321,7 +313,7 @@ public class RepositoryContainer extends AbstractRuntimeContainer
Throwable displayCause = ExceptionStackUtil.getCause(e, publicExceptions); Throwable displayCause = ExceptionStackUtil.getCause(e, publicExceptions);
if (displayCause == null && hideCause != null) if (displayCause == null && hideCause != null)
{ {
AlfrescoRuntimeException alf = null; final AlfrescoRuntimeException alf;
if (e instanceof AlfrescoRuntimeException) if (e instanceof AlfrescoRuntimeException)
{ {
alf = (AlfrescoRuntimeException) e; alf = (AlfrescoRuntimeException) e;
@@ -342,7 +334,7 @@ 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 throws IOException
{ {
final WebScript script = scriptReq.getServiceMatch().getWebScript(); final WebScript script = scriptReq.getServiceMatch().getWebScript();
@@ -351,7 +343,7 @@ public class RepositoryContainer extends AbstractRuntimeContainer
// Escalate the webscript declared level of authentication to the container required authentication // Escalate the webscript declared level of authentication to the container required authentication
// eg. must be guest if MT is enabled unless credentials are empty // 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 RequiredAuthentication required = (desc.getRequiredAuthentication().compareTo(containerRequiredAuthentication) < 0 && !auth.emptyCredentials() ? containerRequiredAuthentication : desc.getRequiredAuthentication());
final boolean isGuest = scriptReq.isGuest(); final boolean isGuest = scriptReq.isGuest();
@@ -361,98 +353,135 @@ public class RepositoryContainer extends AbstractRuntimeContainer
//AuthenticationUtil.clearCurrentSecurityContext(); //AuthenticationUtil.clearCurrentSecurityContext();
transactionedExecuteAs(script, scriptReq, scriptRes); 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."); 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(); String currentUser = AuthenticationUtil.getFullyAuthenticatedUser();
logger.debug("Current authentication: " + (currentUser == null ? "unauthenticated" : "authenticated as " + currentUser));
// logger.debug("Authentication required: " + required);
// Determine if user already authenticated logger.debug("Guest login requested: " + isGuest);
//
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);
}
//
// Apply appropriate authentication to Web Script invocation
//
RetryingTransactionCallback<Boolean> authWork = new RetryingTransactionCallback<Boolean>()
{
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
{ //
// // Apply appropriate authentication to Web Script invocation
// Reset authentication for current thread //
// final RetryingTransactionCallback<Boolean> authWork = () -> {
AuthenticationUtil.popAuthentication(); 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) if (debug)
{ {
String currentUser = AuthenticationUtil.getFullyAuthenticatedUser(); String currentUser = AuthenticationUtil.getFullyAuthenticatedUser();
logger.debug("Authentication reset: " + (currentUser == null ? "unauthenticated" : "authenticated as " + currentUser)); logger.debug("Authentication: " + (currentUser == null ? "unauthenticated" : "authenticated as " + currentUser));
} }
return true;
};
final boolean readOnly = transactionService.isReadOnly();
final boolean requiresNew = !readOnly && AlfrescoTransactionSupport.getTransactionReadState() == TxnReadState.TXN_READ_ONLY;
if (!transactionService.getRetryingTransactionHelper().doInTransaction(authWork, readOnly, requiresNew))
{
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) protected void transactionedExecute(final WebScript script, final WebScriptRequest scriptReq, final WebScriptResponse scriptRes)
throws IOException throws IOException
{ {
final Description description = script.getDescription();
try try
{ {
final Description description = script.getDescription();
if (description.getRequiredTransaction() == RequiredTransaction.none) if (description.getRequiredTransaction() == RequiredTransaction.none)
{ {
script.execute(scriptReq, scriptRes); 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; logger.debug("Webscript with URL '" + scriptReq.getURL() +
final BufferedResponse bufferedRes; "' is a GET request but it's descriptor has declared a readwrite transaction is required");
RequiredTransactionParameters trxParams = description.getRequiredTransactionParameters(); }
if (trxParams.getCapability() == TransactionCapability.readwrite)
try
{
final RetryingTransactionHelper transactionHelper = transactionService.getRetryingTransactionHelper();
if (script instanceof LoginPost)
{ {
if (trxParams.getBufferSize() > 0) //login script requires read-write transaction because of authorization interceptor
transactionHelper.setForceWritable(true);
}
transactionHelper.doInTransaction(() -> {
try
{ {
if (logger.isDebugEnabled()) if (logger.isDebugEnabled())
logger.debug("Creating Transactional Response for ReadWrite transaction; buffersize=" + trxParams.getBufferSize()); logger.debug("Begin retry transaction block: " + description.getRequiredTransaction() + ","
+ description.getRequiredTransactionParameters().getCapability());
// create buffered request and response that allow transaction retrying if (bufferedReq == null || bufferedRes == null)
bufferedReq = new BufferedRequest(scriptReq, streamFactory);
bufferedRes = new BufferedResponse(scriptRes, trxParams.getBufferSize(), responseStreamFactory);
}
else
{
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<Object> work = new RetryingTransactionCallback<Object>()
{
public Object execute() throws Exception
{
try
{ {
if (logger.isDebugEnabled()) script.execute(scriptReq, scriptRes);
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);
}
} }
catch(Exception e) else
{ {
if (logger.isDebugEnabled()) // Reset the request and response in case of a transaction retry
{ bufferedReq.reset();
logger.debug("Transaction exception: " + description.getRequiredTransaction() + ": " + e.getMessage()); // REPO-4388 don't reset specified headers
// Note: user transaction shouldn't be null, but just in case inside this exception handler bufferedRes.reset(preserveHeadersPattern);
UserTransaction userTrx = RetryingTransactionHelper.getActiveUserTransaction(); script.execute(bufferedReq, bufferedRes);
if (userTrx != null) }
{ }
logger.debug("Transaction status: " + userTrx.getStatus()); 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(); UserTransaction userTrx = RetryingTransactionHelper.getActiveUserTransaction();
if (userTrx != null) 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()) if (logger.isDebugEnabled())
logger.debug("Marking web script transaction for rollback"); logger.debug("Caught and ignoring exception during marking for rollback: " + re.getMessage());
try
{
userTrx.setRollbackOnly();
}
catch(Throwable re)
{
if (logger.isDebugEnabled())
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; // re-throw original exception for retry
throw e;
} }
}; finally
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 if (logger.isDebugEnabled())
transactionHelper.setForceWritable(true); logger.debug("End retry transaction block: " + description.getRequiredTransaction() + ","
+ description.getRequiredTransactionParameters().getCapability());
} }
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 return null;
if (bufferedRes != null) }, readonly, requiresNew);
{ }
bufferedRes.writeResponse(); catch (TooBusyException e)
} {
// Map TooBusyException to a 503 status code
throw new WebScriptException(HttpServletResponse.SC_SERVICE_UNAVAILABLE, e.getMessage(), e);
}
// Ensure a response is always flushed after successful execution
if (bufferedRes != null)
{
bufferedRes.writeResponse();
} }
} }
catch (IOException ioe) }
private static void handleIOException(final IOException ioe) throws IOException
{
Throwable socketException = ExceptionStackUtil.getCause(ioe, SocketException.class);
Class<?> clientAbortException = null;
try
{ {
Throwable socketException = ExceptionStackUtil.getCause(ioe, SocketException.class); clientAbortException = Class.forName("org.apache.catalina.connector.ClientAbortException");
Class<?> clientAbortException = null; }
try 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())
{ {
clientAbortException = Class.forName("org.apache.catalina.connector.ClientAbortException"); logger.warn("Client has cut off communication", ioe);
}
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 else
{ {
throw ioe; logger.info("Client has cut off communication");
} }
} }
else
{
throw ioe;
}
} }
/** /**
* Execute script within required level of transaction as required effective user. * Execute script within required level of transaction as required effective user.
* *
* @param script WebScript * @param script WebScript
* @param scriptReq WebScriptRequest * @param scriptReq WebScriptRequest
* @param scriptRes WebScriptResponse * @param scriptRes WebScriptResponse
* @throws IOException * @throws IOException
@@ -659,22 +657,46 @@ public class RepositoryContainer extends AbstractRuntimeContainer
private void transactionedExecuteAs(final WebScript script, final WebScriptRequest scriptReq, private void transactionedExecuteAs(final WebScript script, final WebScriptRequest scriptReq,
final WebScriptResponse scriptRes) throws IOException final WebScriptResponse scriptRes) throws IOException
{ {
String runAs = script.getDescription().getRunAs(); final String runAs = script.getDescription().getRunAs();
if (runAs == null) if (runAs == null)
{ {
transactionedExecute(script, scriptReq, scriptRes); transactionedExecute(script, scriptReq, scriptRes);
} }
else else
{ {
RunAsWork<Object> work = new RunAsWork<Object>() AuthenticationUtil.runAs(() -> {
{ transactionedExecute(script, scriptReq, scriptRes);
public Object doWork() throws Exception return null;
{ }, runAs);
transactionedExecute(script, scriptReq, scriptRes); }
return null; }
}
}; /**
AuthenticationUtil.runAs(work, 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; ContextRefreshedEvent refreshEvent = (ContextRefreshedEvent)event;
ApplicationContext refreshContext = refreshEvent.getApplicationContext(); ApplicationContext refreshContext = refreshEvent.getApplicationContext();
if (refreshContext != null && refreshContext.equals(applicationContext)) if (refreshContext.equals(applicationContext))
{ {
RunAsWork<Object> work = new RunAsWork<Object>() AuthenticationUtil.runAs(() -> {
{ reset();
public Object doWork() throws Exception return null;
{ }, AuthenticationUtil.getSystemUserName());
reset();
return null;
}
};
AuthenticationUtil.runAs(work, AuthenticationUtil.getSystemUserName());
} }
} }
} }
@@ -739,13 +756,9 @@ public class RepositoryContainer extends AbstractRuntimeContainer
@Override @Override
public void reset() public void reset()
{ {
transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback<Object>() transactionService.getRetryingTransactionHelper().doInTransaction(() -> {
{ internalReset();
public Object execute() throws Exception return null;
{
internalReset();
return null;
}
}, true, false); }, true, false);
} }
@@ -753,4 +766,44 @@ public class RepositoryContainer extends AbstractRuntimeContainer
{ {
super.reset(); super.reset();
} }
private static BufferedRequest newBufferedRequest(
final RequiredTransactionParameters trxParams,
final WebScriptRequest scriptReq,
final Supplier<TempOutputStream> 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<TempOutputStream> 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);
}
} }

View File

@@ -36,6 +36,7 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.security.Key; import java.security.Key;
import java.util.function.Supplier;
import javax.crypto.Cipher; import javax.crypto.Cipher;
import javax.crypto.CipherInputStream; import javax.crypto.CipherInputStream;
@@ -88,13 +89,11 @@ public class TempOutputStream extends OutputStream
private final File tempDir; private final File tempDir;
private final int memoryThreshold; private final int memoryThreshold;
private final long maxContentSize; private final long maxContentSize;
private boolean encrypt; private final boolean encrypt;
private boolean deleteTempFileOnClose;
private long length = 0; private long length = 0;
private OutputStream outputStream; private OutputStream outputStream;
private File tempFile; private File tempFile;
private TempByteArrayOutputStream tempStream;
private Key symKey; private Key symKey;
private byte[] iv; private byte[] iv;
@@ -112,58 +111,49 @@ public class TempOutputStream extends OutputStream
* the max content size in B * the max content size in B
* @param encrypt * @param encrypt
* true if temp files should be encrypted * 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.tempDir = tempDir;
this.memoryThreshold = (memoryThreshold < 0) ? DEFAULT_MEMORY_THRESHOLD : memoryThreshold; this.memoryThreshold = (memoryThreshold < 0) ? DEFAULT_MEMORY_THRESHOLD : memoryThreshold;
this.maxContentSize = maxContentSize; this.maxContentSize = maxContentSize;
this.encrypt = encrypt; this.encrypt = encrypt;
this.deleteTempFileOnClose = deleteTempFileOnClose;
this.tempStream = new TempByteArrayOutputStream(); this.outputStream = new ByteArrayOutputStream();
this.outputStream = this.tempStream;
} }
/** /**
* Returns the data as an InputStream * 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)); 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 @Override
public void close() throws IOException public void close() throws IOException
{ {
close(deleteTempFileOnClose); closeOutputStream();
} }
/** /**
@@ -215,7 +205,9 @@ public class TempOutputStream extends OutputStream
*/ */
public void destroy() throws IOException public void destroy() throws IOException
{ {
close(true); closeOutputStream();
deleteTempFile();
} }
public long getLength() 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 (!encrypt)
if (deleteTempFileOnClose)
{ {
deleteTempFile(); return new BufferedOutputStream(new FileOutputStream(file));
} }
} try
private BufferedOutputStream createOutputStream(File file) throws IOException
{
BufferedOutputStream fileOutputStream;
if (encrypt)
{ {
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 logger.error("Cannot initialize encryption cipher", e);
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));
} }
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 private void update(int len) throws IOException
{ {
if (maxContentSize > -1 && length + len > maxContentSize) if (surpassesMaxContentSize(len))
{ {
destroy(); destroy();
throw new ContentLimitViolationException("Content size violation, limit = " + maxContentSize); 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); final BufferedOutputStream fileOutputStream = createFileOutputStream(tempFile);
fileOutputStream.write(this.tempStream.getBuffer(), 0, this.tempStream.getCount()); fileOutputStream.write(((ByteArrayOutputStream) outputStream).toByteArray());
fileOutputStream.flush(); fileOutputStream.flush();
try try
{ {
tempStream.close(); outputStream.close();
} }
catch (IOException e) catch (IOException ignore)
{ {
// Ignore exception // Ignore exception
} }
tempStream = null;
tempFile = file;
outputStream = fileOutputStream; outputStream = fileOutputStream;
} }
length += len; length += len;
} }
private static class TempByteArrayOutputStream extends ByteArrayOutputStream private boolean surpassesMaxContentSize(final int len)
{ {
/** return maxContentSize >= 0 && length + len > maxContentSize;
* @return The internal buffer where data is stored }
*/
public byte[] getBuffer()
{
return buf;
}
/** private boolean surpassesThreshold(final int len)
* @return The number of valid bytes in the buffer. {
*/ return tempFile == null && length + len > memoryThreshold;
public int getCount() }
{
return count; /**
} * Creates a {@link TempOutputStream} factory/supplier.
*
* @param tempDir
* the temporary directory, i.e. <code>isDir == true</code>, 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<TempOutputStream> factory(final File tempDir, final int memoryThreshold,
final long maxContentSize, final boolean encrypt)
{
return () -> new TempOutputStream(tempDir, memoryThreshold, maxContentSize, encrypt);
} }
} }

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.repo.web.scripts;
import java.io.File;
/**
* Factory for {@link TempOutputStream}
*/
public class TempOutputStreamFactory
{
/**
* A temporary directory, i.e. <code>isDir == true</code>, 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. <code>isDir == true</code>, 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;
}
}

View File

@@ -30,8 +30,19 @@ import java.net.InetAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Date; 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.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.repo.model.Repository;
import org.alfresco.service.cmr.model.FileFolderService; import org.alfresco.service.cmr.model.FileFolderService;
import org.alfresco.service.cmr.model.FileInfo; 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.alfresco.service.cmr.repository.NodeRef;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; 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.DeclarativeWebScript;
import org.springframework.extensions.webscripts.Status;
import org.springframework.extensions.webscripts.WebScriptException; import org.springframework.extensions.webscripts.WebScriptException;
import org.springframework.extensions.webscripts.WebScriptRequest;
/** /**
* contains common fields and methods for the import web scripts. * contains common fields and methods for the import web scripts.
@@ -60,10 +75,10 @@ public class AbstractBulkFileSystemImportWebScript extends DeclarativeWebScript
// Web scripts parameters (common) // Web scripts parameters (common)
protected static final String PARAMETER_REPLACE_EXISTING = "replaceExisting"; protected static final String PARAMETER_REPLACE_EXISTING = "replaceExisting";
protected static final String PARAMETER_EXISTING_FILE_MODE = "existingFileMode"; 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_SOURCE_DIRECTORY = "sourceDirectory";
protected static final String PARAMETER_DISABLE_RULES = "disableRules"; 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_MODEL_KEY = "importInProgress";
protected static final String IMPORT_ALREADY_IN_PROGRESS_ERROR_KEY ="bfsit.error.importAlreadyInProgress"; protected static final String IMPORT_ALREADY_IN_PROGRESS_ERROR_KEY ="bfsit.error.importAlreadyInProgress";
@@ -219,4 +234,198 @@ public class AbstractBulkFileSystemImportWebScript extends DeclarativeWebScript
this.repository = repository; this.repository = repository;
} }
protected class MultithreadedImportWebScriptLogic
{
private final MultiThreadedBulkFilesystemImporter bulkImporter;
private final Supplier<NodeImporter> nodeImporterFactory;
private final WebScriptRequest request;
private final Status status;
private final Cache cache;
public MultithreadedImportWebScriptLogic(MultiThreadedBulkFilesystemImporter bulkImporter, Supplier<NodeImporter> 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<String, Object> executeImport()
{
Map<String, Object> 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<String, String> paramsProvider;
private final NodeRefCreator nodeRefCreator;
private final int defaultBatchSize;
private final int defaultNumThreads;
public BulkImportParametersExtractor(final Function<String, String> 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;
}
}
} }

View File

@@ -27,17 +27,12 @@
package org.alfresco.repo.web.scripts.bulkimport.copy; package org.alfresco.repo.web.scripts.bulkimport.copy;
import java.io.File; import java.io.File;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.alfresco.repo.bulkimport.BulkImportParameters;
import org.alfresco.repo.bulkimport.NodeImporter; import org.alfresco.repo.bulkimport.NodeImporter;
import org.alfresco.repo.bulkimport.impl.MultiThreadedBulkFilesystemImporter; import org.alfresco.repo.bulkimport.impl.MultiThreadedBulkFilesystemImporter;
import org.alfresco.repo.bulkimport.impl.StreamingNodeImporterFactory; import org.alfresco.repo.bulkimport.impl.StreamingNodeImporterFactory;
import org.alfresco.repo.web.scripts.bulkimport.AbstractBulkFileSystemImportWebScript; 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.Cache;
import org.springframework.extensions.webscripts.Status; import org.springframework.extensions.webscripts.Status;
import org.springframework.extensions.webscripts.WebScriptException; import org.springframework.extensions.webscripts.WebScriptException;
@@ -69,170 +64,22 @@ public class BulkFilesystemImportWebScript extends AbstractBulkFileSystemImportW
@Override @Override
protected Map<String, Object> executeImpl(final WebScriptRequest request, final Status status, final Cache cache) protected Map<String, Object> executeImpl(final WebScriptRequest request, final Status status, final Cache cache)
{ {
Map<String, Object> model = new HashMap<String, Object>(); final MultithreadedImportWebScriptLogic importLogic = new MultithreadedImportWebScriptLogic(bulkImporter,
String targetNodeRefStr = null; () -> createNodeImporter(request), request, status, cache);
String targetPath = null; return importLogic.executeImport();
String sourceDirectoryStr = null; }
@Deprecated String replaceExistingStr = null;
String existingFileModeStr = null;
String batchSizeStr = null;
String numThreadsStr = null;
String disableRulesStr = null;
cache.setNeverCache(true); private NodeImporter createNodeImporter(WebScriptRequest request)
{
try final String sourceDirectoryStr = request.getParameter(PARAMETER_SOURCE_DIRECTORY);
if (sourceDirectoryStr == null || sourceDirectoryStr.trim().length() == 0)
{ {
if(!bulkImporter.getStatus().inProgress()) throw new WebScriptException("Error: mandatory parameter '" + PARAMETER_SOURCE_DIRECTORY + "' was not provided.");
{
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));
}
}
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);
} }
} }

View File

@@ -152,8 +152,7 @@ public class FileFolderLoaderPost extends AbstractWebScript implements Applicati
throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Could not parse JSON from req.", je); throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Could not parse JSON from req.", je);
} }
// Write the response // Write the response
OutputStream os = res.getOutputStream(); try (OutputStream os = res.getOutputStream())
try
{ {
JSONObject json = new JSONObject(); JSONObject json = new JSONObject();
json.put(KEY_COUNT, count); json.put(KEY_COUNT, count);
@@ -163,9 +162,5 @@ public class FileFolderLoaderPost extends AbstractWebScript implements Applicati
{ {
throw new WebScriptException(Status.STATUS_INTERNAL_SERVER_ERROR, "Failed to write JSON", e); throw new WebScriptException(Status.STATUS_INTERNAL_SERVER_ERROR, "Failed to write JSON", e);
} }
finally
{
os.close();
}
} }
} }

View File

@@ -126,14 +126,13 @@ public class PostSnapshotCommandProcessor implements CommandProcessor
logger.debug("success"); logger.debug("success");
resp.setStatus(Status.STATUS_OK); resp.setStatus(Status.STATUS_OK);
OutputStream out = resp.getOutputStream(); try (OutputStream out = resp.getOutputStream())
resp.setContentType("text/xml"); {
resp.setContentEncoding("utf-8"); resp.setContentType("text/xml");
resp.setContentEncoding("utf-8");
receiver.generateRequsite(transferId, out);
out.close();
receiver.generateRequsite(transferId, out);
}
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@@ -2,7 +2,7 @@
* #%L * #%L
* Alfresco Remote API * 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. * This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of * 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.content.BinaryResource;
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo; import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
import org.alfresco.rest.framework.resource.parameters.Parameters; import org.alfresco.rest.framework.resource.parameters.Parameters;
import org.alfresco.service.cmr.repository.DirectAccessUrl;
/** /**
* Handles trashcan / deleted nodes * Handles trashcan / deleted nodes
@@ -99,4 +100,29 @@ public interface DeletedNodes
* @return * @return
*/ */
CollectionWithPagingInfo<Rendition> getRenditions(String archivedId, Parameters parameters); CollectionWithPagingInfo<Rendition> 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);
} }

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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;
}
}

View File

@@ -2,7 +2,7 @@
* #%L * #%L
* Alfresco Remote API * 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. * This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of * 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.CollectionWithPagingInfo;
import org.alfresco.rest.framework.resource.parameters.Parameters; import org.alfresco.rest.framework.resource.parameters.Parameters;
import org.alfresco.service.cmr.repository.ChildAssociationRef; 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.NodeRef;
import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.QName;
@@ -266,6 +267,49 @@ public interface Nodes
*/ */
Node unlock(String nodeId, Parameters parameters); 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 * 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 * the respository to a map of String to Object that can be formatted/expressed

View File

@@ -2,7 +2,7 @@
* #%L * #%L
* Alfresco Remote API * 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. * This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of * 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.content.BinaryResource;
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo; import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
import org.alfresco.rest.framework.resource.parameters.Parameters; 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.NodeRef;
import org.alfresco.service.cmr.repository.StoreRef;
import java.util.List; import java.util.List;
@@ -186,5 +188,58 @@ public interface Renditions
* @return the rendition stream * @return the rendition stream
*/ */
BinaryResource getContentNoValidation(NodeRef nodeRef, String versionId, String renditionId, Parameters parameters); 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);
} }

View File

@@ -2,7 +2,7 @@
* #%L * #%L
* Alfresco Remote API * 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. * This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of * If the software was purchased under a paid Alfresco license, the terms of
@@ -25,6 +25,7 @@
*/ */
package org.alfresco.rest.api.discovery; 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.DiscoveryDetails;
import org.alfresco.rest.api.model.ModulePackage; import org.alfresco.rest.api.model.ModulePackage;
import org.alfresco.rest.api.model.RepositoryInfo; 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.ModuleDetails;
import org.alfresco.service.cmr.module.ModuleService; import org.alfresco.service.cmr.module.ModuleService;
import org.alfresco.service.cmr.quickshare.QuickShareService; 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.cmr.thumbnail.ThumbnailService;
import org.alfresco.service.descriptor.Descriptor; import org.alfresco.service.descriptor.Descriptor;
import org.alfresco.service.descriptor.DescriptorService; import org.alfresco.service.descriptor.DescriptorService;
@@ -67,6 +69,8 @@ public class DiscoveryApiWebscript extends AbstractWebScript implements Recogniz
private ModuleService moduleService; private ModuleService moduleService;
private ApiAssistant assistant; private ApiAssistant assistant;
private ThumbnailService thumbnailService; private ThumbnailService thumbnailService;
private RestApiDirectUrlConfig restApiDirectUrlConfig;
private ContentService contentService;
private boolean enabled = true; private boolean enabled = true;
private final static String DISABLED = "Not Implemented"; private final static String DISABLED = "Not Implemented";
@@ -106,6 +110,16 @@ public class DiscoveryApiWebscript extends AbstractWebScript implements Recogniz
this.thumbnailService = thumbnailService; this.thumbnailService = thumbnailService;
} }
public void setRestApiDirectUrlConfig(RestApiDirectUrlConfig restApiDirectUrlConfig)
{
this.restApiDirectUrlConfig = restApiDirectUrlConfig;
}
public void setContentService(ContentService contentService)
{
this.contentService = contentService;
}
@Override @Override
public void afterPropertiesSet() throws Exception public void afterPropertiesSet() throws Exception
{ {
@@ -116,6 +130,8 @@ public class DiscoveryApiWebscript extends AbstractWebScript implements Recogniz
PropertyCheck.mandatory(this, "moduleService", moduleService); PropertyCheck.mandatory(this, "moduleService", moduleService);
PropertyCheck.mandatory(this, "assistant", assistant); PropertyCheck.mandatory(this, "assistant", assistant);
PropertyCheck.mandatory(this, "thumbnailService", thumbnailService); PropertyCheck.mandatory(this, "thumbnailService", thumbnailService);
PropertyCheck.mandatory(this, "restApiDirectUrlConfig", restApiDirectUrlConfig);
PropertyCheck.mandatory(this, "contentService", contentService);
} }
@Override @Override
@@ -154,7 +170,8 @@ public class DiscoveryApiWebscript extends AbstractWebScript implements Recogniz
.setReadOnly(repoAdminService.getUsage().isReadOnly()) .setReadOnly(repoAdminService.getUsage().isReadOnly())
.setAuditEnabled(auditService.isAuditEnabled()) .setAuditEnabled(auditService.isAuditEnabled())
.setQuickShareEnabled(quickShareService.isQuickShareEnabled()) .setQuickShareEnabled(quickShareService.isQuickShareEnabled())
.setThumbnailGenerationEnabled(thumbnailService.getThumbnailsEnabled())); .setThumbnailGenerationEnabled(thumbnailService.getThumbnailsEnabled())
.setDirectAccessUrlEnabled(isContentDirectUrlEnabled()));
} }
private List<ModulePackage> getModules() private List<ModulePackage> getModules()
@@ -194,4 +211,10 @@ public class DiscoveryApiWebscript extends AbstractWebScript implements Recogniz
throw new DisabledServiceException(DISABLED); throw new DisabledServiceException(DISABLED);
} }
} }
protected boolean isContentDirectUrlEnabled()
{
return (restApiDirectUrlConfig.isEnabled() && contentService.isContentDirectUrlEnabled());
}
} }

View File

@@ -2,7 +2,7 @@
* #%L * #%L
* Alfresco Remote API * 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. * This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of * 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.CollectionWithPagingInfo;
import org.alfresco.rest.framework.resource.parameters.Parameters; import org.alfresco.rest.framework.resource.parameters.Parameters;
import org.alfresco.rest.framework.tools.RecognizedParamsExtractor; 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.NodeRef;
import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.StoreRef; 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); NodeRef nodeRef = new NodeRef(StoreRef.STORE_REF_ARCHIVE_SPACESSTORE, archivedId);
return renditions.getRenditions(nodeRef, parameters); 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);
}
}
} }

View File

@@ -2,7 +2,7 @@
* #%L * #%L
* Alfresco Remote API * 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. * This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of * 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.ContentIOException;
import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.ContentService;
import org.alfresco.service.cmr.repository.ContentWriter; 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.DuplicateChildNodeNameException;
import org.alfresco.service.cmr.repository.InvalidNodeRefException; import org.alfresco.service.cmr.repository.InvalidNodeRefException;
import org.alfresco.service.cmr.repository.MimetypeService; import org.alfresco.service.cmr.repository.MimetypeService;
@@ -3478,6 +3479,20 @@ public class NodesImpl implements Nodes
return getFolderOrDocument(nodeId, parameters); 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 * Checks if same permission is sent more than once
* @param locallySetPermissions * @param locallySetPermissions

View File

@@ -2,7 +2,7 @@
* #%L * #%L
* Alfresco Remote API * 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. * This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of * If the software was purchased under a paid Alfresco license, the terms of
@@ -26,6 +26,19 @@
package org.alfresco.rest.api.impl; 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.heartbeat.RenditionsDataCollector;
import org.alfresco.model.ContentModel; import org.alfresco.model.ContentModel;
import org.alfresco.query.PagingResults; 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.ServiceRegistry;
import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.ContentData; 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.MimetypeService;
import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService; 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.context.ResourceLoaderAware;
import org.springframework.core.io.ResourceLoader; 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 * @author Jamal Kaabi-Mofrad, janv
*/ */
@@ -483,6 +484,22 @@ public class RenditionsImpl implements Renditions, ResourceLoaderAware
return getContentImpl(nodeRef, renditionId, parameters); 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) private BinaryResource getContentImpl(NodeRef nodeRef, String renditionId, Parameters parameters)
{ {
NodeRef renditionNodeRef = getRenditionByName(nodeRef, renditionId, parameters); NodeRef renditionNodeRef = getRenditionByName(nodeRef, renditionId, parameters);

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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);
}
}
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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;
}
}

View File

@@ -2,7 +2,7 @@
* #%L * #%L
* Alfresco Remote API * 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. * This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of * 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 isAuditEnabled;
private boolean isQuickShareEnabled; private boolean isQuickShareEnabled;
private boolean isThumbnailGenerationEnabled; private boolean isThumbnailGenerationEnabled;
private boolean isDirectAccessUrlEnabled;
public StatusInfo() public StatusInfo()
{ {
@@ -465,6 +466,17 @@ public class RepositoryInfo
return this; return this;
} }
public boolean getIsDirectAccessUrlEnabled()
{
return isDirectAccessUrlEnabled;
}
public StatusInfo setDirectAccessUrlEnabled(boolean isDirectAccessUrlEnabled)
{
this.isDirectAccessUrlEnabled = isDirectAccessUrlEnabled;
return this;
}
@Override @Override
public String toString() public String toString()
{ {
@@ -473,6 +485,7 @@ public class RepositoryInfo
.append(", isAuditEnabled=").append(isAuditEnabled) .append(", isAuditEnabled=").append(isAuditEnabled)
.append(", isQuickShareEnabled=").append(isQuickShareEnabled) .append(", isQuickShareEnabled=").append(isQuickShareEnabled)
.append(", isThumbnailGenerationEnabled=").append(isThumbnailGenerationEnabled) .append(", isThumbnailGenerationEnabled=").append(isThumbnailGenerationEnabled)
.append(", isDirectAccessUrlEnabled=").append(isDirectAccessUrlEnabled)
.append(']'); .append(']');
return sb.toString(); return sb.toString();
} }

View File

@@ -2,7 +2,7 @@
* #%L * #%L
* Alfresco Remote API * 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. * This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of * If the software was purchased under a paid Alfresco license, the terms of
@@ -26,16 +26,27 @@
package org.alfresco.rest.api.nodes; 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.Renditions;
import org.alfresco.rest.api.model.DirectAccessUrlRequest;
import org.alfresco.rest.api.model.Rendition; import org.alfresco.rest.api.model.Rendition;
import org.alfresco.rest.framework.BinaryProperties; import org.alfresco.rest.framework.BinaryProperties;
import org.alfresco.rest.framework.Operation;
import org.alfresco.rest.framework.WebApiDescription; 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.RelationshipResource;
import org.alfresco.rest.framework.resource.actions.interfaces.RelationshipResourceAction; import org.alfresco.rest.framework.resource.actions.interfaces.RelationshipResourceAction;
import org.alfresco.rest.framework.resource.actions.interfaces.RelationshipResourceBinaryAction; import org.alfresco.rest.framework.resource.actions.interfaces.RelationshipResourceBinaryAction;
import org.alfresco.rest.framework.resource.content.BinaryResource; import org.alfresco.rest.framework.resource.content.BinaryResource;
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo; import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
import org.alfresco.rest.framework.resource.parameters.Parameters; 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.NodeRef;
import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.util.PropertyCheck; import org.alfresco.util.PropertyCheck;
@@ -58,12 +69,18 @@ public class NodeRenditionsRelation implements RelationshipResourceAction.Read<R
{ {
private Renditions renditions; private Renditions renditions;
private DirectAccessUrlHelper directAccessUrlHelper;
public void setRenditions(Renditions renditions) public void setRenditions(Renditions renditions)
{ {
this.renditions = renditions; this.renditions = renditions;
} }
public void setDirectAccessUrlHelper(DirectAccessUrlHelper directAccessUrlHelper)
{
this.directAccessUrlHelper = directAccessUrlHelper;
}
@Override @Override
public void afterPropertiesSet() throws Exception public void afterPropertiesSet() throws Exception
{ {
@@ -102,4 +119,26 @@ public class NodeRenditionsRelation implements RelationshipResourceAction.Read<R
return renditions.getContent(nodeRef, renditionId, parameters); return renditions.getContent(nodeRef, 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 nodeId, String renditionId, DirectAccessUrlRequest directAccessUrlRequest, Parameters parameters, WithResponse withResponse)
{
boolean attachment = directAccessUrlHelper.getAttachment(directAccessUrlRequest);
Long validFor = directAccessUrlHelper.getDefaultExpiryTimeInSec();
NodeRef nodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, nodeId);
DirectAccessUrl directAccessUrl;
try
{
directAccessUrl = renditions.requestContentDirectUrl(nodeRef, null, renditionId, attachment, validFor);
}
catch (DirectAccessUrlDisabledException ex)
{
throw new DisabledServiceException(ex.getMessage());
}
return directAccessUrl;
}
} }

View File

@@ -2,7 +2,7 @@
* #%L * #%L
* Alfresco Remote API * 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. * This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of * If the software was purchased under a paid Alfresco license, the terms of
@@ -26,6 +26,8 @@
package org.alfresco.rest.api.nodes; package org.alfresco.rest.api.nodes;
import java.util.List;
import org.alfresco.rest.api.Renditions; import org.alfresco.rest.api.Renditions;
import org.alfresco.rest.api.model.Rendition; import org.alfresco.rest.api.model.Rendition;
import org.alfresco.rest.framework.BinaryProperties; import org.alfresco.rest.framework.BinaryProperties;
@@ -42,8 +44,6 @@ import org.alfresco.util.PropertyCheck;
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.InitializingBean;
import org.springframework.extensions.webscripts.Status; import org.springframework.extensions.webscripts.Status;
import java.util.List;
/** /**
* *
* Node version renditions * Node version renditions

View File

@@ -2,7 +2,7 @@
* #%L * #%L
* Alfresco Remote API * 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. * This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of * If the software was purchased under a paid Alfresco license, the terms of
@@ -26,16 +26,22 @@
package org.alfresco.rest.api.nodes; package org.alfresco.rest.api.nodes;
import org.alfresco.model.ContentModel; import org.alfresco.model.ContentModel;
import org.alfresco.repo.content.directurl.DirectAccessUrlDisabledException;
import org.alfresco.repo.node.integrity.IntegrityException; import org.alfresco.repo.node.integrity.IntegrityException;
import org.alfresco.repo.policy.BehaviourFilter; import org.alfresco.repo.policy.BehaviourFilter;
import org.alfresco.repo.version.Version2Model; import org.alfresco.repo.version.Version2Model;
import org.alfresco.repo.version.VersionModel; import org.alfresco.repo.version.VersionModel;
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.Node;
import org.alfresco.rest.api.model.UserInfo; import org.alfresco.rest.api.model.UserInfo;
import org.alfresco.rest.api.model.VersionOptions; import org.alfresco.rest.api.model.VersionOptions;
import org.alfresco.rest.framework.BinaryProperties; import org.alfresco.rest.framework.BinaryProperties;
import org.alfresco.rest.framework.Operation; import org.alfresco.rest.framework.Operation;
import org.alfresco.rest.framework.WebApiDescription; 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.core.exceptions.EntityNotFoundException;
import org.alfresco.rest.framework.core.exceptions.PermissionDeniedException; import org.alfresco.rest.framework.core.exceptions.PermissionDeniedException;
import org.alfresco.rest.framework.resource.RelationshipResource; import org.alfresco.rest.framework.resource.RelationshipResource;
@@ -46,6 +52,7 @@ import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
import org.alfresco.rest.framework.resource.parameters.Parameters; import org.alfresco.rest.framework.resource.parameters.Parameters;
import org.alfresco.rest.framework.webscripts.WithResponse; import org.alfresco.rest.framework.webscripts.WithResponse;
import org.alfresco.service.cmr.coci.CheckOutCheckInService; import org.alfresco.service.cmr.coci.CheckOutCheckInService;
import org.alfresco.service.cmr.repository.DirectAccessUrl;
import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.security.AccessStatus; import org.alfresco.service.cmr.security.AccessStatus;
import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.cmr.security.PermissionService;
@@ -80,6 +87,12 @@ public class NodeVersionsRelation extends AbstractNodeRelation implements
{ {
protected VersionService versionService; protected VersionService versionService;
protected BehaviourFilter behaviourFilter; protected BehaviourFilter behaviourFilter;
private DirectAccessUrlHelper directAccessUrlHelper;
public void setDirectAccessUrlHelper(DirectAccessUrlHelper directAccessUrlHelper)
{
this.directAccessUrlHelper = directAccessUrlHelper;
}
@Override @Override
public void afterPropertiesSet() public void afterPropertiesSet()
@@ -288,4 +301,32 @@ public class NodeVersionsRelation extends AbstractNodeRelation implements
} }
return null; return null;
} }
@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, String versionId, DirectAccessUrlRequest directAccessUrlRequest, Parameters parameters, WithResponse withResponse)
{
boolean attachment = directAccessUrlHelper.getAttachment(directAccessUrlRequest);
Long validFor = directAccessUrlHelper.getDefaultExpiryTimeInSec();
Version version = findVersion(nodeId, versionId);
if (version != null)
{
NodeRef versionNodeRef = version.getFrozenStateNodeRef();
DirectAccessUrl directAccessUrl;
try
{
directAccessUrl = nodes.requestContentDirectUrl(versionNodeRef, attachment, validFor);
}
catch (DirectAccessUrlDisabledException ex)
{
throw new DisabledServiceException(ex.getMessage());
}
return directAccessUrl;
}
throw new EntityNotFoundException(nodeId+"-"+versionId);
}
} }

View File

@@ -2,7 +2,7 @@
* #%L * #%L
* Alfresco Remote API * 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. * This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of * If the software was purchased under a paid Alfresco license, the terms of
@@ -25,11 +25,13 @@
*/ */
package org.alfresco.rest.api.nodes; package org.alfresco.rest.api.nodes;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream; import java.io.InputStream;
import javax.servlet.http.HttpServletResponse; import org.alfresco.repo.content.directurl.DirectAccessUrlDisabledException;
import org.alfresco.rest.api.DirectAccessUrlHelper;
import org.alfresco.rest.api.Nodes; import org.alfresco.rest.api.Nodes;
import org.alfresco.rest.api.model.DirectAccessUrlRequest;
import org.alfresco.rest.api.model.LockInfo; import org.alfresco.rest.api.model.LockInfo;
import org.alfresco.rest.api.model.Node; import org.alfresco.rest.api.model.Node;
import org.alfresco.rest.api.model.NodeTarget; import org.alfresco.rest.api.model.NodeTarget;
@@ -37,6 +39,8 @@ import org.alfresco.rest.framework.BinaryProperties;
import org.alfresco.rest.framework.Operation; import org.alfresco.rest.framework.Operation;
import org.alfresco.rest.framework.WebApiDescription; import org.alfresco.rest.framework.WebApiDescription;
import org.alfresco.rest.framework.WebApiParam; 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.core.exceptions.EntityNotFoundException;
import org.alfresco.rest.framework.resource.EntityResource; import org.alfresco.rest.framework.resource.EntityResource;
import org.alfresco.rest.framework.resource.actions.interfaces.BinaryResourceAction; import org.alfresco.rest.framework.resource.actions.interfaces.BinaryResourceAction;
@@ -45,7 +49,10 @@ import org.alfresco.rest.framework.resource.content.BasicContentInfo;
import org.alfresco.rest.framework.resource.content.BinaryResource; import org.alfresco.rest.framework.resource.content.BinaryResource;
import org.alfresco.rest.framework.resource.parameters.Parameters; import org.alfresco.rest.framework.resource.parameters.Parameters;
import org.alfresco.rest.framework.webscripts.WithResponse; import org.alfresco.rest.framework.webscripts.WithResponse;
import org.alfresco.service.cmr.repository.DirectAccessUrl;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.util.ParameterCheck; import org.alfresco.util.ParameterCheck;
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.InitializingBean;
/** /**
@@ -61,13 +68,19 @@ public class NodesEntityResource implements
BinaryResourceAction.Read, BinaryResourceAction.Update<Node>, InitializingBean BinaryResourceAction.Read, BinaryResourceAction.Update<Node>, InitializingBean
{ {
private Nodes nodes; private Nodes nodes;
private DirectAccessUrlHelper directAccessUrlHelper;
public void setNodes(Nodes nodes) public void setNodes(Nodes nodes)
{ {
this.nodes = nodes; this.nodes = nodes;
} }
@Override public void setDirectAccessUrlHelper(DirectAccessUrlHelper directAccessUrlHelper)
{
this.directAccessUrlHelper = directAccessUrlHelper;
}
@Override
public void afterPropertiesSet() public void afterPropertiesSet()
{ {
ParameterCheck.mandatory("nodes", this.nodes); ParameterCheck.mandatory("nodes", this.nodes);
@@ -189,5 +202,27 @@ public class NodesEntityResource implements
return nodes.unlock(nodeId, parameters); 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;
}
} }

View File

@@ -2,7 +2,7 @@
* #%L * #%L
* Alfresco Remote API * 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. * This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of * 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 javax.servlet.http.HttpServletResponse;
import org.alfresco.repo.content.directurl.DirectAccessUrlDisabledException;
import org.alfresco.rest.api.DeletedNodes; 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.Node;
import org.alfresco.rest.api.model.NodeTargetAssoc; import org.alfresco.rest.api.model.NodeTargetAssoc;
import org.alfresco.rest.framework.BinaryProperties; 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.WebApiDescription;
import org.alfresco.rest.framework.WebApiParam; import org.alfresco.rest.framework.WebApiParam;
import org.alfresco.rest.framework.core.ResourceParameter; 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.core.exceptions.EntityNotFoundException;
import org.alfresco.rest.framework.resource.EntityResource; import org.alfresco.rest.framework.resource.EntityResource;
import org.alfresco.rest.framework.resource.actions.interfaces.BinaryResourceAction; 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.CollectionWithPagingInfo;
import org.alfresco.rest.framework.resource.parameters.Parameters; import org.alfresco.rest.framework.resource.parameters.Parameters;
import org.alfresco.rest.framework.webscripts.WithResponse; import org.alfresco.rest.framework.webscripts.WithResponse;
import org.alfresco.service.cmr.repository.DirectAccessUrl;
/** /**
* An implementation of an Entity Resource for handling archived content * An implementation of an Entity Resource for handling archived content
@@ -55,12 +60,18 @@ public class TrashcanEntityResource implements
EntityResourceAction.ReadById<Node>, EntityResourceAction.Read<Node>, EntityResourceAction.Delete, BinaryResourceAction.Read EntityResourceAction.ReadById<Node>, EntityResourceAction.Read<Node>, EntityResourceAction.Delete, BinaryResourceAction.Read
{ {
private DeletedNodes deletedNodes; private DeletedNodes deletedNodes;
private DirectAccessUrlHelper directAccessUrlHelper;
public void setDeletedNodes(DeletedNodes deletedNodes) public void setDeletedNodes(DeletedNodes deletedNodes)
{ {
this.deletedNodes = deletedNodes; this.deletedNodes = deletedNodes;
} }
public void setDirectAccessUrlHelper(DirectAccessUrlHelper directAccessUrlHelper)
{
this.directAccessUrlHelper = directAccessUrlHelper;
}
@Override @Override
public CollectionWithPagingInfo<Node> readAll(Parameters params) public CollectionWithPagingInfo<Node> readAll(Parameters params)
{ {
@@ -89,6 +100,27 @@ public class TrashcanEntityResource implements
return deletedNodes.getContent(nodeId, null, parameters); 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 @Override
public void delete(String nodeId, Parameters parameters) public void delete(String nodeId, Parameters parameters)
{ {

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