From bc666f2b26f0986f697f6e7c8f5c89c2daad9f4f Mon Sep 17 00:00:00 2001
From: Nicolas Barithel
Date: Thu, 25 Jul 2019 18:26:59 +0200
Subject: [PATCH 01/58] Project 'alfresco-filer' layout
- Maven build with a clear separation between root (project general
requirements), quality, parent (project quality and test requirements)
and core (Alfresco repository extension to build filer tree structures)
- Jenkins CI configuration
- Alfresco JAR module 'filer' with initial configuration of Spring
context, log4j and Alfresco properties files
- Code Quality with PMD and Checkstyle
- Unit testing with JUnit, AssertJ, Mockito and PostgreSQL
- First test to check the successful startup of the repository
Change-Id: I0130beacc421290279ec23128c237fdb7f5ca857
---
.gitignore | 1 +
Jenkinsfile | 98 ++++++
LICENSE | 201 ++++++++++++
README.md | 12 +
alfresco-filer-core/assembly-delivery-amp.xml | 24 ++
alfresco-filer-core/pom.xml | 201 ++++++++++++
.../main/config/alfresco-global.properties | 1 +
.../src/main/config/log4j.properties | 1 +
.../src/main/config/module-context.xml | 7 +
.../src/main/config/module.properties | 6 +
.../core/test/content/ContextStartupTest.java | 60 ++++
.../core/test/util/PostgreSQLExtension.java | 94 ++++++
.../test/resources/alfresco-global.properties | 11 +
.../alfresco/extension/test-log4j.properties | 1 +
.../test/resources/junit-platform.properties | 3 +
alfresco-filer-parent/pom.xml | 186 +++++++++++
alfresco-filer-quality/pom.xml | 18 ++
.../filer/checkstyle-suppressions.xml | 5 +
.../src/main/resources/filer/checkstyle.xml | 126 ++++++++
.../src/main/resources/filer/pmd-ruleset.xml | 73 +++++
pom.xml | 305 ++++++++++++++++++
21 files changed, 1434 insertions(+)
create mode 100644 .gitignore
create mode 100644 Jenkinsfile
create mode 100644 LICENSE
create mode 100644 README.md
create mode 100644 alfresco-filer-core/assembly-delivery-amp.xml
create mode 100644 alfresco-filer-core/pom.xml
create mode 100644 alfresco-filer-core/src/main/config/alfresco-global.properties
create mode 100644 alfresco-filer-core/src/main/config/log4j.properties
create mode 100644 alfresco-filer-core/src/main/config/module-context.xml
create mode 100644 alfresco-filer-core/src/main/config/module.properties
create mode 100644 alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/content/ContextStartupTest.java
create mode 100644 alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/util/PostgreSQLExtension.java
create mode 100644 alfresco-filer-core/src/test/resources/alfresco-global.properties
create mode 100644 alfresco-filer-core/src/test/resources/alfresco/extension/test-log4j.properties
create mode 100644 alfresco-filer-core/src/test/resources/junit-platform.properties
create mode 100644 alfresco-filer-parent/pom.xml
create mode 100644 alfresco-filer-quality/pom.xml
create mode 100644 alfresco-filer-quality/src/main/resources/filer/checkstyle-suppressions.xml
create mode 100644 alfresco-filer-quality/src/main/resources/filer/checkstyle.xml
create mode 100644 alfresco-filer-quality/src/main/resources/filer/pmd-ruleset.xml
create mode 100644 pom.xml
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..2f7896d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+target/
diff --git a/Jenkinsfile b/Jenkinsfile
new file mode 100644
index 0000000..f29a8c8
--- /dev/null
+++ b/Jenkinsfile
@@ -0,0 +1,98 @@
+#!/usr/bin/env groovy
+
+pipeline {
+ agent {
+ docker {
+ image 'maven:3.6-jdk-11-slim'
+ // Add configuration for Nexus repositories and user/group mapping for uid:gid of 1000:1000
+ args "-v ${env.JENKINS_HOME}/.m2/settings.xml:/tmp/settings.xml:ro -v /etc/passwd:/etc/passwd:ro -v /etc/group:/etc/group:ro"
+ }
+ }
+ triggers {
+ // Empty string, to allow post-commit hook to notify Jenkins
+ pollSCM ''
+ }
+ options {
+ // LogRotator will keep the 10 most recents jobs and retain the artifacts only for the 2 latest
+ buildDiscarder(logRotator(numToKeepStr: '10', artifactNumToKeepStr: '2'))
+ }
+ parameters {
+ string(name: 'MAVEN_OPTIONS', defaultValue: '', description: 'Optional parameters to be added to the mvn command line')
+ booleanParam(name: 'FORCE_DEPLOY', defaultValue: false, description: 'Force the execution of the deploy stage')
+ }
+ environment {
+ MAVEN_GLOBAL_OPTIONS = "-Duser.home=${env.WORKSPACE} -s /tmp/settings.xml --batch-mode --errors -Pdelivery"
+ }
+
+ stages {
+ stage('Build') {
+ environment {
+ MAVEN_OPTIONS = "${env.MAVEN_GLOBAL_OPTIONS} -U -DskipTests ${params.MAVEN_OPTIONS}"
+ }
+ steps {
+ // Print disk space
+ sh 'df -h $WORKSPACE'
+ // Run the maven build (mvn is in the PATH of the Docker image)
+ sh 'mvn $MAVEN_OPTIONS clean compile'
+ }
+ }
+ stage('Test') {
+ // Do not run the tests when performing the release on master, they should have run earlier
+ when {
+ not { branch 'master' }
+ }
+ environment {
+ MAVEN_OPTIONS = "${env.MAVEN_GLOBAL_OPTIONS} ${params.MAVEN_OPTIONS}"
+ }
+ steps {
+ sh 'mvn $MAVEN_OPTIONS verify'
+ }
+ }
+ stage('Package') {
+ environment {
+ // Uses maven.test.skip rather than skipTests to also ignore the goals compile:testCompile, resources:testResources
+ MAVEN_OPTIONS = "${env.MAVEN_GLOBAL_OPTIONS} -Dmaven.test.skip=true ${params.MAVEN_OPTIONS}"
+ }
+ steps {
+ sh 'mvn $MAVEN_OPTIONS package'
+ }
+ }
+ stage('Deploy') {
+ when {
+ anyOf {
+ branch 'master'
+ branch 'dev'
+ buildingTag()
+ expression { params.FORCE_DEPLOY }
+ }
+ }
+ environment {
+ MAVEN_OPTIONS = "${env.MAVEN_GLOBAL_OPTIONS} -Dmaven.test.skip=true ${params.MAVEN_OPTIONS}"
+ }
+ steps {
+ sh 'mvn $MAVEN_OPTIONS deploy'
+ }
+ }
+ }
+ post {
+ always {
+ junit testResults: '**/target/surefire-reports/TEST-*.xml', allowEmptyResults: true
+ recordIssues(
+ enabledForFailure: true, aggregatingResults: true,
+ tools: [
+ mavenConsole(),
+ java(),
+ checkStyle(pattern: '**/target/checkstyle-result.xml', reportEncoding: 'UTF-8'),
+ pmdParser(pattern: '**/target/pmd.xml')
+ ]
+ )
+ }
+ success {
+ archiveArtifacts artifacts: '**/target/*.jar', fingerprint: true
+ archiveArtifacts artifacts: '**/target/*.amp', fingerprint: true, allowEmptyArchive: true
+ }
+ cleanup {
+ sh 'mvn $MAVEN_GLOBAL_OPTIONS --quiet clean'
+ }
+ }
+}
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..f49a4e1
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..33867c0
--- /dev/null
+++ b/README.md
@@ -0,0 +1,12 @@
+# AtolCD Alfresco Filer
+
+This is an Alfresco Content Services module to perform filer operations.
+
+# Few things to notice
+
+ * Runs with Alfresco Content Services 6.2 and JDK 11
+ * Standard JAR packaging and layout
+ * AMP as an assembly
+ * Tested with JUnit 5, Mockito 3 and PostgreSQL 10
+
+# TODO
diff --git a/alfresco-filer-core/assembly-delivery-amp.xml b/alfresco-filer-core/assembly-delivery-amp.xml
new file mode 100644
index 0000000..e9fb60a
--- /dev/null
+++ b/alfresco-filer-core/assembly-delivery-amp.xml
@@ -0,0 +1,24 @@
+
+ amp
+
+
+ amp
+
+
+ false
+
+
+
+ src/main/config/module.properties
+ true
+
+
+
+
+
+ lib
+
+
+
+
diff --git a/alfresco-filer-core/pom.xml b/alfresco-filer-core/pom.xml
new file mode 100644
index 0000000..6ab649a
--- /dev/null
+++ b/alfresco-filer-core/pom.xml
@@ -0,0 +1,201 @@
+
+
+ 4.0.0
+
+
+ com.atolcd.alfresco.filer
+ alfresco-filer-parent
+ 0.1.0-SNAPSHOT
+ ../alfresco-filer-parent/pom.xml
+
+
+ alfresco-filer-core
+
+ AtolCD Alfresco Filer - Core
+ Alfresco Content Services Module to perform filer operations
+
+
+ filer
+ ${project.version}
+ ${project.name}
+ ${project.description}
+
+ 6.2.0-A2
+
+ ${project.build.directory}/generated-test-resources
+
+
+
+
+
+ org.alfresco
+ content-services-community
+ ${alfresco-filer-core.alfresco-content-services.version}
+ classes
+ provided
+
+
+ cglib
+ cglib
+
+
+ commons-logging
+ commons-logging
+
+
+ junit
+ junit
+
+
+ net.sf.ehcache
+ ehcache-core
+
+
+ org.apache.chemistry.opencmis
+ chemistry-opencmis-test-tck
+
+
+
+
+
+
+
+ org.alfresco
+ content-services-community
+ classes
+
+
+ org.junit.jupiter
+ junit-jupiter
+
+
+ org.springframework
+ spring-test
+
+
+ org.mockito
+ mockito-core
+
+
+ org.mockito
+ mockito-junit-jupiter
+
+
+ org.assertj
+ assertj-core
+
+
+ com.opentable.components
+ otj-pg-embedded
+
+
+ javax.servlet
+ javax.servlet-api
+
+
+
+
+
+
+ src/main/resources
+
+
+ src/main/config
+ alfresco/module/${alfresco.module.name}
+ true
+
+
+
+
+ src/test/resources
+ true
+
+
+ ${alfresco-filer-core.generated-test-resources.directory}
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ unpack-alfresco-core-log4j-properties
+ generate-test-resources
+
+ unpack-dependencies
+
+
+ ${alfresco-filer-core.generated-test-resources.directory}
+ content-services-community
+ log4j.properties
+
+
+
+
+
+ com.google.code.maven-replacer-plugin
+ replacer
+
+
+ replace-log4j-properties
+ generate-test-resources
+
+ replace
+
+
+ ${alfresco-filer-core.generated-test-resources.directory}/log4j.properties
+
+ MULTILINE
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ delivery
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+
+ false
+ true
+
+ assembly-delivery-amp.xml
+
+
+
+
+ assembly-delivery-amp
+ package
+
+ single
+
+
+
+
+
+
+
+
+
diff --git a/alfresco-filer-core/src/main/config/alfresco-global.properties b/alfresco-filer-core/src/main/config/alfresco-global.properties
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/alfresco-filer-core/src/main/config/alfresco-global.properties
@@ -0,0 +1 @@
+
diff --git a/alfresco-filer-core/src/main/config/log4j.properties b/alfresco-filer-core/src/main/config/log4j.properties
new file mode 100644
index 0000000..a9b8a85
--- /dev/null
+++ b/alfresco-filer-core/src/main/config/log4j.properties
@@ -0,0 +1 @@
+log4j.logger.${project.groupId}.core=INFO
diff --git a/alfresco-filer-core/src/main/config/module-context.xml b/alfresco-filer-core/src/main/config/module-context.xml
new file mode 100644
index 0000000..35c0571
--- /dev/null
+++ b/alfresco-filer-core/src/main/config/module-context.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/alfresco-filer-core/src/main/config/module.properties b/alfresco-filer-core/src/main/config/module.properties
new file mode 100644
index 0000000..ad9a98a
--- /dev/null
+++ b/alfresco-filer-core/src/main/config/module.properties
@@ -0,0 +1,6 @@
+module.id=${alfresco.module.name}
+module.version=${alfresco.module.version}
+module.title=${alfresco.module.title}
+module.description=${alfresco.module.description}
+
+module.repo.version.min=5.2
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/content/ContextStartupTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/content/ContextStartupTest.java
new file mode 100644
index 0000000..72acf96
--- /dev/null
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/content/ContextStartupTest.java
@@ -0,0 +1,60 @@
+package com.atolcd.alfresco.filer.core.test.content;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.UUID;
+
+import org.alfresco.model.ContentModel;
+import org.alfresco.repo.security.authentication.AuthenticationComponent;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.repository.NodeService;
+import org.alfresco.service.cmr.repository.StoreRef;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.transaction.support.TransactionSynchronizationManager;
+
+import com.atolcd.alfresco.filer.core.test.util.PostgreSQLExtension;
+
+@ExtendWith(PostgreSQLExtension.class)
+@ExtendWith(SpringExtension.class)
+@ContextConfiguration({
+ "classpath:alfresco/application-context.xml"
+})
+@Transactional
+public class ContextStartupTest {
+
+ @Autowired
+ @Qualifier("NodeService")
+ private NodeService nodeService;
+
+ @Autowired
+ private AuthenticationComponent authenticationComponent;
+
+ @BeforeEach
+ public void setUpTest() {
+ authenticationComponent.setSystemUserAsCurrentUser();
+ }
+
+ @AfterEach
+ public void tearDown() {
+ authenticationComponent.clearCurrentSecurityContext();
+ }
+
+ @Test
+ public void basicWriteOperations() {
+ assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isTrue();
+ String rootNodeName = UUID.randomUUID().toString();
+
+ NodeRef rootNodeRef = nodeService.getRootNode(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE);
+ nodeService.setProperty(rootNodeRef, ContentModel.PROP_NAME, rootNodeName);
+
+ assertThat(nodeService.getProperty(rootNodeRef, ContentModel.PROP_NAME)).isEqualTo(rootNodeName);
+ }
+}
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/util/PostgreSQLExtension.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/util/PostgreSQLExtension.java
new file mode 100644
index 0000000..ec926c2
--- /dev/null
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/util/PostgreSQLExtension.java
@@ -0,0 +1,94 @@
+package com.atolcd.alfresco.filer.core.test.util;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.commons.lang3.RandomStringUtils;
+import org.junit.jupiter.api.extension.BeforeAllCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.opentable.db.postgres.embedded.EmbeddedPostgres;
+
+public class PostgreSQLExtension implements BeforeAllCallback {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(PostgreSQLExtension.class);
+
+ private static final String PORT_PROPERTY = "DB_PORT";
+ private static final String DATABASE_NAME_PROPERTY = "DB_NAME";
+ private static final String USERNAME_PROPERTY = "DB_USERNAME";
+
+ /**
+ * Cache for the database.
+ *
+ * This needs to be static, since test instances may be destroyed and recreated
+ * between invocations of individual test methods, as is the case with JUnit.
+ *
+ * @see org.springframework.test.context.TestContextManager#contextCache
+ */
+ private static final Map, EmbeddedPostgres> CACHE = new ConcurrentHashMap<>();
+
+ @Override
+ public void beforeAll(final ExtensionContext context) throws Exception {
+ CACHE.computeIfAbsent(getClass(), this::initDatabase);
+ }
+
+ private EmbeddedPostgres initDatabase(final Class> testClass) { // NOPMD - method must be a Function
+ try {
+ EmbeddedPostgres database = EmbeddedPostgres.builder().start();
+
+ // The database closes itself automatically by adding a shutdown hook to the JVM
+
+ String userName = buildRandomName();
+ String databaseName = buildRandomName();
+
+ createDatabase(database, userName, databaseName);
+
+ LOGGER.info("Database {} created with user {} at port {}", databaseName, userName, database.getPort());
+
+ System.setProperty(PORT_PROPERTY, String.valueOf(database.getPort()));
+ System.setProperty(DATABASE_NAME_PROPERTY, databaseName);
+ System.setProperty(USERNAME_PROPERTY, userName);
+
+ return database;
+ } catch (RuntimeException e) { // NOPMD - for logging purposes
+ LOGGER.error("Error starting database: ", e);
+ throw e;
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ private void createDatabase(final EmbeddedPostgres embeddedPostgres, final String userName, final String databaseName) {
+ try (Connection connection = embeddedPostgres.getPostgresDatabase().getConnection();
+ PreparedStatement createUserStatement = connection
+ .prepareStatement(String.format("CREATE USER %s WITH CREATEDB", userName));
+ PreparedStatement createDatabaseStatement = connection
+ .prepareStatement(String.format("CREATE DATABASE %s OWNER %s ENCODING = 'utf8'", databaseName, userName))) {
+ createUserStatement.execute();
+ createDatabaseStatement.execute();
+ } catch (SQLException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ /**
+ * Build a random name (12 lower case alphabetic characters)
+ *
+ *
+ * Name generation is taken from {@link com.opentable.db.postgres.embedded.PreparedDbProvider.PrepPipeline#run()}
+ *
+ *
+ * @return Generated name
+ */
+ private static String buildRandomName() {
+ return RandomStringUtils.randomAlphabetic(12).toLowerCase(Locale.ENGLISH);
+ }
+}
diff --git a/alfresco-filer-core/src/test/resources/alfresco-global.properties b/alfresco-filer-core/src/test/resources/alfresco-global.properties
new file mode 100644
index 0000000..7f5c8e1
--- /dev/null
+++ b/alfresco-filer-core/src/test/resources/alfresco-global.properties
@@ -0,0 +1,11 @@
+dir.root=${project.build.directory}/test_data/alf_data
+
+db.driver=org.postgresql.Driver
+db.url=jdbc:postgresql://localhost:${DB_PORT}/${DB_NAME}
+db.username=${DB_USERNAME}
+
+jodconverter.enabled=false
+messaging.subsystem.autoStart=false
+local.transform.service.enabled=false
+legacy.transform.service.enabled=false
+ftp.enabled=false
diff --git a/alfresco-filer-core/src/test/resources/alfresco/extension/test-log4j.properties b/alfresco-filer-core/src/test/resources/alfresco/extension/test-log4j.properties
new file mode 100644
index 0000000..b0779cb
--- /dev/null
+++ b/alfresco-filer-core/src/test/resources/alfresco/extension/test-log4j.properties
@@ -0,0 +1 @@
+log4j.logger.${project.groupId}.core=DEBUG
diff --git a/alfresco-filer-core/src/test/resources/junit-platform.properties b/alfresco-filer-core/src/test/resources/junit-platform.properties
new file mode 100644
index 0000000..8670e43
--- /dev/null
+++ b/alfresco-filer-core/src/test/resources/junit-platform.properties
@@ -0,0 +1,3 @@
+junit.jupiter.execution.parallel.enabled=true
+junit.jupiter.execution.parallel.mode.default=same_thread
+junit.jupiter.execution.parallel.mode.classes.default=concurrent
diff --git a/alfresco-filer-parent/pom.xml b/alfresco-filer-parent/pom.xml
new file mode 100644
index 0000000..4c0c292
--- /dev/null
+++ b/alfresco-filer-parent/pom.xml
@@ -0,0 +1,186 @@
+
+
+ 4.0.0
+
+
+ com.atolcd.alfresco.filer
+ alfresco-filer
+ 0.1.0-SNAPSHOT
+
+
+ alfresco-filer-parent
+ pom
+
+ AtolCD Alfresco Filer - Parent
+
+
+ 3.1.0
+ 8.22
+
+ 3.12.0
+ 6.16.0
+ 2.22.2
+
+ 5.5.1
+ 5.1.8.RELEASE
+ 3.0.0
+ 3.12.2
+ 0.13.1
+ 3.0.1
+
+
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter
+ ${alfresco-filer-parent.junit.version}
+ test
+
+
+ org.springframework
+ spring-test
+ ${alfresco-filer-parent.spring.version}
+ test
+
+
+ commons-logging
+ commons-logging
+
+
+
+
+ org.mockito
+ mockito-core
+ ${alfresco-filer-parent.mockito.version}
+ test
+
+
+ org.mockito
+ mockito-junit-jupiter
+ ${alfresco-filer-parent.mockito.version}
+ test
+
+
+ org.assertj
+ assertj-core
+ ${alfresco-filer-parent.assertj.version}
+ test
+
+
+ com.opentable.components
+ otj-pg-embedded
+ ${alfresco-filer-parent.otj-pg-embedded.version}
+ test
+
+
+ javax.servlet
+ javax.servlet-api
+ ${alfresco-filer-parent.servlet-api.version}
+ test
+
+
+
+
+
+
+ alfresco-public
+ https://artifacts.alfresco.com/nexus/content/groups/public
+
+ false
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ ${alfresco-filer-parent.maven-surefire-plugin.version}
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-checkstyle-plugin
+ ${alfresco-filer-parent.maven-checkstyle-plugin.version}
+
+ filer/checkstyle.xml
+ filer/checkstyle-suppressions.xml
+ warning
+
+
+
+ check
+ validate
+
+ check
+
+
+ **/*.properties,**/*.xml,**/*(?<!-min).js,**/*(?<!-min).css,**/*.ftl,**/*.json,**/*.md,**/Vagrantfile
+ src/main/java/**/*.java,src/test/java/**/*.java,pom.xml,${alfresco-filer-parent.checkstyle.includes}
+
+ ${project.basedir}
+
+
+
+
+
+
+ com.atolcd.alfresco.filer
+ alfresco-filer-quality
+ 0.1.0-SNAPSHOT
+
+
+ com.puppycrawl.tools
+ checkstyle
+ ${alfresco-filer-parent.checkstyle.version}
+
+
+
+
+ org.apache.maven.plugins
+ maven-pmd-plugin
+ ${alfresco-filer-parent.maven-pmd-plugin.version}
+
+
+ filer/pmd-ruleset.xml
+
+ true
+ true
+ false
+
+
+
+ check
+ validate
+
+ check
+
+
+
+
+
+ com.atolcd.alfresco.filer
+ alfresco-filer-quality
+ 0.1.0-SNAPSHOT
+
+
+ net.sourceforge.pmd
+ pmd-core
+ ${alfresco-filer-parent.pmd.version}
+
+
+ net.sourceforge.pmd
+ pmd-java
+ ${alfresco-filer-parent.pmd.version}
+
+
+
+
+
+
diff --git a/alfresco-filer-quality/pom.xml b/alfresco-filer-quality/pom.xml
new file mode 100644
index 0000000..f8fb0dc
--- /dev/null
+++ b/alfresco-filer-quality/pom.xml
@@ -0,0 +1,18 @@
+
+
+ 4.0.0
+
+
+ com.atolcd.alfresco.filer
+ alfresco-filer
+ 0.1.0-SNAPSHOT
+
+
+ alfresco-filer-quality
+
+ AtolCD Alfresco Filer - Code Quality
+
+
+ true
+
+
diff --git a/alfresco-filer-quality/src/main/resources/filer/checkstyle-suppressions.xml b/alfresco-filer-quality/src/main/resources/filer/checkstyle-suppressions.xml
new file mode 100644
index 0000000..b0ae02a
--- /dev/null
+++ b/alfresco-filer-quality/src/main/resources/filer/checkstyle-suppressions.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/alfresco-filer-quality/src/main/resources/filer/checkstyle.xml b/alfresco-filer-quality/src/main/resources/filer/checkstyle.xml
new file mode 100644
index 0000000..c7694e9
--- /dev/null
+++ b/alfresco-filer-quality/src/main/resources/filer/checkstyle.xml
@@ -0,0 +1,126 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/alfresco-filer-quality/src/main/resources/filer/pmd-ruleset.xml b/alfresco-filer-quality/src/main/resources/filer/pmd-ruleset.xml
new file mode 100644
index 0000000..b0b548d
--- /dev/null
+++ b/alfresco-filer-quality/src/main/resources/filer/pmd-ruleset.xml
@@ -0,0 +1,73 @@
+
+
+
+
+ Code Quality Rules
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..bef8830
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,305 @@
+
+
+ 4.0.0
+
+ com.atolcd.alfresco.filer
+ alfresco-filer
+ 0.1.0-SNAPSHOT
+ pom
+
+ AtolCD Alfresco Filer
+ Alfresco Content Services Module to perform filer operations
+
+ Atol Conseils et Développements
+ https://www.atolcd.com
+
+
+
+ The Apache Software License, Version 2.0
+ http://www.apache.org/licenses/LICENSE-2.0.txt
+ repo
+
+
+
+
+ alfresco-filer-quality
+ alfresco-filer-parent
+ alfresco-filer-core
+
+
+
+ 3.6.0
+ 1.8
+
+ UTF-8
+ UTF-8
+ ${alfresco-filer.java.version}
+ ${alfresco-filer.java.version}
+
+ 1.4.1
+ 3.1.0
+ 3.1.0
+ 3.8.0
+ 3.1.2
+ 3.0.1
+ 3.0.1
+ 2.6
+ 2.5.2
+ 2.8.2
+ 4.0.0
+ 1.5.3
+ 1.0-m5.1
+ 1.1.0
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ ${alfresco-filer.maven-compiler-plugin.version}
+
+ none
+
+
+
+ org.apache.maven.plugins
+ maven-clean-plugin
+ ${alfresco-filer.maven-clean-plugin.version}
+
+
+ org.apache.maven.plugins
+ maven-resources-plugin
+ ${alfresco-filer.maven-resources-plugin.version}
+
+ ISO-8859-1
+
+ ${*}
+
+ false
+
+ zip
+ gz
+ pdf
+ xls
+ xlsx
+ doc
+ docx
+ jpg
+ jpeg
+ gif
+ png
+ svg
+ psd
+ ftl
+ lic
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ ${alfresco-filer.maven-jar-plugin.version}
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+ ${alfresco-filer.maven-javadoc-plugin.version}
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+ ${alfresco-filer.maven-assembly-plugin.version}
+
+
+ org.alfresco.maven.plugin
+ alfresco-maven-plugin
+ ${alfresco-filer.alfresco-maven-plugin.version}
+
+
+
+
+ org.apache.maven.plugins
+ maven-install-plugin
+ ${alfresco-filer.maven-install-plugin.version}
+
+
+ org.apache.maven.plugins
+ maven-deploy-plugin
+ ${alfresco-filer.maven-deploy-plugin.version}
+
+
+ com.google.code.maven-replacer-plugin
+ replacer
+ ${alfresco-filer.replacer-plugin.version}
+
+
+ external.atlassian.jgitflow
+ jgitflow-maven-plugin
+ ${alfresco-filer.jgitflow-maven-plugin.version}
+
+
+ master
+ dev
+ ft-
+ rl-
+ hf-
+ v
+
+ true
+ true
+ true
+ true
+ [release]
+ true
+
+
+
+
+ org.eclipse.m2e
+ lifecycle-mapping
+ 1.0.0
+
+
+
+
+
+ com.google.code.maven-replacer-plugin
+ replacer
+ [1.5.3,)
+
+ replace
+
+
+
+
+ false
+
+
+
+
+
+ org.codehaus.mojo
+ tidy-maven-plugin
+ [1.0.0,)
+
+ check
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-enforcer-plugin
+ ${alfresco-filer.maven-enforcer-plugin.version}
+
+
+ enforce
+
+ enforce
+
+
+
+
+
+
+ org.springframework:spring
+ org.springframework:spring-ldap
+ commons-logging:commons-logging
+ cglib:cglib
+ rhino:rhino
+ rhino:js
+ javassist:javassist
+ apache-xerces:xml-apis
+ javax.servlet:servlet-api
+ net.sf.ehcache:ehcache-core
+ postgresql:postgresql
+
+
+
+ [${alfresco-filer.maven.version},)
+
+
+ ${alfresco-filer.java.version}
+
+
+
+
+
+
+
+ org.codehaus.mojo
+ tidy-maven-plugin
+ ${alfresco-filer.tidy-maven-plugin.version}
+
+
+ check
+ validate
+
+ check
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+ ${alfresco-filer.maven-source-plugin.version}
+
+
+ attach-sources
+
+ jar-no-fork
+
+
+
+
+
+
+
+
+
+ development
+
+ true
+
+
+ true
+ true
+ true
+ true
+
+
+
+ delivery
+
+
+ maven-registry
+ Atol CD Releases
+ https://maven-registry.priv.atolcd.com
+
+
+ maven-snapshot-registry
+ Atol CD Snapshots
+ https://maven-snapshot-registry.priv.atolcd.com
+
+
+
+ false
+ true
+ false
+ false
+
+
+
+
From d3c9c9e9f8c32a7fa1f0b2b554dd6dfeb1f06560 Mon Sep 17 00:00:00 2001
From: Nicolas Barithel
Date: Wed, 31 Jul 2019 10:47:21 +0200
Subject: [PATCH 02/58] Initial implementation of the filer engine
- based on a policy which adds the fileable aspect inside a folder with
the subscriber aspect
- based on a policy which triggers the filing when the fileable aspect
is added, or when a node with the fileable aspect is moved or updated
- adds property inheritance from the parent folder for aspects with
propertyInheritance defined
- manages segment folders created and deleted automaticaly with segment
aspect
Change-Id: I165884effefcc17b66531c06871cb6c6c02fa44b
---
.../main/config/alfresco-global.properties | 5 +
.../src/main/config/context/model-context.xml | 14 ++
.../main/config/context/policy-context.xml | 25 ++
.../src/main/config/context/scope-context.xml | 30 +++
.../main/config/context/service-context.xml | 63 +++++
.../src/main/config/model/filerModel.xml | 37 +++
.../src/main/config/module-context.xml | 2 +-
.../filer/core/model/FilerAction.java | 14 ++
.../alfresco/filer/core/model/FilerEvent.java | 18 ++
.../filer/core/model/FilerException.java | 10 +
.../filer/core/model/FilerFolderContext.java | 63 +++++
.../filer/core/model/InboundFilerEvent.java | 25 ++
.../filer/core/model/PropertyInheritance.java | 43 ++++
.../model/PropertyInheritancePayload.java | 37 +++
.../filer/core/model/RepositoryNode.java | 136 +++++++++++
.../core/model/RepositoryNodeDifference.java | 92 +++++++
.../filer/core/model/UpdateFilerEvent.java | 28 +++
.../core/model/impl/AbstractFilerAction.java | 100 ++++++++
.../core/model/impl/AbstractFilerEvent.java | 52 ++++
.../model/impl/RepositoryNodeBuilder.java | 90 +++++++
.../filer/core/policy/FileableAspect.java | 95 ++++++++
.../filer/core/policy/FilerSegmentAspect.java | 53 ++++
.../core/policy/FilerSubscriberAspect.java | 72 ++++++
.../filer/core/scope/FilerScopeLoader.java | 10 +
.../scope/impl/AbstractFilerScopeLoader.java | 34 +++
.../scope/impl/AspectsFilerScopeLoader.java | 22 ++
.../scope/impl/DefaultFilerScopeLoader.java | 26 ++
.../scope/impl/EmptyFilerScopeLoader.java | 17 ++
.../impl/PropertiesFilerScopeLoader.java | 22 ++
.../core/scope/impl/SiteFilerScopeLoader.java | 25 ++
.../core/service/FilerFolderService.java | 18 ++
.../filer/core/service/FilerModelService.java | 23 ++
.../core/service/FilerOperationService.java | 30 +++
.../filer/core/service/FilerRegistry.java | 30 +++
.../filer/core/service/FilerService.java | 27 +++
.../core/service/FilerUpdateService.java | 16 ++
.../service/PropertyInheritanceService.java | 39 +++
.../filer/core/service/impl/FilerBuilder.java | 41 ++++
.../core/service/impl/FilerFolderBuilder.java | 81 +++++++
.../service/impl/FilerFolderServiceImpl.java | 128 ++++++++++
.../service/impl/FilerFolderTypeBuilder.java | 131 ++++++++++
.../service/impl/FilerModelServiceImpl.java | 93 +++++++
.../core/service/impl/FilerNameBuilder.java | 96 ++++++++
.../impl/FilerOperationServiceImpl.java | 176 ++++++++++++++
.../core/service/impl/FilerRegistryImpl.java | 39 +++
.../core/service/impl/FilerServiceImpl.java | 226 ++++++++++++++++++
.../service/impl/FilerUpdateServiceImpl.java | 118 +++++++++
.../impl/PropertyInheritanceServiceImpl.java | 214 +++++++++++++++++
.../filer/core/util/FilerNodeUtils.java | 61 +++++
.../core/util/FilerTransactionUtils.java | 58 +++++
50 files changed, 2904 insertions(+), 1 deletion(-)
create mode 100644 alfresco-filer-core/src/main/config/context/model-context.xml
create mode 100644 alfresco-filer-core/src/main/config/context/policy-context.xml
create mode 100644 alfresco-filer-core/src/main/config/context/scope-context.xml
create mode 100644 alfresco-filer-core/src/main/config/context/service-context.xml
create mode 100644 alfresco-filer-core/src/main/config/model/filerModel.xml
create mode 100644 alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/FilerAction.java
create mode 100644 alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/FilerEvent.java
create mode 100644 alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/FilerException.java
create mode 100644 alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/FilerFolderContext.java
create mode 100644 alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/InboundFilerEvent.java
create mode 100644 alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/PropertyInheritance.java
create mode 100644 alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/PropertyInheritancePayload.java
create mode 100644 alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/RepositoryNode.java
create mode 100644 alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/RepositoryNodeDifference.java
create mode 100644 alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/UpdateFilerEvent.java
create mode 100644 alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/impl/AbstractFilerAction.java
create mode 100644 alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/impl/AbstractFilerEvent.java
create mode 100644 alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/impl/RepositoryNodeBuilder.java
create mode 100644 alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/policy/FileableAspect.java
create mode 100644 alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/policy/FilerSegmentAspect.java
create mode 100644 alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/policy/FilerSubscriberAspect.java
create mode 100644 alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/scope/FilerScopeLoader.java
create mode 100644 alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/scope/impl/AbstractFilerScopeLoader.java
create mode 100644 alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/scope/impl/AspectsFilerScopeLoader.java
create mode 100644 alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/scope/impl/DefaultFilerScopeLoader.java
create mode 100644 alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/scope/impl/EmptyFilerScopeLoader.java
create mode 100644 alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/scope/impl/PropertiesFilerScopeLoader.java
create mode 100644 alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/scope/impl/SiteFilerScopeLoader.java
create mode 100644 alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/FilerFolderService.java
create mode 100644 alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/FilerModelService.java
create mode 100644 alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/FilerOperationService.java
create mode 100644 alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/FilerRegistry.java
create mode 100644 alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/FilerService.java
create mode 100644 alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/FilerUpdateService.java
create mode 100644 alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/PropertyInheritanceService.java
create mode 100644 alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerBuilder.java
create mode 100644 alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerFolderBuilder.java
create mode 100644 alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerFolderServiceImpl.java
create mode 100644 alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerFolderTypeBuilder.java
create mode 100644 alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerModelServiceImpl.java
create mode 100644 alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerNameBuilder.java
create mode 100644 alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerOperationServiceImpl.java
create mode 100644 alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerRegistryImpl.java
create mode 100644 alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerServiceImpl.java
create mode 100644 alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerUpdateServiceImpl.java
create mode 100644 alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/PropertyInheritanceServiceImpl.java
create mode 100644 alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/util/FilerNodeUtils.java
create mode 100644 alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/util/FilerTransactionUtils.java
diff --git a/alfresco-filer-core/src/main/config/alfresco-global.properties b/alfresco-filer-core/src/main/config/alfresco-global.properties
index 8b13789..7abc7da 100644
--- a/alfresco-filer-core/src/main/config/alfresco-global.properties
+++ b/alfresco-filer-core/src/main/config/alfresco-global.properties
@@ -1 +1,6 @@
+filer.owner.username=admin
+filer.aspect.fileable={http://www.atolcd.com/model/filer/1.0}fileable
+filer.aspect.segment={http://www.atolcd.com/model/filer/1.0}segment
+filer.aspect.subscriber={http://www.atolcd.com/model/filer/1.0}subscriber
+filer.aspect.propertyInheritance={http://www.atolcd.com/model/filer/1.0}propertyInheritance
diff --git a/alfresco-filer-core/src/main/config/context/model-context.xml b/alfresco-filer-core/src/main/config/context/model-context.xml
new file mode 100644
index 0000000..2da10b3
--- /dev/null
+++ b/alfresco-filer-core/src/main/config/context/model-context.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+ alfresco/module/${alfresco.module.name}/model/filerModel.xml
+
+
+
+
+
diff --git a/alfresco-filer-core/src/main/config/context/policy-context.xml b/alfresco-filer-core/src/main/config/context/policy-context.xml
new file mode 100644
index 0000000..e967dd9
--- /dev/null
+++ b/alfresco-filer-core/src/main/config/context/policy-context.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/alfresco-filer-core/src/main/config/context/scope-context.xml b/alfresco-filer-core/src/main/config/context/scope-context.xml
new file mode 100644
index 0000000..9323415
--- /dev/null
+++ b/alfresco-filer-core/src/main/config/context/scope-context.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/alfresco-filer-core/src/main/config/context/service-context.xml b/alfresco-filer-core/src/main/config/context/service-context.xml
new file mode 100644
index 0000000..887e6ce
--- /dev/null
+++ b/alfresco-filer-core/src/main/config/context/service-context.xml
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/alfresco-filer-core/src/main/config/model/filerModel.xml b/alfresco-filer-core/src/main/config/model/filerModel.xml
new file mode 100644
index 0000000..bea37ea
--- /dev/null
+++ b/alfresco-filer-core/src/main/config/model/filerModel.xml
@@ -0,0 +1,37 @@
+
+
+
+ Filer behaviour indicators
+ Atol CD
+ 1.0
+
+
+
+
+
+
+
+
+ Automatically filed
+
+
+
+ Automatically fill incoming elements
+
+
+
+ Segment Folder
+ filer:subscriber
+
+ false
+
+
+
+ Inherits properties
+
+
+
+
+
diff --git a/alfresco-filer-core/src/main/config/module-context.xml b/alfresco-filer-core/src/main/config/module-context.xml
index 35c0571..89504e3 100644
--- a/alfresco-filer-core/src/main/config/module-context.xml
+++ b/alfresco-filer-core/src/main/config/module-context.xml
@@ -3,5 +3,5 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
-
+
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/FilerAction.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/FilerAction.java
new file mode 100644
index 0000000..fec2da5
--- /dev/null
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/FilerAction.java
@@ -0,0 +1,14 @@
+package com.atolcd.alfresco.filer.core.model;
+
+import org.springframework.core.Ordered;
+
+public interface FilerAction extends Ordered, Comparable {
+
+ String getName();
+
+ boolean supportsActionResolution(FilerEvent event);
+
+ boolean supportsActionExecution(RepositoryNode node);
+
+ void execute(RepositoryNode node);
+}
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/FilerEvent.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/FilerEvent.java
new file mode 100644
index 0000000..d04881e
--- /dev/null
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/FilerEvent.java
@@ -0,0 +1,18 @@
+package com.atolcd.alfresco.filer.core.model;
+
+import java.util.Optional;
+
+public interface FilerEvent {
+
+ boolean isExecuted();
+
+ void setExecuted();
+
+ RepositoryNode getNode();
+
+ Optional getAction();
+
+ void setAction(FilerAction action);
+
+ void comesAfter(FilerEvent previous);
+}
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/FilerException.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/FilerException.java
new file mode 100644
index 0000000..9070bea
--- /dev/null
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/FilerException.java
@@ -0,0 +1,10 @@
+package com.atolcd.alfresco.filer.core.model;
+
+public class FilerException extends RuntimeException {
+
+ private static final long serialVersionUID = 1484082601926986444L;
+
+ public FilerException(final String message) {
+ super(message);
+ }
+}
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/FilerFolderContext.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/FilerFolderContext.java
new file mode 100644
index 0000000..8860804
--- /dev/null
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/FilerFolderContext.java
@@ -0,0 +1,63 @@
+package com.atolcd.alfresco.filer.core.model;
+
+import java.util.Optional;
+
+import org.alfresco.service.cmr.repository.NodeRef;
+
+public final class FilerFolderContext {
+
+ private final RepositoryNode node;
+ private final NodeRef parent;
+
+ private PropertyInheritance propertyInheritance;
+ private boolean enabled;
+
+ private FilerFolderContext(final RepositoryNode node, final NodeRef parent,
+ final PropertyInheritance propertyInheritance, final boolean enabled) {
+ this.node = node;
+ this.parent = parent;
+ this.propertyInheritance = Optional.ofNullable(propertyInheritance).map(PropertyInheritance::new).orElse(null);
+ this.enabled = enabled;
+ }
+
+ public FilerFolderContext(final RepositoryNode node) {
+ this(node, null, null, true);
+ }
+
+ public FilerFolderContext(final FilerFolderContext other) {
+ this(other, other.parent);
+ }
+
+ public FilerFolderContext(final FilerFolderContext other, final NodeRef parent) {
+ this(other.node, parent, other.propertyInheritance, other.enabled);
+ }
+
+ public RepositoryNode getNode() {
+ return node;
+ }
+
+ public NodeRef getParent() {
+ return parent;
+ }
+
+ public PropertyInheritance getPropertyInheritance() {
+ propertyInheritance = Optional.ofNullable(propertyInheritance).orElseGet(PropertyInheritance::new);
+ return propertyInheritance;
+ }
+
+ public boolean hasPropertyInheritance() {
+ return propertyInheritance != null;
+ }
+
+ public void clearPropertyInheritance() {
+ propertyInheritance = null; // NOPMD - reset value
+ }
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public void enable(final boolean enable) {
+ this.enabled = enable;
+ }
+}
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/InboundFilerEvent.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/InboundFilerEvent.java
new file mode 100644
index 0000000..3b43cac
--- /dev/null
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/InboundFilerEvent.java
@@ -0,0 +1,25 @@
+package com.atolcd.alfresco.filer.core.model;
+
+import java.text.MessageFormat;
+
+import org.alfresco.service.cmr.repository.NodeRef;
+
+import com.atolcd.alfresco.filer.core.model.impl.AbstractFilerEvent;
+import com.atolcd.alfresco.filer.core.util.FilerNodeUtils;
+
+public class InboundFilerEvent extends AbstractFilerEvent {
+
+ public InboundFilerEvent(final NodeRef nodeRef, final boolean original) {
+ super(nodeRef);
+ FilerNodeUtils.setOriginal(getNode(), original);
+ }
+
+ @Override
+ public String toString() {
+ return MessageFormat.format("Inbound{action={0}, name={1}, node={2}, store={3}}",
+ getAction().orElse(null),
+ getNode().getName().orElse(null),
+ getNode().getNodeRef().getId(),
+ getNode().getNodeRef().getStoreRef());
+ }
+}
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/PropertyInheritance.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/PropertyInheritance.java
new file mode 100644
index 0000000..115d5e2
--- /dev/null
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/PropertyInheritance.java
@@ -0,0 +1,43 @@
+package com.atolcd.alfresco.filer.core.model;
+
+import java.text.MessageFormat;
+import java.util.HashSet;
+import java.util.Optional;
+import java.util.Set;
+
+import org.alfresco.service.namespace.QName;
+
+public class PropertyInheritance {
+
+ private Set mandatoryAspects; // Always apply
+ private Set optionalAspects; // Ignore if absent in the payload but always apply if present
+
+ public PropertyInheritance() {
+ // Use by default, so that in can be used instead of a null value
+ }
+
+ private PropertyInheritance(final Set mandatoryAspects, final Set optionalAspects) {
+ this();
+ this.mandatoryAspects = Optional.ofNullable(mandatoryAspects).map(HashSet::new).orElse(null);
+ this.optionalAspects = Optional.ofNullable(optionalAspects).map(HashSet::new).orElse(null);
+ }
+
+ public PropertyInheritance(final PropertyInheritance inheritance) {
+ this(inheritance.mandatoryAspects, inheritance.optionalAspects);
+ }
+
+ @Override
+ public String toString() {
+ return MessageFormat.format("{mandatory={0}, optional={1}}", mandatoryAspects, optionalAspects);
+ }
+
+ public Set getMandatoryAspects() {
+ mandatoryAspects = Optional.ofNullable(mandatoryAspects).orElseGet(HashSet::new);
+ return mandatoryAspects;
+ }
+
+ public Set getOptionalAspects() {
+ optionalAspects = Optional.ofNullable(optionalAspects).orElseGet(HashSet::new);
+ return optionalAspects;
+ }
+}
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/PropertyInheritancePayload.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/PropertyInheritancePayload.java
new file mode 100644
index 0000000..06f4a83
--- /dev/null
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/PropertyInheritancePayload.java
@@ -0,0 +1,37 @@
+package com.atolcd.alfresco.filer.core.model;
+
+import java.io.Serializable;
+import java.text.MessageFormat;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+import org.alfresco.service.namespace.QName;
+
+public class PropertyInheritancePayload {
+
+ private final Map> added;
+ private final Map> removed;
+
+ public PropertyInheritancePayload(final Map> added, final Map> removed) {
+ this.added = Objects.requireNonNull(added);
+ this.removed = Objects.requireNonNull(removed);
+ }
+
+ @Override
+ public String toString() {
+ return MessageFormat.format("{added={0}, removed={1}}", added, removed);
+ }
+
+ public boolean isEmpty() {
+ return added.isEmpty() && removed.isEmpty();
+ }
+
+ public Map> getAdded() {
+ return added;
+ }
+
+ public Map> getRemoved() {
+ return removed;
+ }
+}
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/RepositoryNode.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/RepositoryNode.java
new file mode 100644
index 0000000..86b78e5
--- /dev/null
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/RepositoryNode.java
@@ -0,0 +1,136 @@
+package com.atolcd.alfresco.filer.core.model;
+
+import java.io.Serializable;
+import java.text.MessageFormat;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+
+import org.alfresco.model.ContentModel;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.namespace.QName;
+
+import com.atolcd.alfresco.filer.core.model.impl.RepositoryNodeBuilder;
+
+public class RepositoryNode implements Serializable {
+
+ private static final long serialVersionUID = 6758895936238032221L;
+
+ private NodeRef nodeRef;
+
+ private NodeRef parent;
+ private QName type;
+ private Set aspects;
+ private Map properties;
+
+ private Map extensions;
+
+ public RepositoryNode() {
+ // In case nodeRef is unknown
+ }
+
+ public RepositoryNode(final NodeRef nodeRef) {
+ this();
+ this.nodeRef = nodeRef;
+ }
+
+ public RepositoryNode(final NodeRef nodeRef, final NodeRef parent, final QName type, final Set aspects,
+ final Map properties, final Map extensions) {
+ this(nodeRef);
+ this.parent = parent;
+ this.type = type;
+ this.aspects = Optional.ofNullable(aspects).map(LinkedHashSet::new).orElse(null);
+ this.properties = Optional.ofNullable(properties).map(LinkedHashMap::new).orElse(null);
+ this.extensions = Optional.ofNullable(extensions).map(LinkedHashMap::new).orElse(null);
+ }
+
+ public RepositoryNode(final RepositoryNode other) {
+ this(other.nodeRef, other.parent, other.type, other.aspects, other.properties, other.extensions);
+ }
+
+ public static RepositoryNodeBuilder builder() {
+ return new RepositoryNodeBuilder();
+ }
+
+ public NodeRef getNodeRef() {
+ return nodeRef;
+ }
+
+ public void setNodeRef(final NodeRef nodeRef) {
+ this.nodeRef = nodeRef;
+ }
+
+ public NodeRef getParent() {
+ return parent;
+ }
+
+ public void setParent(final NodeRef parent) {
+ this.parent = parent;
+ }
+
+ public QName getType() {
+ return type;
+ }
+
+ public void setType(final QName type) {
+ this.type = type;
+ }
+
+ public Set getAspects() {
+ aspects = Optional.ofNullable(aspects).orElseGet(LinkedHashSet::new);
+ return aspects;
+ }
+
+ public Map getProperties() {
+ properties = Optional.ofNullable(properties).orElseGet(LinkedHashMap::new);
+ return properties;
+ }
+
+ @SuppressWarnings("unchecked")
+ public T getProperty(final QName name, final Class clazz) {
+ return (T) getProperties().get(name);
+ }
+
+ public Optional getName() {
+ return Optional.ofNullable(properties).map(p -> p.get(ContentModel.PROP_NAME)).filter(Objects::nonNull).map(String::valueOf);
+ }
+
+ public Map getExtensions() {
+ extensions = Optional.ofNullable(extensions).orElseGet(LinkedHashMap::new);
+ return extensions;
+ }
+
+ @SuppressWarnings("unchecked")
+ public T getExtension(final String name, final Class clazz) {
+ return (T) getExtensions().get(name);
+ }
+
+ @Override
+ public boolean equals(final Object object) {
+ if (object == null) {
+ return false;
+ }
+ if (object == this) {
+ return true;
+ }
+ if (object instanceof RepositoryNode) {
+ RepositoryNode other = (RepositoryNode) object;
+ return Objects.equals(nodeRef, other.getNodeRef());
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(nodeRef);
+ }
+
+ @Override
+ public String toString() {
+ return MessageFormat.format("{0}{type={1}, properties={2}, aspects={3}, parent={4}, nodeRef={5}}",
+ getName().orElse(null), type, properties, aspects, parent, nodeRef);
+ }
+}
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/RepositoryNodeDifference.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/RepositoryNodeDifference.java
new file mode 100644
index 0000000..86c5712
--- /dev/null
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/RepositoryNodeDifference.java
@@ -0,0 +1,92 @@
+package com.atolcd.alfresco.filer.core.model;
+
+import java.io.Serializable;
+import java.text.MessageFormat;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Optional;
+import java.util.Set;
+
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.namespace.QName;
+
+public class RepositoryNodeDifference {
+
+ private QName typeToSet;
+ private final Set aspectsToRemove;
+ private final Set aspectsToAdd;
+ private final Set propertiesToRemove;
+ private final Map propertiesToAdd;
+ private NodeRef parentToMove;
+
+ public RepositoryNodeDifference(final RepositoryNode source, final RepositoryNode target) {
+ // Type
+ if (source.getType() != null && !source.getType().equals(target.getType())) {
+ typeToSet = target.getType();
+ }
+ // Compute aspects
+ aspectsToRemove = new HashSet<>(source.getAspects());
+ aspectsToRemove.removeAll(target.getAspects());
+ aspectsToAdd = new HashSet<>(target.getAspects());
+ aspectsToAdd.removeAll(source.getAspects());
+ // Compute properties
+ propertiesToRemove = new HashSet<>(source.getProperties().keySet());
+ propertiesToRemove.removeAll(target.getProperties().keySet());
+ propertiesToAdd = new HashMap<>(target.getProperties());
+ removeDuplicateProperties(source.getProperties(), propertiesToAdd);
+ // Parent
+ if (source.getParent() != null && !source.getParent().equals(target.getParent())) {
+ parentToMove = target.getParent();
+ }
+ }
+
+ private static void removeDuplicateProperties(final Map source, final Map target) {
+ for (Entry property : source.entrySet()) {
+ Serializable sourceValue = property.getValue();
+ Serializable targetValue = target.get(property.getKey());
+ if (targetValue == null && sourceValue == null || targetValue != null && targetValue.equals(sourceValue)) {
+ target.remove(property.getKey());
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ return MessageFormat.format("{0} type, adding {1} {2}, removing {3} {4}, {5} parent",
+ Optional.ofNullable(typeToSet).map(String::valueOf).orElse("same"),
+ propertiesToAdd, aspectsToAdd,
+ propertiesToRemove, aspectsToRemove,
+ Optional.ofNullable(parentToMove).map(String::valueOf).orElse("same"));
+ }
+
+ public boolean isEmpty() {
+ return typeToSet == null && aspectsToRemove.isEmpty() && aspectsToAdd.isEmpty() && propertiesToRemove.isEmpty()
+ && propertiesToAdd.isEmpty() && parentToMove == null;
+ }
+
+ public Optional getTypeToSet() {
+ return Optional.ofNullable(typeToSet);
+ }
+
+ public Set getAspectsToAdd() {
+ return aspectsToAdd;
+ }
+
+ public Set getAspectsToRemove() {
+ return aspectsToRemove;
+ }
+
+ public Set getPropertiesToRemove() {
+ return propertiesToRemove;
+ }
+
+ public Map getPropertiesToAdd() {
+ return propertiesToAdd;
+ }
+
+ public Optional getParentToMove() {
+ return Optional.ofNullable(parentToMove);
+ }
+}
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/UpdateFilerEvent.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/UpdateFilerEvent.java
new file mode 100644
index 0000000..f1e83b2
--- /dev/null
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/UpdateFilerEvent.java
@@ -0,0 +1,28 @@
+package com.atolcd.alfresco.filer.core.model;
+
+import java.io.Serializable;
+import java.text.MessageFormat;
+import java.util.Map;
+
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.namespace.QName;
+
+import com.atolcd.alfresco.filer.core.model.impl.AbstractFilerEvent;
+
+public class UpdateFilerEvent extends AbstractFilerEvent {
+
+ public UpdateFilerEvent(final NodeRef nodeRef, final Map after) {
+ super(nodeRef);
+ // Set node properties values
+ getNode().getProperties().putAll(after);
+ }
+
+ @Override
+ public String toString() {
+ return MessageFormat.format("Update{action={0}, name={1}, node={2}, store={3}}",
+ getAction().orElse(null),
+ getNode().getName().orElse(null),
+ getNode().getNodeRef().getId(),
+ getNode().getNodeRef().getStoreRef());
+ }
+}
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/impl/AbstractFilerAction.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/impl/AbstractFilerAction.java
new file mode 100644
index 0000000..f61e9d7
--- /dev/null
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/impl/AbstractFilerAction.java
@@ -0,0 +1,100 @@
+package com.atolcd.alfresco.filer.core.model.impl;
+
+import java.util.Comparator;
+import java.util.Objects;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.springframework.beans.factory.BeanNameAware;
+import org.springframework.beans.factory.InitializingBean;
+
+import com.atolcd.alfresco.filer.core.model.FilerAction;
+import com.atolcd.alfresco.filer.core.model.FilerException;
+import com.atolcd.alfresco.filer.core.model.RepositoryNode;
+import com.atolcd.alfresco.filer.core.service.FilerRegistry;
+import com.atolcd.alfresco.filer.core.service.FilerService;
+import com.atolcd.alfresco.filer.core.service.impl.FilerBuilder;
+
+public abstract class AbstractFilerAction implements FilerAction, InitializingBean, BeanNameAware {
+
+ private static final Comparator NATURAL_ORDER_COMPARATOR =
+ Comparator.comparing(FilerAction::getOrder).thenComparing(FilerAction::getName);
+
+ private FilerRegistry filerRegistry;
+ private FilerService filerService;
+ private String name;
+
+ @Override
+ public void afterPropertiesSet() {
+ filerRegistry.registerAction(this);
+ }
+
+ @Override
+ public final void execute(final RepositoryNode node) {
+ FilerBuilder builder = new FilerBuilder(filerService, node);
+ execute(builder);
+ }
+
+ protected abstract void execute(FilerBuilder builder);
+
+ protected void deny(final FilerBuilder builder, final Predicate check) {
+ if (check.test(builder.getNode())) {
+ throw new FilerException("Filer action DENIED: " + name);
+ }
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public int getOrder() { // NOPMD - 0 is the default value
+ return 0;
+ }
+
+ @Override
+ public boolean equals(final Object object) {
+ if (object == null) {
+ return false;
+ }
+ if (object == this) {
+ return true;
+ }
+ if (object instanceof AbstractFilerAction) {
+ AbstractFilerAction other = (AbstractFilerAction) object;
+ return Objects.equals(getOrder(), other.getOrder())
+ && Objects.equals(getName(), other.getName());
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(getOrder(), name);
+ }
+
+ @Override
+ public int compareTo(final FilerAction other) {
+ return NATURAL_ORDER_COMPARATOR.compare(this, other);
+ }
+
+ @Override
+ public String toString() {
+ return Stream.of(name, getOrder()).map(String::valueOf).collect(Collectors.joining(", "));
+ }
+
+ public void setFilerRegistry(final FilerRegistry filerRegistry) {
+ this.filerRegistry = filerRegistry;
+ }
+
+ public void setFilerService(final FilerService filerService) {
+ this.filerService = filerService;
+ }
+
+ @Override
+ public void setBeanName(final String beanName) {
+ this.name = beanName;
+ }
+}
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/impl/AbstractFilerEvent.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/impl/AbstractFilerEvent.java
new file mode 100644
index 0000000..08c2ccb
--- /dev/null
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/impl/AbstractFilerEvent.java
@@ -0,0 +1,52 @@
+package com.atolcd.alfresco.filer.core.model.impl;
+
+import java.util.Optional;
+
+import org.alfresco.service.cmr.repository.NodeRef;
+
+import com.atolcd.alfresco.filer.core.model.FilerAction;
+import com.atolcd.alfresco.filer.core.model.FilerEvent;
+import com.atolcd.alfresco.filer.core.model.RepositoryNode;
+
+public abstract class AbstractFilerEvent implements FilerEvent {
+
+ private RepositoryNode node;
+ private FilerAction action;
+ private boolean executed;
+
+ public AbstractFilerEvent(final NodeRef nodeRef) {
+ this.node = new RepositoryNode(nodeRef);
+ }
+
+ @Override
+ public boolean isExecuted() {
+ return executed;
+ }
+
+ @Override
+ public void setExecuted() {
+ executed = true;
+ }
+
+ @Override
+ public RepositoryNode getNode() {
+ return node;
+ }
+
+ @Override
+ public void comesAfter(final FilerEvent previous) {
+ node = previous.getNode();
+ action = previous.getAction().orElse(null);
+ executed = previous.isExecuted();
+ }
+
+ @Override
+ public Optional getAction() {
+ return Optional.ofNullable(action);
+ }
+
+ @Override
+ public void setAction(final FilerAction action) {
+ this.action = action;
+ }
+}
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/impl/RepositoryNodeBuilder.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/impl/RepositoryNodeBuilder.java
new file mode 100644
index 0000000..4db9e36
--- /dev/null
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/impl/RepositoryNodeBuilder.java
@@ -0,0 +1,90 @@
+package com.atolcd.alfresco.filer.core.model.impl;
+
+import java.io.Serializable;
+import java.time.ZonedDateTime;
+import java.util.Date;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.function.BiConsumer;
+
+import org.alfresco.model.ContentModel;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.namespace.QName;
+
+import com.atolcd.alfresco.filer.core.model.RepositoryNode;
+
+public class RepositoryNodeBuilder {
+
+ private final RepositoryNode node = new RepositoryNode();
+
+ public RepositoryNodeBuilder nodeRef(final NodeRef nodeRef) {
+ node.setNodeRef(nodeRef);
+ return this;
+ }
+
+ public RepositoryNodeBuilder parent(final NodeRef parent) {
+ node.setParent(parent);
+ return this;
+ }
+
+ public RepositoryNodeBuilder type(final QName type) {
+ node.setType(type);
+ return this;
+ }
+
+ public RepositoryNodeBuilder named(final String name) {
+ return property(ContentModel.PROP_NAME, name);
+ }
+
+ public RepositoryNodeBuilder named(final UUID name) {
+ return named(name.toString());
+ }
+
+ public RepositoryNodeBuilder aspect(final QName value) {
+ node.getAspects().add(value);
+ return this;
+ }
+
+ public RepositoryNodeBuilder aspects(final Set aspects) {
+ node.getAspects().addAll(aspects);
+ return this;
+ }
+
+ public RepositoryNodeBuilder property(final QName name, final Serializable value) {
+ node.getProperties().put(name, value);
+ return this;
+ }
+
+ public RepositoryNodeBuilder property(final QName name, final UUID value) {
+ return property(name, value.toString());
+ }
+
+ public RepositoryNodeBuilder property(final QName name, final ZonedDateTime value) {
+ return property(name, Date.from(value.toInstant()));
+ }
+
+ public RepositoryNodeBuilder properties(final Map properties) {
+ node.getProperties().putAll(properties);
+ return this;
+ }
+
+ public RepositoryNodeBuilder extension(final String extensionId, final Object value) {
+ node.getExtensions().put(extensionId, value);
+ return this;
+ }
+
+ public RepositoryNodeBuilder extensions(final Map extensions) {
+ node.getExtensions().putAll(extensions);
+ return this;
+ }
+
+ public RepositoryNodeBuilder with(final BiConsumer consumer, final T value) {
+ consumer.accept(node, value);
+ return this;
+ }
+
+ public RepositoryNode build() {
+ return node;
+ }
+}
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/policy/FileableAspect.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/policy/FileableAspect.java
new file mode 100644
index 0000000..d2a4adf
--- /dev/null
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/policy/FileableAspect.java
@@ -0,0 +1,95 @@
+package com.atolcd.alfresco.filer.core.policy;
+
+import java.io.Serializable;
+import java.util.Map;
+import java.util.Objects;
+
+import org.alfresco.repo.node.NodeServicePolicies;
+import org.alfresco.repo.policy.Behaviour.NotificationFrequency;
+import org.alfresco.repo.policy.JavaBehaviour;
+import org.alfresco.repo.policy.PolicyComponent;
+import org.alfresco.service.cmr.repository.ChildAssociationRef;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.namespace.QName;
+import org.springframework.beans.factory.InitializingBean;
+
+import com.atolcd.alfresco.filer.core.model.InboundFilerEvent;
+import com.atolcd.alfresco.filer.core.model.UpdateFilerEvent;
+import com.atolcd.alfresco.filer.core.service.FilerModelService;
+import com.atolcd.alfresco.filer.core.service.FilerService;
+
+public class FileableAspect implements InitializingBean, NodeServicePolicies.OnAddAspectPolicy,
+ NodeServicePolicies.BeforeUpdateNodePolicy, NodeServicePolicies.OnUpdatePropertiesPolicy,
+ NodeServicePolicies.OnDeleteNodePolicy, NodeServicePolicies.OnMoveNodePolicy {
+
+ private FilerService filerService;
+ private FilerModelService filerModelService;
+ private PolicyComponent policyComponent;
+
+ @Override
+ public void afterPropertiesSet() {
+ Objects.requireNonNull(filerService);
+ Objects.requireNonNull(filerModelService);
+ Objects.requireNonNull(policyComponent);
+ QName fileableAspect = filerModelService.getFileableAspect();
+ // Using TRANSACTION_COMMIT ensures all properties are added/updated before applying filer
+ policyComponent.bindClassBehaviour(NodeServicePolicies.OnAddAspectPolicy.QNAME,
+ fileableAspect, new JavaBehaviour(this, "onAddAspect", NotificationFrequency.TRANSACTION_COMMIT));
+ policyComponent.bindClassBehaviour(NodeServicePolicies.BeforeUpdateNodePolicy.QNAME,
+ fileableAspect, new JavaBehaviour(this, "beforeUpdateNode", NotificationFrequency.FIRST_EVENT));
+ policyComponent.bindClassBehaviour(NodeServicePolicies.OnUpdatePropertiesPolicy.QNAME,
+ fileableAspect, new JavaBehaviour(this, "onUpdateProperties", NotificationFrequency.TRANSACTION_COMMIT));
+ policyComponent.bindClassBehaviour(NodeServicePolicies.OnMoveNodePolicy.QNAME,
+ fileableAspect, new JavaBehaviour(this, "onMoveNode", NotificationFrequency.TRANSACTION_COMMIT));
+ policyComponent.bindClassBehaviour(NodeServicePolicies.OnDeleteNodePolicy.QNAME,
+ fileableAspect, new JavaBehaviour(this, "onDeleteNode"));
+ }
+
+ @Override
+ public void onAddAspect(final NodeRef nodeRef, final QName aspectTypeQName) {
+ filerService.executeAction(new InboundFilerEvent(nodeRef, false));
+ }
+
+ @Override
+ public void beforeUpdateNode(final NodeRef nodeRef) {
+ filerService.initFileable(nodeRef);
+ }
+
+ @Override
+ public void onUpdateProperties(final NodeRef nodeRef,
+ final Map before, final Map after) {
+ // Avoid getting triggered on initial node creation
+ if (!before.isEmpty()) {
+ UpdateFilerEvent event = new UpdateFilerEvent(nodeRef, after);
+ filerService.executeAction(event);
+ }
+ }
+
+ @Override
+ public void onMoveNode(final ChildAssociationRef oldChildAssocRef, final ChildAssociationRef newChildAssocRef) {
+ // In a try-catch just in case, so that old parent segment can be deleted
+ try {
+ filerService.executeAction(new InboundFilerEvent(newChildAssocRef.getChildRef(), false));
+ } finally {
+ filerService.operations().deleteSegment(oldChildAssocRef.getParentRef());
+ }
+ }
+
+ @Override
+ public void onDeleteNode(final ChildAssociationRef childAssocRef, final boolean isNodeArchived) {
+ NodeRef parent = childAssocRef.getParentRef();
+ filerService.operations().deleteSegment(parent);
+ }
+
+ public void setFilerService(final FilerService filerService) {
+ this.filerService = filerService;
+ }
+
+ public void setFilerModelService(final FilerModelService filerModelService) {
+ this.filerModelService = filerModelService;
+ }
+
+ public void setPolicyComponent(final PolicyComponent policyComponent) {
+ this.policyComponent = policyComponent;
+ }
+}
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/policy/FilerSegmentAspect.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/policy/FilerSegmentAspect.java
new file mode 100644
index 0000000..0ba1a98
--- /dev/null
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/policy/FilerSegmentAspect.java
@@ -0,0 +1,53 @@
+package com.atolcd.alfresco.filer.core.policy;
+
+import java.util.Objects;
+
+import org.alfresco.repo.node.NodeServicePolicies;
+import org.alfresco.repo.policy.JavaBehaviour;
+import org.alfresco.repo.policy.PolicyComponent;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.security.OwnableService;
+import org.alfresco.service.namespace.QName;
+import org.springframework.beans.factory.InitializingBean;
+
+import com.atolcd.alfresco.filer.core.service.FilerModelService;
+
+public class FilerSegmentAspect implements InitializingBean, NodeServicePolicies.OnAddAspectPolicy {
+
+ private FilerModelService filerModelService;
+ private PolicyComponent policyComponent;
+ private OwnableService ownableService;
+
+ private String username;
+
+ @Override
+ public void afterPropertiesSet() {
+ Objects.requireNonNull(filerModelService);
+ Objects.requireNonNull(policyComponent);
+ Objects.requireNonNull(ownableService);
+ Objects.requireNonNull(username);
+ policyComponent.bindClassBehaviour(NodeServicePolicies.OnAddAspectPolicy.QNAME,
+ filerModelService.getSegmentAspect(), new JavaBehaviour(this, "onAddAspect"));
+ }
+
+ @Override
+ public void onAddAspect(final NodeRef nodeRef, final QName aspectTypeQName) {
+ ownableService.setOwner(nodeRef, username);
+ }
+
+ public void setFilerModelService(final FilerModelService filerModelService) {
+ this.filerModelService = filerModelService;
+ }
+
+ public void setPolicyComponent(final PolicyComponent policyComponent) {
+ this.policyComponent = policyComponent;
+ }
+
+ public void setOwnableService(final OwnableService ownableService) {
+ this.ownableService = ownableService;
+ }
+
+ public void setUsername(final String username) {
+ this.username = username;
+ }
+}
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/policy/FilerSubscriberAspect.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/policy/FilerSubscriberAspect.java
new file mode 100644
index 0000000..172bf4d
--- /dev/null
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/policy/FilerSubscriberAspect.java
@@ -0,0 +1,72 @@
+package com.atolcd.alfresco.filer.core.policy;
+
+import java.util.Objects;
+import java.util.Optional;
+
+import org.alfresco.repo.node.NodeServicePolicies;
+import org.alfresco.repo.policy.JavaBehaviour;
+import org.alfresco.repo.policy.PolicyComponent;
+import org.alfresco.service.cmr.repository.ChildAssociationRef;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.namespace.QName;
+import org.springframework.beans.factory.InitializingBean;
+
+import com.atolcd.alfresco.filer.core.model.InboundFilerEvent;
+import com.atolcd.alfresco.filer.core.service.FilerModelService;
+import com.atolcd.alfresco.filer.core.service.FilerService;
+import com.atolcd.alfresco.filer.core.util.FilerTransactionUtils;
+
+public class FilerSubscriberAspect implements InitializingBean, NodeServicePolicies.BeforeDeleteChildAssociationPolicy,
+ NodeServicePolicies.OnCreateChildAssociationPolicy {
+
+ private FilerService filerService;
+ private FilerModelService filerModelService;
+ private PolicyComponent policyComponent;
+
+ @Override
+ public void afterPropertiesSet() {
+ Objects.requireNonNull(filerService);
+ Objects.requireNonNull(filerModelService);
+ Objects.requireNonNull(policyComponent);
+ QName subscriberAspect = filerModelService.getSubscriberAspect();
+ policyComponent.bindAssociationBehaviour(NodeServicePolicies.BeforeDeleteChildAssociationPolicy.QNAME,
+ subscriberAspect, new JavaBehaviour(this, "beforeDeleteChildAssociation"));
+ policyComponent.bindAssociationBehaviour(NodeServicePolicies.OnCreateChildAssociationPolicy.QNAME,
+ subscriberAspect, new JavaBehaviour(this, "onCreateChildAssociation"));
+ }
+
+ @Override
+ public void beforeDeleteChildAssociation(final ChildAssociationRef childAssocRef) {
+ if (childAssocRef.isPrimary()) {
+ // Store previous parent to determine later if it is a move on the same parent
+ FilerTransactionUtils.putDeletedAssoc(childAssocRef.getChildRef(), childAssocRef.getParentRef());
+ }
+ }
+
+ @Override
+ public void onCreateChildAssociation(final ChildAssociationRef childAssocRef, final boolean isNewNode) {
+ // Ignore node rename, it is not a move nor a creation, the node was there before
+ if (childAssocRef.isPrimary() && !isRename(childAssocRef)) {
+ // Renaming a Segment triggers this policy
+ filerService.resolveFileable(new InboundFilerEvent(childAssocRef.getChildRef(), isNewNode));
+ }
+ }
+
+ private boolean isRename(final ChildAssociationRef childAssocRef) {
+ Optional oldParent = FilerTransactionUtils.getDeletedAssoc(childAssocRef.getChildRef());
+ // It is in fact a rename if a previous deletion of the child happened on the same parent
+ return oldParent.isPresent() && oldParent.get().equals(childAssocRef.getParentRef());
+ }
+
+ public void setFilerService(final FilerService filerService) {
+ this.filerService = filerService;
+ }
+
+ public void setFilerModelService(final FilerModelService filerModelService) {
+ this.filerModelService = filerModelService;
+ }
+
+ public void setPolicyComponent(final PolicyComponent policyComponent) {
+ this.policyComponent = policyComponent;
+ }
+}
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/scope/FilerScopeLoader.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/scope/FilerScopeLoader.java
new file mode 100644
index 0000000..c84c300
--- /dev/null
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/scope/FilerScopeLoader.java
@@ -0,0 +1,10 @@
+package com.atolcd.alfresco.filer.core.scope;
+
+import com.atolcd.alfresco.filer.core.model.FilerEvent;
+
+public interface FilerScopeLoader {
+
+ void init(FilerEvent event);
+
+ void update(FilerEvent event);
+}
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/scope/impl/AbstractFilerScopeLoader.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/scope/impl/AbstractFilerScopeLoader.java
new file mode 100644
index 0000000..5783e04
--- /dev/null
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/scope/impl/AbstractFilerScopeLoader.java
@@ -0,0 +1,34 @@
+package com.atolcd.alfresco.filer.core.scope.impl;
+
+import java.util.Objects;
+
+import org.springframework.beans.factory.InitializingBean;
+
+import com.atolcd.alfresco.filer.core.model.FilerEvent;
+import com.atolcd.alfresco.filer.core.scope.FilerScopeLoader;
+import com.atolcd.alfresco.filer.core.service.FilerRegistry;
+
+public abstract class AbstractFilerScopeLoader implements FilerScopeLoader, InitializingBean {
+
+ private FilerRegistry filerRegistry;
+
+ @Override
+ public void afterPropertiesSet() {
+ Objects.requireNonNull(filerRegistry);
+ filerRegistry.registerScopeLoader(this);
+ }
+
+ @Override
+ public void init(final FilerEvent event) { // NOPMD - default empty method, in case init is not required
+ // no op
+ }
+
+ @Override
+ public void update(final FilerEvent event) { // NOPMD - default empty method, in case update is not required
+ // no op
+ }
+
+ public void setFilerRegistry(final FilerRegistry filerRegistry) {
+ this.filerRegistry = filerRegistry;
+ }
+}
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/scope/impl/AspectsFilerScopeLoader.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/scope/impl/AspectsFilerScopeLoader.java
new file mode 100644
index 0000000..9e7f0c3
--- /dev/null
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/scope/impl/AspectsFilerScopeLoader.java
@@ -0,0 +1,22 @@
+package com.atolcd.alfresco.filer.core.scope.impl;
+
+import org.alfresco.service.cmr.repository.NodeService;
+
+import com.atolcd.alfresco.filer.core.model.FilerEvent;
+import com.atolcd.alfresco.filer.core.model.RepositoryNode;
+
+public class AspectsFilerScopeLoader extends AbstractFilerScopeLoader {
+
+ private NodeService nodeService;
+
+ @Override
+ public void update(final FilerEvent event) {
+ RepositoryNode node = event.getNode();
+ // Put aspects
+ node.getAspects().addAll(nodeService.getAspects(node.getNodeRef()));
+ }
+
+ public void setNodeService(final NodeService nodeService) {
+ this.nodeService = nodeService;
+ }
+}
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/scope/impl/DefaultFilerScopeLoader.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/scope/impl/DefaultFilerScopeLoader.java
new file mode 100644
index 0000000..91a9927
--- /dev/null
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/scope/impl/DefaultFilerScopeLoader.java
@@ -0,0 +1,26 @@
+package com.atolcd.alfresco.filer.core.scope.impl;
+
+import org.alfresco.service.cmr.repository.NodeService;
+
+import com.atolcd.alfresco.filer.core.model.FilerEvent;
+import com.atolcd.alfresco.filer.core.model.RepositoryNode;
+
+public class DefaultFilerScopeLoader extends AbstractFilerScopeLoader {
+
+ private NodeService nodeService;
+
+ @Override
+ public void init(final FilerEvent event) {
+ RepositoryNode node = event.getNode();
+ // Init node type
+ node.setType(nodeService.getType(node.getNodeRef()));
+ // Init parent to get inherited aspects from it, but it could already be set on inbound event
+ if (node.getParent() == null) {
+ node.setParent(nodeService.getPrimaryParent(node.getNodeRef()).getParentRef());
+ }
+ }
+
+ public void setNodeService(final NodeService nodeService) {
+ this.nodeService = nodeService;
+ }
+}
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/scope/impl/EmptyFilerScopeLoader.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/scope/impl/EmptyFilerScopeLoader.java
new file mode 100644
index 0000000..7639826
--- /dev/null
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/scope/impl/EmptyFilerScopeLoader.java
@@ -0,0 +1,17 @@
+package com.atolcd.alfresco.filer.core.scope.impl;
+
+import com.atolcd.alfresco.filer.core.model.FilerEvent;
+import com.atolcd.alfresco.filer.core.scope.FilerScopeLoader;
+
+public class EmptyFilerScopeLoader implements FilerScopeLoader {
+
+ @Override
+ public void init(final FilerEvent event) {
+ // no op
+ }
+
+ @Override
+ public void update(final FilerEvent event) {
+ // no op
+ }
+}
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/scope/impl/PropertiesFilerScopeLoader.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/scope/impl/PropertiesFilerScopeLoader.java
new file mode 100644
index 0000000..b58f983
--- /dev/null
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/scope/impl/PropertiesFilerScopeLoader.java
@@ -0,0 +1,22 @@
+package com.atolcd.alfresco.filer.core.scope.impl;
+
+import org.alfresco.service.cmr.repository.NodeService;
+
+import com.atolcd.alfresco.filer.core.model.FilerEvent;
+import com.atolcd.alfresco.filer.core.model.RepositoryNode;
+
+public class PropertiesFilerScopeLoader extends AbstractFilerScopeLoader {
+
+ private NodeService nodeService;
+
+ @Override
+ public void update(final FilerEvent event) {
+ RepositoryNode node = event.getNode();
+ // Put properties
+ node.getProperties().putAll(nodeService.getProperties(node.getNodeRef()));
+ }
+
+ public void setNodeService(final NodeService nodeService) {
+ this.nodeService = nodeService;
+ }
+}
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/scope/impl/SiteFilerScopeLoader.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/scope/impl/SiteFilerScopeLoader.java
new file mode 100644
index 0000000..aebfa08
--- /dev/null
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/scope/impl/SiteFilerScopeLoader.java
@@ -0,0 +1,25 @@
+package com.atolcd.alfresco.filer.core.scope.impl;
+
+import org.alfresco.service.cmr.site.SiteInfo;
+import org.alfresco.service.cmr.site.SiteService;
+
+import com.atolcd.alfresco.filer.core.model.FilerEvent;
+import com.atolcd.alfresco.filer.core.model.RepositoryNode;
+import com.atolcd.alfresco.filer.core.util.FilerNodeUtils;
+
+public class SiteFilerScopeLoader extends AbstractFilerScopeLoader {
+
+ private SiteService siteService;
+
+ @Override
+ public void init(final FilerEvent event) {
+ RepositoryNode node = event.getNode();
+ // Init site information
+ SiteInfo siteInfo = siteService.getSite(node.getNodeRef());
+ FilerNodeUtils.setSiteInfo(node, siteInfo);
+ }
+
+ public void setSiteService(final SiteService siteService) {
+ this.siteService = siteService;
+ }
+}
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/FilerFolderService.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/FilerFolderService.java
new file mode 100644
index 0000000..67a829d
--- /dev/null
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/FilerFolderService.java
@@ -0,0 +1,18 @@
+package com.atolcd.alfresco.filer.core.service;
+
+import java.util.function.Consumer;
+
+import org.alfresco.service.cmr.repository.NodeRef;
+
+import com.atolcd.alfresco.filer.core.model.RepositoryNode;
+
+public interface FilerFolderService {
+
+ void fetchFolder(RepositoryNode node, Consumer onGet);
+
+ void fetchOrCreateFolder(RepositoryNode node, Consumer onGet, Consumer onCreate);
+
+ void updateFolder(RepositoryNode node, Consumer onGet, Consumer onCreate);
+
+ void lockFolder(NodeRef nodeRef);
+}
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/FilerModelService.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/FilerModelService.java
new file mode 100644
index 0000000..0ed3562
--- /dev/null
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/FilerModelService.java
@@ -0,0 +1,23 @@
+package com.atolcd.alfresco.filer.core.service;
+
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.namespace.QName;
+
+public interface FilerModelService {
+
+ QName getFileableAspect();
+
+ QName getSegmentAspect();
+
+ QName getSubscriberAspect();
+
+ QName getPropertyInheritanceAspect();
+
+ void runWithoutFileableBehaviour(Runnable callback);
+
+ void runWithoutFileableBehaviour(NodeRef nodeRef, Runnable callback);
+
+ void runWithoutSubscriberBehaviour(NodeRef nodeRef, Runnable callback);
+
+ void runWithoutBehaviours(NodeRef nodeRef, Runnable callback, QName... behaviours);
+}
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/FilerOperationService.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/FilerOperationService.java
new file mode 100644
index 0000000..0c6d595
--- /dev/null
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/FilerOperationService.java
@@ -0,0 +1,30 @@
+package com.atolcd.alfresco.filer.core.service;
+
+import java.util.function.Consumer;
+
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.namespace.QName;
+
+import com.atolcd.alfresco.filer.core.model.FilerAction;
+import com.atolcd.alfresco.filer.core.model.RepositoryNode;
+
+public interface FilerOperationService {
+
+ void execute(FilerAction action, RepositoryNode node);
+
+ void setFileable(NodeRef nodeRef);
+
+ void setSegment(NodeRef nodeRef);
+
+ void setSubscriber(NodeRef nodeRef);
+
+ NodeRef getFolder(NodeRef parent, String name, Consumer onGet);
+
+ NodeRef getOrCreateFolder(NodeRef parent, QName type, String name, Consumer onGet, Consumer onCreate);
+
+ void updateFileable(RepositoryNode node, NodeRef destination, String newName);
+
+ void updateFolder(RepositoryNode node, Consumer onGet, Consumer onCreate);
+
+ void deleteSegment(NodeRef nodeRef);
+}
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/FilerRegistry.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/FilerRegistry.java
new file mode 100644
index 0000000..3cd55c7
--- /dev/null
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/FilerRegistry.java
@@ -0,0 +1,30 @@
+package com.atolcd.alfresco.filer.core.service;
+
+import java.util.Set;
+import java.util.SortedSet;
+
+import com.atolcd.alfresco.filer.core.model.FilerAction;
+import com.atolcd.alfresco.filer.core.scope.FilerScopeLoader;
+
+public interface FilerRegistry {
+
+ /**
+ * Register an action that can be executed
+ */
+ void registerAction(FilerAction action);
+
+ /**
+ * Register a loader that will be able to initialize required information to perform the filing
+ */
+ void registerScopeLoader(FilerScopeLoader scopeLoader);
+
+ /**
+ * Get actions that can be executed
+ */
+ SortedSet getActions();
+
+ /**
+ * Get loaders to initialize the node scope
+ */
+ Set getScopeLoaders();
+}
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/FilerService.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/FilerService.java
new file mode 100644
index 0000000..469d9b9
--- /dev/null
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/FilerService.java
@@ -0,0 +1,27 @@
+package com.atolcd.alfresco.filer.core.service;
+
+import org.alfresco.service.cmr.repository.NodeRef;
+
+import com.atolcd.alfresco.filer.core.model.FilerEvent;
+
+public interface FilerService {
+
+ /**
+ * Gather information on a fileable node that is being updated
+ */
+ void initFileable(NodeRef nodeRef);
+
+ /**
+ * Execute an action on a fileable node
+ */
+ void executeAction(FilerEvent event);
+
+ /**
+ * Decide whether the node associated to this event should be filed and in that case set it fileable
+ */
+ boolean resolveFileable(FilerEvent event);
+
+ FilerOperationService operations();
+
+ PropertyInheritanceService propertyInheritance();
+}
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/FilerUpdateService.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/FilerUpdateService.java
new file mode 100644
index 0000000..de8c2cd
--- /dev/null
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/FilerUpdateService.java
@@ -0,0 +1,16 @@
+package com.atolcd.alfresco.filer.core.service;
+
+import com.atolcd.alfresco.filer.core.model.RepositoryNode;
+
+public interface FilerUpdateService {
+
+ /**
+ * Update and move node according to the filer operation result.
+ * Node was first present with the state of initialNode and may have been updated to the state of originalNode before
+ * filer operation
+ * @param initialNode node at the beginning, before the request
+ * @param originalNode node before applying filer
+ * @param resultingNode node after applying filer
+ */
+ void updateAndMoveFileable(RepositoryNode initialNode, RepositoryNode originalNode, RepositoryNode resultingNode);
+}
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/PropertyInheritanceService.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/PropertyInheritanceService.java
new file mode 100644
index 0000000..f90d12f
--- /dev/null
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/PropertyInheritanceService.java
@@ -0,0 +1,39 @@
+package com.atolcd.alfresco.filer.core.service;
+
+import org.alfresco.service.cmr.repository.NodeRef;
+
+import com.atolcd.alfresco.filer.core.model.PropertyInheritance;
+import com.atolcd.alfresco.filer.core.model.PropertyInheritancePayload;
+import com.atolcd.alfresco.filer.core.model.RepositoryNode;
+import com.atolcd.alfresco.filer.core.model.RepositoryNodeDifference;
+
+/**
+ * Service responsible for dealing with property inheritance (mandatory or optional)
+ * that has to be enforced when a node is created or updated
+ */
+public interface PropertyInheritanceService {
+
+ /**
+ * Retrieve the aspects and properties that are inherited from the parent node identified by its {@link NodeRef}
+ * and stores them in the resulting {@link RepositoryNode}
+ */
+ void computeAspectsAndProperties(NodeRef nodeRef, RepositoryNode result);
+
+ /**
+ * Update the node identified by its {@link NodeRef} using the {@link PropertyInheritance} definition to decide
+ * which aspects and properties should be applied from the {@code payload}
+ */
+ void setProperties(NodeRef nodeRef, RepositoryNode payload, PropertyInheritance inheritance);
+
+ /**
+ * Generate the {@link PropertyInheritancePayload} based on the {@link RepositoryNodeDifference} which is needed to
+ * know which aspects and properties are to be added or removed on the children recursively
+ */
+ PropertyInheritancePayload getPayload(RepositoryNodeDifference difference);
+
+ /**
+ * Update recursively a tree view identified by its root {@link NodeRef} using the {@link PropertyInheritancePayload}
+ * content to know which aspects and properties to add or remove
+ */
+ void setInheritance(NodeRef root, PropertyInheritancePayload payload);
+}
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerBuilder.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerBuilder.java
new file mode 100644
index 0000000..16343ff
--- /dev/null
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerBuilder.java
@@ -0,0 +1,41 @@
+package com.atolcd.alfresco.filer.core.service.impl;
+
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+import org.alfresco.service.cmr.repository.NodeRef;
+
+import com.atolcd.alfresco.filer.core.model.FilerFolderContext;
+import com.atolcd.alfresco.filer.core.model.RepositoryNode;
+import com.atolcd.alfresco.filer.core.service.FilerService;
+
+public class FilerBuilder {
+
+ private final FilerService filerService;
+ private final RepositoryNode node;
+
+ public FilerBuilder(final FilerService filerService, final RepositoryNode node) {
+ this.filerService = filerService;
+ this.node = node;
+ }
+
+ public FilerFolderBuilder root(final NodeRef nodeRef) {
+ return new FilerFolderBuilder(filerService, new FilerFolderContext(node), nodeRef);
+ }
+
+ public FilerFolderBuilder root(final Function nodeRefExtractor) {
+ return root(nodeRefExtractor.apply(node));
+ }
+
+ public FilerFolderBuilder root(final Supplier nodeRefSupplier) {
+ return root(nodeRefSupplier.get());
+ }
+
+ public FilerFolderTypeBuilder with(final Function builder) {
+ return builder.apply(this);
+ }
+
+ public RepositoryNode getNode() {
+ return node;
+ }
+}
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerFolderBuilder.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerFolderBuilder.java
new file mode 100644
index 0000000..b26314a
--- /dev/null
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerFolderBuilder.java
@@ -0,0 +1,81 @@
+package com.atolcd.alfresco.filer.core.service.impl;
+
+import java.util.Optional;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+import org.alfresco.model.ContentModel;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.namespace.QName;
+
+import com.atolcd.alfresco.filer.core.model.FilerFolderContext;
+import com.atolcd.alfresco.filer.core.model.RepositoryNode;
+import com.atolcd.alfresco.filer.core.service.FilerService;
+
+public class FilerFolderBuilder {
+
+ private final FilerService filerService;
+
+ private final FilerFolderContext context;
+
+ private FilerNameBuilder nodeNameBuilder;
+
+ public FilerFolderBuilder(final FilerService filerService, final FilerFolderContext context, final NodeRef parent) {
+ this.filerService = filerService;
+ this.context = new FilerFolderContext(context, parent);
+ }
+
+ public FilerFolderBuilder condition(final Predicate condition) {
+ context.enable(condition.test(context.getNode()));
+ nodeNameBuilder = null; // NOPMD - reset value
+ return this;
+ }
+
+ public FilerFolderBuilder conditionReverse() {
+ return condition(x -> !context.isEnabled());
+ }
+
+ public FilerFolderBuilder conditionEnd() {
+ return condition(x -> true);
+ }
+
+ public FilerFolderTypeBuilder with(final Function builder) {
+ return builder.apply(this);
+ }
+
+ public FilerFolderBuilder tree(final Function builder) {
+ return builder.apply(this);
+ }
+
+ public FilerFolderTypeBuilder folder(final QName type) {
+ return new FilerFolderTypeBuilder(filerService, context, type);
+ }
+
+ public FilerFolderTypeBuilder folder() {
+ return folder(ContentModel.TYPE_FOLDER);
+ }
+
+ public FilerNameBuilder rename() {
+ nodeNameBuilder = Optional.ofNullable(nodeNameBuilder).orElseGet(() -> new FilerNameBuilder<>(this, context));
+ return nodeNameBuilder;
+ }
+
+ public void updateAndMove() {
+ if (context.isEnabled()) {
+ String name = Optional.ofNullable(rename().getName()).orElseGet(() -> context.getNode().getName().get());
+ filerService.operations().updateFileable(context.getNode(), context.getParent(), name);
+ }
+ }
+
+ public FilerFolderBuilder contextFrom(final Consumer withContext) {
+ if (context.isEnabled()) {
+ withContext.accept(context);
+ }
+ return this;
+ }
+
+ public FilerFolderContext getContext() {
+ return context;
+ }
+}
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerFolderServiceImpl.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerFolderServiceImpl.java
new file mode 100644
index 0000000..2558bd5
--- /dev/null
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerFolderServiceImpl.java
@@ -0,0 +1,128 @@
+package com.atolcd.alfresco.filer.core.service.impl;
+
+import java.util.Collections;
+import java.util.Objects;
+import java.util.function.Consumer;
+
+import org.alfresco.model.ContentModel;
+import org.alfresco.repo.domain.node.NodeDAO;
+import org.alfresco.repo.security.authentication.AuthenticationUtil;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.repository.NodeService;
+import org.alfresco.service.namespace.NamespaceService;
+import org.alfresco.service.namespace.QName;
+
+import com.atolcd.alfresco.filer.core.model.FilerException;
+import com.atolcd.alfresco.filer.core.model.RepositoryNode;
+import com.atolcd.alfresco.filer.core.service.FilerFolderService;
+import com.atolcd.alfresco.filer.core.service.FilerModelService;
+import com.atolcd.alfresco.filer.core.util.FilerNodeUtils;
+
+public class FilerFolderServiceImpl implements FilerFolderService {
+
+ private FilerModelService filerModelService;
+ private NodeService nodeService;
+ private NodeDAO nodeDAO;
+
+ @Override
+ public void fetchFolder(final RepositoryNode node, final Consumer onGet) {
+ Objects.requireNonNull(onGet);
+ fetchOrCreateFolderImpl(node, onGet, null);
+ if (node.getNodeRef() == null) {
+ throw new FilerException("Could not get filer folder: " + node);
+ }
+ }
+
+ @Override
+ public void fetchOrCreateFolder(final RepositoryNode node, final Consumer onGet, final Consumer onCreate) {
+ Objects.requireNonNull(onGet);
+ Objects.requireNonNull(onCreate);
+ fetchOrCreateFolderImpl(node, onGet, onCreate);
+ }
+
+ @Override
+ public void updateFolder(final RepositoryNode node, final Consumer onGet, final Consumer onCreate) {
+ Objects.requireNonNull(onGet);
+ Objects.requireNonNull(onCreate);
+ if (FilerNodeUtils.isOriginal(node)) {
+ afterCreateFolder(node, onCreate);
+ } else {
+ afterGetFolder(node, onGet);
+ }
+ }
+
+ private void fetchOrCreateFolderImpl(final RepositoryNode node, final Consumer onGet,
+ final Consumer onCreate) {
+ doGetFolder(node, onGet);
+ if (onCreate != null && node.getNodeRef() == null) {
+ NodeRef nodeRef = node.getParent();
+ lockFolder(nodeRef);
+ // Proceed with creation
+ filerModelService.runWithoutSubscriberBehaviour(nodeRef, () -> {
+ doCreateFolder(node, onCreate);
+ });
+ }
+ }
+
+ @Override
+ public void lockFolder(final NodeRef nodeRef) {
+ Long nodeId = nodeDAO.getNodePair(nodeRef).getFirst();
+ filerModelService.runWithoutBehaviours(nodeRef, () -> {
+ // This will effectively lock the node preventing other transactions to go further
+ // They will be blocked here and when they become free, they will throw a ConcurrencyFailureException
+ // which will cause a retry of the whole transaction in the RetryingTransactionHelper
+ nodeDAO.updateNode(nodeId, null, null);
+ }, ContentModel.ASPECT_AUDITABLE);
+ }
+
+ private void doGetFolder(final RepositoryNode node, final Consumer onGet) {
+ NodeRef nodeRef = nodeService.getChildByName(node.getParent(), ContentModel.ASSOC_CONTAINS, node.getName().get());
+ if (nodeRef != null) {
+ node.setNodeRef(nodeRef);
+ afterGetFolder(node, onGet);
+ }
+ }
+
+ private void afterGetFolder(final RepositoryNode node, final Consumer onGet) {
+ onGet.accept(node.getNodeRef());
+ }
+
+ private void doCreateFolder(final RepositoryNode node, final Consumer onCreate) {
+ QName assoc = QName.createQNameWithValidLocalName(NamespaceService.CONTENT_MODEL_1_0_URI, node.getName().get());
+ // Node can have a fileable mandatory-aspect, but it is already created at the right place so there is no need to
+ // trigger filer on it (FileableAspect#onAddAspect). Disable behaviour globally because nodeRef is unknown at creation time
+ filerModelService.runWithoutFileableBehaviour(() -> {
+ NodeRef nodeRef = nodeService.createNode(node.getParent(), ContentModel.ASSOC_CONTAINS, assoc, node.getType(),
+ Collections.singletonMap(ContentModel.PROP_NAME, node.getName().get())).getChildRef();
+ node.setNodeRef(nodeRef);
+ });
+ afterCreateFolder(node, onCreate);
+ }
+
+ /**
+ * Run as System because current user may not have the update permission on the node anymore (he might not be the owner)
+ * @see com.atolcd.alfresco.filer.repo.policy.FilerSegmentAspect#onAddAspect
+ */
+ private void afterCreateFolder(final RepositoryNode node, final Consumer onCreate) {
+ AuthenticationUtil.runAsSystem(() -> {
+ // Do not trigger a filer action on itself i.e. the updated node (FileableAspect#OnUpdateProperties).
+ // This is mainly due to property inheritance
+ filerModelService.runWithoutFileableBehaviour(node.getNodeRef(), () -> {
+ onCreate.accept(node.getNodeRef());
+ });
+ return null;
+ });
+ }
+
+ public void setFilerModelService(final FilerModelService filerModelService) {
+ this.filerModelService = filerModelService;
+ }
+
+ public void setNodeService(final NodeService nodeService) {
+ this.nodeService = nodeService;
+ }
+
+ public void setNodeDAO(final NodeDAO nodeDAO) {
+ this.nodeDAO = nodeDAO;
+ }
+}
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerFolderTypeBuilder.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerFolderTypeBuilder.java
new file mode 100644
index 0000000..5c436a6
--- /dev/null
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerFolderTypeBuilder.java
@@ -0,0 +1,131 @@
+package com.atolcd.alfresco.filer.core.service.impl;
+
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.namespace.QName;
+
+import com.atolcd.alfresco.filer.core.model.FilerFolderContext;
+import com.atolcd.alfresco.filer.core.model.RepositoryNode;
+import com.atolcd.alfresco.filer.core.service.FilerService;
+
+public class FilerFolderTypeBuilder {
+
+ private final FilerService filerService;
+ private final FilerFolderContext context;
+ private final QName filerType;
+
+ private FilerNameBuilder nameBuilder;
+ private Consumer onFilerGet;
+ private Consumer onFilerCreate;
+
+ public FilerFolderTypeBuilder(final FilerService filerService, final FilerFolderContext context, final QName filerType) {
+ this.filerService = filerService;
+ this.context = new FilerFolderContext(context);
+ this.filerType = filerType;
+ }
+
+ private Consumer onCreate() {
+ return Optional.ofNullable(onFilerCreate).orElse(x -> {});
+ }
+
+ public FilerFolderTypeBuilder onCreate(final Consumer onCreate) {
+ onFilerCreate = onCreate().andThen(onCreate);
+ return this;
+ }
+
+ public FilerFolderTypeBuilder onCreate(final BiConsumer onCreate) {
+ return onCreate(nodeRef -> onCreate.accept(nodeRef, context.getNode()));
+ }
+
+ public FilerFolderTypeBuilder asSegment() {
+ return onCreate(filerService.operations()::setSegment);
+ }
+
+ public FilerFolderTypeBuilder asSubscriber() {
+ return onCreate(filerService.operations()::setSubscriber);
+ }
+
+ public FilerFolderTypeBuilder asFileable() {
+ return onCreate(filerService.operations()::setFileable);
+ }
+
+ public FilerFolderTypeBuilder mandatoryPropertyInheritance(final QName... aspects) {
+ if (context.isEnabled()) {
+ context.getPropertyInheritance().getMandatoryAspects().addAll(Arrays.asList(aspects));
+ }
+ return this;
+ }
+
+ public FilerFolderTypeBuilder optionalPropertyInheritance(final QName... aspects) {
+ if (context.isEnabled()) {
+ context.getPropertyInheritance().getOptionalAspects().addAll(Arrays.asList(aspects));
+ }
+ return this;
+ }
+
+ public FilerFolderTypeBuilder clearPropertyInheritance() {
+ if (context.isEnabled()) {
+ context.clearPropertyInheritance();
+ }
+ return this;
+ }
+
+ private Consumer onGet() {
+ return Optional.ofNullable(onFilerGet).orElse(x -> {});
+ }
+
+ public FilerFolderTypeBuilder onGet(final Consumer onGet) {
+ onFilerGet = onGet().andThen(onGet);
+ return this;
+ }
+
+ public FilerFolderTypeBuilder onGet(final BiConsumer onGet) {
+ return onGet(nodeRef -> onGet.accept(nodeRef, context.getNode()));
+ }
+
+ public FilerNameBuilder named() {
+ nameBuilder = Optional.ofNullable(nameBuilder).orElseGet(() -> new FilerNameBuilder<>(this, context));
+ return nameBuilder;
+ }
+
+ public FilerFolderBuilder getOrCreate() {
+ NodeRef child = context.getParent();
+ if (context.isEnabled()) {
+ String name = Objects.requireNonNull(named().getName());
+ if (context.hasPropertyInheritance()) {
+ // Apply property inheritance on the folder
+ onCreate(nodeRef -> filerService.propertyInheritance()
+ .setProperties(nodeRef, context.getNode(), context.getPropertyInheritance()));
+ }
+ child = filerService.operations().getOrCreateFolder(child, filerType, name, onGet(), onCreate());
+ }
+ return new FilerFolderBuilder(filerService, context, child);
+ }
+
+ public FilerFolderBuilder get() {
+ NodeRef child = context.getParent();
+ if (context.isEnabled()) {
+ String name = Objects.requireNonNull(named().getName());
+ child = filerService.operations().getFolder(child, name, onGet());
+ }
+ return new FilerFolderBuilder(filerService, context, child);
+ }
+
+ public void updateAndMove() {
+ if (context.isEnabled()) {
+ RepositoryNode node = context.getNode();
+ // Update type
+ node.setType(filerType);
+ String name = Objects.requireNonNull(named().getName());
+ // Update node, which will apply property inheritance
+ filerService.operations().updateFileable(node, context.getParent(), name);
+ // Apply node get/create functions if this is required, property inheritance is already applied
+ filerService.operations().updateFolder(node, onGet(), onCreate());
+ }
+ }
+}
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerModelServiceImpl.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerModelServiceImpl.java
new file mode 100644
index 0000000..ca5702d
--- /dev/null
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerModelServiceImpl.java
@@ -0,0 +1,93 @@
+package com.atolcd.alfresco.filer.core.service.impl;
+
+import org.alfresco.repo.policy.BehaviourFilter;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.namespace.QName;
+
+import com.atolcd.alfresco.filer.core.service.FilerModelService;
+
+public class FilerModelServiceImpl implements FilerModelService {
+
+ private BehaviourFilter behaviourFilter;
+
+ private QName fileableAspect;
+ private QName segmentAspect;
+ private QName subscriberAspect;
+ private QName propertyInheritanceAspect;
+
+ @Override
+ public QName getFileableAspect() {
+ return fileableAspect;
+ }
+
+ @Override
+ public QName getSegmentAspect() {
+ return segmentAspect;
+ }
+
+ @Override
+ public QName getSubscriberAspect() {
+ return subscriberAspect;
+ }
+
+ @Override
+ public QName getPropertyInheritanceAspect() {
+ return propertyInheritanceAspect;
+ }
+
+ @Override
+ public void runWithoutFileableBehaviour(final Runnable callback) {
+ behaviourFilter.disableBehaviour(fileableAspect);
+ try {
+ callback.run();
+ } finally {
+ behaviourFilter.enableBehaviour(fileableAspect);
+ }
+ }
+
+ @Override
+ public void runWithoutFileableBehaviour(final NodeRef nodeRef, final Runnable callback) {
+ runWithoutBehaviours(nodeRef, callback, fileableAspect);
+ }
+
+ @Override
+ public void runWithoutSubscriberBehaviour(final NodeRef nodeRef, final Runnable callback) {
+ runWithoutBehaviours(nodeRef, callback,
+ segmentAspect, // Segment extends Subscriber but it is ignored... (see ALF-21992)
+ subscriberAspect);
+ }
+
+ @Override
+ public void runWithoutBehaviours(final NodeRef nodeRef, final Runnable callback, final QName... behaviours) {
+ for (QName behaviour : behaviours) {
+ behaviourFilter.disableBehaviour(nodeRef, behaviour);
+ }
+ try {
+ callback.run();
+ } finally {
+ for (QName behaviour : behaviours) {
+ behaviourFilter.enableBehaviour(nodeRef, behaviour);
+ }
+ }
+ }
+
+ public void setBehaviourFilter(final BehaviourFilter behaviourFilter) {
+ this.behaviourFilter = behaviourFilter;
+ }
+
+ public void setFileableAspectQName(final String fileableAspectQName) {
+ this.fileableAspect = QName.createQName(fileableAspectQName);
+ }
+
+ public void setSegmentAspectQName(final String segmentAspectQName) {
+ this.segmentAspect = QName.createQName(segmentAspectQName);
+ }
+
+ public void setSubscriberAspectQName(final String subscriberAspectQName) {
+ this.subscriberAspect = QName.createQName(subscriberAspectQName);
+ }
+
+ public void setPropertyInheritanceAspectQName(final String propertyInheritanceAspectQName) {
+ this.propertyInheritanceAspect = QName.createQName(propertyInheritanceAspectQName);
+ }
+}
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerNameBuilder.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerNameBuilder.java
new file mode 100644
index 0000000..eda0a8b
--- /dev/null
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerNameBuilder.java
@@ -0,0 +1,96 @@
+package com.atolcd.alfresco.filer.core.service.impl;
+
+import java.io.Serializable;
+import java.text.DateFormat;
+import java.text.MessageFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+import org.alfresco.model.ContentModel;
+import org.alfresco.service.namespace.QName;
+
+import com.atolcd.alfresco.filer.core.model.FilerException;
+import com.atolcd.alfresco.filer.core.model.FilerFolderContext;
+import com.atolcd.alfresco.filer.core.model.RepositoryNode;
+
+public class FilerNameBuilder {
+
+ private final T builder;
+ private final FilerFolderContext context;
+
+ private String filerName;
+
+ public FilerNameBuilder(final T builder, final FilerFolderContext context) {
+ this.builder = builder;
+ this.context = context;
+ }
+
+ public String getName() {
+ return filerName;
+ }
+
+ public T with(final String name) {
+ if (context.isEnabled()) {
+ filerName = name;
+ }
+ return builder;
+ }
+
+ public T with(final Date date, final String dateFormat) {
+ return with(() -> {
+ DateFormat formatter = new SimpleDateFormat(dateFormat);
+ return formatter.format(date);
+ });
+ }
+
+ public T with(final String pattern, final QName... properties) {
+ return with(() -> {
+ List values = new ArrayList<>();
+ for (QName property : properties) {
+ Serializable value = getProperty(property, Serializable.class);
+ values.add(value);
+ }
+ return MessageFormat.format(pattern, values.toArray());
+ });
+ }
+
+ public T with(final Supplier nodeNameFormatter) {
+ return with(node -> {
+ return nodeNameFormatter.get();
+ });
+ }
+
+ public T with(final Function nodeNameFormatter) {
+ String name = null;
+ if (context.isEnabled()) {
+ name = nodeNameFormatter.apply(context.getNode());
+ }
+ return with(name);
+ }
+
+ public T withPropertyName() {
+ return withProperty(ContentModel.PROP_NAME);
+ }
+
+ public T withProperty(final QName propertyName) {
+ String name = getProperty(propertyName, String.class);
+ return with(name);
+ }
+
+ public T withPropertyDate(final QName propertyName, final String dateFormat) {
+ Date date = getProperty(propertyName, Date.class);
+ return with(date, dateFormat);
+ }
+
+ private C getProperty(final QName propertyName, final Class clazz) {
+ C value = context.getNode().getProperty(propertyName, clazz);
+ if (value == null && context.isEnabled()) {
+ throw new FilerException("Could not get property '" + propertyName + "' for node: " + context.getNode());
+ }
+ return value;
+ }
+}
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerOperationServiceImpl.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerOperationServiceImpl.java
new file mode 100644
index 0000000..d768db6
--- /dev/null
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerOperationServiceImpl.java
@@ -0,0 +1,176 @@
+package com.atolcd.alfresco.filer.core.service.impl;
+
+import java.util.Collections;
+import java.util.function.Consumer;
+
+import org.alfresco.model.ContentModel;
+import org.alfresco.repo.security.authentication.AuthenticationUtil;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.repository.NodeService;
+import org.alfresco.service.cmr.security.PermissionService;
+import org.alfresco.service.namespace.QName;
+import org.alfresco.service.namespace.RegexQNamePattern;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.atolcd.alfresco.filer.core.model.FilerAction;
+import com.atolcd.alfresco.filer.core.model.RepositoryNode;
+import com.atolcd.alfresco.filer.core.service.FilerFolderService;
+import com.atolcd.alfresco.filer.core.service.FilerModelService;
+import com.atolcd.alfresco.filer.core.service.FilerOperationService;
+import com.atolcd.alfresco.filer.core.service.FilerUpdateService;
+import com.atolcd.alfresco.filer.core.util.FilerNodeUtils;
+import com.atolcd.alfresco.filer.core.util.FilerTransactionUtils;
+
+public class FilerOperationServiceImpl implements FilerOperationService {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(FilerOperationServiceImpl.class);
+
+ private FilerModelService filerModelService;
+ private FilerFolderService filerFolderService;
+ private FilerUpdateService filerUpdateService;
+ private NodeService nodeService;
+ private PermissionService permissionService;
+
+ @Override
+ public void execute(final FilerAction action, final RepositoryNode node) {
+ filerModelService.runWithoutFileableBehaviour(node.getNodeRef(), () -> {
+ action.execute(node);
+ });
+ }
+
+ @Override
+ public void setSegment(final NodeRef nodeRef) {
+ nodeService.addAspect(nodeRef, filerModelService.getSegmentAspect(), Collections.emptyMap());
+ }
+
+ @Override
+ public void setFileable(final NodeRef nodeRef) {
+ nodeService.addAspect(nodeRef, filerModelService.getFileableAspect(), Collections.emptyMap());
+ }
+
+ @Override
+ public void setSubscriber(final NodeRef nodeRef) {
+ nodeService.addAspect(nodeRef, filerModelService.getSubscriberAspect(), Collections.emptyMap());
+ }
+
+ @Override
+ public NodeRef getFolder(final NodeRef parent, final String name, final Consumer onGet) {
+ RepositoryNode node = RepositoryNode.builder().parent(parent).named(name).build();
+ try {
+ filerFolderService.fetchFolder(node, onGet);
+ } catch (RuntimeException e) { // NOPMD - for logging purposes
+ LOGGER.error("Could not get filer folder: " + node, e);
+ throw e;
+ }
+ return node.getNodeRef();
+ }
+
+ @Override
+ public NodeRef getOrCreateFolder(final NodeRef parent, final QName type, final String name,
+ final Consumer onGet, final Consumer onCreate) {
+ RepositoryNode node = RepositoryNode.builder().parent(parent).type(type).named(name).build();
+ try {
+ filerFolderService.fetchOrCreateFolder(node, onGet, onCreate);
+ } catch (RuntimeException e) { // NOPMD - for logging purposes
+ LOGGER.error("Could not get or create filer folder: " + node, e);
+ throw e;
+ }
+ return node.getNodeRef();
+ }
+
+ @Override
+ public void updateFileable(final RepositoryNode node, final NodeRef destination, final String newName) {
+ node.setParent(destination);
+ node.getProperties().put(ContentModel.PROP_NAME, newName);
+ RepositoryNode initialNode = FilerTransactionUtils.getInitialNode(node.getNodeRef());
+ RepositoryNode originalNode = FilerNodeUtils.getOriginalNode(node);
+ try {
+ filerUpdateService.updateAndMoveFileable(initialNode, originalNode, node);
+ } catch (RuntimeException e) { // NOPMD - for logging purposes
+ LOGGER.error("Could not update fileable: " + node, e);
+ throw e;
+ }
+ // Delete previous parent if it became an empty segment
+ deleteSegment(originalNode.getParent());
+ }
+
+ @Override
+ public void updateFolder(final RepositoryNode node, final Consumer onGet, final Consumer onCreate) {
+ try {
+ filerFolderService.updateFolder(node, onGet, onCreate);
+ } catch (RuntimeException e) { // NOPMD - for logging purposes
+ LOGGER.error("Could not update filer folder: " + node, e);
+ throw e;
+ }
+ }
+
+ @Override
+ public void deleteSegment(final NodeRef nodeRef) {
+ try {
+ // Run as System because current user may not have the permission to see all nodes nor to remove nodes
+ AuthenticationUtil.runAsSystem(() -> {
+ deleteSegmentRecursively(nodeRef);
+ return null;
+ });
+ } catch (RuntimeException e) { // NOPMD - for logging purposes
+ LOGGER.error("Could not remove filer segment: " + nodeRef, e);
+ throw e;
+ }
+ }
+
+ private void deleteSegmentRecursively(final NodeRef nodeRef) {
+ // Check that this is indeed a filer
+ if (nodeService.exists(nodeRef) && nodeService.hasAspect(nodeRef, filerModelService.getSegmentAspect())) {
+ // Lock it, to prevent any concurrent removal that may fail to find that it became empty
+ // Indeed, another thread can be deleting the last child but may have not committed yet
+ filerFolderService.lockFolder(nodeRef);
+ // Check that it has no child anymore
+ if (nodeService.getChildAssocs(nodeRef, ContentModel.ASSOC_CONTAINS, RegexQNamePattern.MATCH_ALL).isEmpty()) {
+ // Node could be part of a hierarchy deletion, in this case it will be deleted that way
+ doDeleteSegment(nodeRef);
+ }
+ }
+ }
+
+ private void doDeleteSegment(final NodeRef nodeRef) {
+ // Get parent nodeRef before deleting child... so the association still exists
+ NodeRef parent = nodeService.getPrimaryParent(nodeRef).getParentRef();
+ if (!nodeService.hasAspect(nodeRef, ContentModel.ASPECT_PENDING_DELETE)) {
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("Deleting empty filer segment: {}{node={}, path=\"{}\"}",
+ nodeService.getProperty(nodeRef, ContentModel.PROP_NAME),
+ nodeRef.getId(),
+ nodeService.getPath(nodeRef).toDisplayPath(nodeService, permissionService));
+ }
+ // In case filerSegment is a fileable too
+ filerModelService.runWithoutFileableBehaviour(nodeRef, () -> {
+ nodeService.deleteNode(nodeRef);
+ });
+ }
+ // Check if parent is now also an empty filer (policies are not applied recursively)
+ if (parent != null) {
+ deleteSegmentRecursively(parent);
+ }
+ }
+
+ public void setFilerModelService(final FilerModelService filerModelService) {
+ this.filerModelService = filerModelService;
+ }
+
+ public void setFilerFolderService(final FilerFolderService filerFolderService) {
+ this.filerFolderService = filerFolderService;
+ }
+
+ public void setFilerUpdateService(final FilerUpdateService filerUpdateService) {
+ this.filerUpdateService = filerUpdateService;
+ }
+
+ public void setNodeService(final NodeService nodeService) {
+ this.nodeService = nodeService;
+ }
+
+ public void setPermissionService(final PermissionService permissionService) {
+ this.permissionService = permissionService;
+ }
+}
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerRegistryImpl.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerRegistryImpl.java
new file mode 100644
index 0000000..d4c0be6
--- /dev/null
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerRegistryImpl.java
@@ -0,0 +1,39 @@
+package com.atolcd.alfresco.filer.core.service.impl;
+
+import java.util.LinkedHashSet;
+import java.util.Optional;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import com.atolcd.alfresco.filer.core.model.FilerAction;
+import com.atolcd.alfresco.filer.core.scope.FilerScopeLoader;
+import com.atolcd.alfresco.filer.core.service.FilerRegistry;
+
+public class FilerRegistryImpl implements FilerRegistry {
+
+ private SortedSet actions;
+ private Set scopeLoaders;
+
+ @Override
+ public void registerAction(final FilerAction action) {
+ getActions().add(action);
+ }
+
+ @Override
+ public void registerScopeLoader(final FilerScopeLoader scopeLoader) {
+ getScopeLoaders().add(scopeLoader);
+ }
+
+ @Override
+ public SortedSet getActions() {
+ actions = Optional.ofNullable(actions).orElseGet(TreeSet::new);
+ return actions;
+ }
+
+ @Override
+ public Set getScopeLoaders() {
+ scopeLoaders = Optional.ofNullable(scopeLoaders).orElseGet(LinkedHashSet::new);
+ return scopeLoaders;
+ }
+}
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerServiceImpl.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerServiceImpl.java
new file mode 100644
index 0000000..c2ee3cf
--- /dev/null
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerServiceImpl.java
@@ -0,0 +1,226 @@
+package com.atolcd.alfresco.filer.core.service.impl;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+import org.alfresco.model.ContentModel;
+import org.alfresco.service.cmr.lock.LockService;
+import org.alfresco.service.cmr.lock.NodeLockedException;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.repository.NodeService;
+import org.alfresco.service.cmr.security.PermissionService;
+import org.alfresco.service.namespace.QName;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.atolcd.alfresco.filer.core.model.FilerAction;
+import com.atolcd.alfresco.filer.core.model.FilerEvent;
+import com.atolcd.alfresco.filer.core.model.RepositoryNode;
+import com.atolcd.alfresco.filer.core.model.RepositoryNodeDifference;
+import com.atolcd.alfresco.filer.core.model.UpdateFilerEvent;
+import com.atolcd.alfresco.filer.core.service.FilerOperationService;
+import com.atolcd.alfresco.filer.core.service.FilerRegistry;
+import com.atolcd.alfresco.filer.core.service.FilerService;
+import com.atolcd.alfresco.filer.core.service.PropertyInheritanceService;
+import com.atolcd.alfresco.filer.core.util.FilerNodeUtils;
+import com.atolcd.alfresco.filer.core.util.FilerTransactionUtils;
+
+public class FilerServiceImpl implements FilerService {
+
+ private static final Collection IGNORED_PROPERTIES = Arrays.asList(
+ ContentModel.PROP_CONTENT, // Because content is never used to build a filer plan
+ ContentModel.PROP_LAST_THUMBNAIL_MODIFICATION_DATA, // Added by Share while browsing parent folder
+ ContentModel.PROP_CASCADE_CRC, ContentModel.PROP_CASCADE_TX, // Added by CascadeUpdateAspect on move */
+ ContentModel.PROP_MODIFIER, ContentModel.PROP_MODIFIED
+ );
+
+ private static final Collection IGNORED_ASPECTS = Arrays.asList(
+ ContentModel.ASPECT_THUMBNAIL_MODIFICATION // Added by Share while browsing parent folder
+ );
+ private static final Logger LOGGER = LoggerFactory.getLogger(FilerServiceImpl.class);
+
+ private FilerRegistry filerRegistry;
+ private FilerOperationService filerOperationService;
+ private PropertyInheritanceService propertyInheritanceService;
+ private NodeService nodeService;
+ private PermissionService permissionService;
+ private LockService lockService;
+
+ @Override
+ public void initFileable(final NodeRef nodeRef) {
+ if (nodeService.exists(nodeRef)) {
+ RepositoryNode initialNode = RepositoryNode.builder().nodeRef(nodeRef)
+ .aspects(nodeService.getAspects(nodeRef))
+ .properties(nodeService.getProperties(nodeRef)).build();
+ FilerTransactionUtils.putInitialNode(nodeRef, initialNode);
+ }
+ }
+
+ @Override
+ public void executeAction(final FilerEvent event) {
+ try {
+ executeActionImpl(event);
+ } catch (RuntimeException e) { // NOPMD - for logging purposes
+ LOGGER.error("Could not execute action: " + event, e);
+ throw e;
+ }
+ }
+
+ @Override
+ public boolean resolveFileable(final FilerEvent event) {
+ try {
+ boolean result = false;
+ // Upon creation, node details may not be all set, so only perform resolution checks
+ if (resolveAction(event, true)) {
+ filerOperationService.setFileable(event.getNode().getNodeRef());
+ result = true;
+ }
+ return result;
+ } catch (RuntimeException e) { // NOPMD - for logging purposes
+ LOGGER.error("Could not resolve fileable: " + event, e);
+ throw e;
+ }
+ }
+
+ private void executeActionImpl(final FilerEvent event) {
+ if (resolveAction(event, false)) {
+ RepositoryNode node = event.getNode();
+ // Put display path for logging purposes
+ if (LOGGER.isDebugEnabled()) {
+ // Compute display path now, because it may not exist afterwards if a filer has been removed
+ FilerNodeUtils.setDisplayPath(node,
+ nodeService.getPath(node.getNodeRef()).toDisplayPath(nodeService, permissionService));
+ }
+ // Execute filer action
+ event.setExecuted();
+ filerOperationService.execute(event.getAction().get(), node);
+ if (LOGGER.isDebugEnabled()) {
+ String beforePath = FilerNodeUtils.getDisplayPath(node);
+ String afterPath = nodeService.getPath(node.getNodeRef()).toDisplayPath(nodeService, permissionService);
+ afterPath = afterPath.equals(beforePath) ? "Same location" : "It is now at " + afterPath;
+ String afterName = (String) nodeService.getProperty(node.getNodeRef(), ContentModel.PROP_NAME);
+ afterName = node.getName().get().equals(afterName) ? "" : " and renamed " + afterName;
+ LOGGER.debug("Executed filer on {} at {}: {}{}", event, beforePath, afterPath, afterName);
+ }
+ }
+ }
+
+ private boolean resolveAction(final FilerEvent event, final boolean checkOnly) {
+ boolean result = false;
+ NodeRef nodeRef = event.getNode().getNodeRef();
+ // Ensure node exists, it could have been deleted before commit (e.g. check-out/check-in working copy)
+ if (nodeService.exists(nodeRef) && !isLocked(nodeRef)) {
+ Optional previous = FilerTransactionUtils.getEventNode(nodeRef);
+ // Retrieve information from previous event
+ if (previous.isPresent()) {
+ event.comesAfter(previous.get());
+ }
+ // Ensure action is only executed once
+ if (!event.isExecuted() && initAction(event, checkOnly)) {
+ FilerTransactionUtils.putEventNode(nodeRef, event);
+ result = true;
+ }
+ }
+ return result;
+ }
+
+ private boolean initAction(final FilerEvent event, final boolean checkOnly) {
+ RepositoryNode node = event.getNode();
+ filerRegistry.getScopeLoaders().forEach(loader -> loader.init(event));
+ if (!checkOnly) {
+ filerRegistry.getScopeLoaders().forEach(loader -> loader.update(event));
+ // Put original node to be able to compute updates required by filer action
+ // Do this before property inheritance, as it could update the node
+ FilerNodeUtils.setOriginalNode(node, new RepositoryNode(node));
+ // Initialize inherited aspects and associated properties from parent
+ if (!(event instanceof UpdateFilerEvent)) {
+ // Do not do this on update event, this would override updated inheritance properties/aspects
+ propertyInheritanceService.computeAspectsAndProperties(node.getParent(), node);
+ }
+ }
+ return putEventAction(event, checkOnly);
+ }
+
+ private boolean putEventAction(final FilerEvent event, final boolean checkOnly) {
+ boolean hasAction = false;
+ if (checkOnly || isUpdateEvent(event)) {
+ for (FilerAction filer : filerRegistry.getActions()) {
+ hasAction = filer.supportsActionResolution(event) && (checkOnly || filer.supportsActionExecution(event.getNode()));
+ if (hasAction && !checkOnly) {
+ event.setAction(filer);
+ }
+ if (hasAction) {
+ break;
+ }
+ }
+ }
+ return hasAction;
+ }
+
+ private static boolean isUpdateEvent(final FilerEvent event) {
+ boolean result = true;
+ if (event instanceof UpdateFilerEvent) {
+ RepositoryNode initialNode = FilerTransactionUtils.getInitialNode(event.getNode().getNodeRef());
+ RepositoryNodeDifference difference = new RepositoryNodeDifference(initialNode, event.getNode());
+ long updatedPropertiesCount = Stream.of(difference.getPropertiesToAdd().keySet(), difference.getPropertiesToRemove())
+ .flatMap(set -> set.stream())
+ .filter(property -> !IGNORED_PROPERTIES.contains(property))
+ .count();
+ long updatedAspectCount = Stream.of(difference.getAspectsToAdd(), difference.getAspectsToRemove())
+ .flatMap(set -> set.stream())
+ .filter(property -> !IGNORED_ASPECTS.contains(property))
+ .count();
+ if (updatedPropertiesCount + updatedAspectCount == 0) {
+ result = false;
+ LOGGER.debug("Ignoring update event without any updated property nor aspect: " + event);
+ }
+ }
+ return result;
+ }
+
+ private boolean isLocked(final NodeRef nodeRef) {
+ boolean result = false;
+ try {
+ lockService.checkForLock(nodeRef);
+ } catch (NodeLockedException e) {
+ result = true;
+ }
+ return result;
+ }
+
+ @Override
+ public FilerOperationService operations() {
+ return filerOperationService;
+ }
+
+ @Override
+ public PropertyInheritanceService propertyInheritance() {
+ return propertyInheritanceService;
+ }
+
+ public void setFilerRegistry(final FilerRegistry filerRegistry) {
+ this.filerRegistry = filerRegistry;
+ }
+
+ public void setFilerOperationService(final FilerOperationService filerOperationService) {
+ this.filerOperationService = filerOperationService;
+ }
+
+ public void setPropertyInheritanceService(final PropertyInheritanceService propertyInheritanceService) {
+ this.propertyInheritanceService = propertyInheritanceService;
+ }
+
+ public void setNodeService(final NodeService nodeService) {
+ this.nodeService = nodeService;
+ }
+
+ public void setPermissionService(final PermissionService permissionService) {
+ this.permissionService = permissionService;
+ }
+
+ public void setLockService(final LockService lockService) {
+ this.lockService = lockService;
+ }
+}
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerUpdateServiceImpl.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerUpdateServiceImpl.java
new file mode 100644
index 0000000..981b7f4
--- /dev/null
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerUpdateServiceImpl.java
@@ -0,0 +1,118 @@
+package com.atolcd.alfresco.filer.core.service.impl;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.alfresco.model.ContentModel;
+import org.alfresco.repo.copy.AbstractBaseCopyService;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.repository.NodeService;
+import org.alfresco.service.namespace.QName;
+import org.alfresco.util.GUID;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.atolcd.alfresco.filer.core.model.PropertyInheritancePayload;
+import com.atolcd.alfresco.filer.core.model.RepositoryNode;
+import com.atolcd.alfresco.filer.core.model.RepositoryNodeDifference;
+import com.atolcd.alfresco.filer.core.service.FilerModelService;
+import com.atolcd.alfresco.filer.core.service.FilerUpdateService;
+import com.atolcd.alfresco.filer.core.service.PropertyInheritanceService;
+
+public class FilerUpdateServiceImpl extends AbstractBaseCopyService implements FilerUpdateService {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(FilerUpdateServiceImpl.class);
+
+ private FilerModelService filerModelService;
+ private PropertyInheritanceService propertyInheritanceService;
+ private NodeService nodeService;
+
+ @Override
+ public void updateAndMoveFileable(final RepositoryNode initialNode, final RepositoryNode originalNode,
+ final RepositoryNode resultingNode) {
+ NodeRef parent = resultingNode.getParent();
+ // Disable behaviours on parent in case node needs to be moved
+ filerModelService.runWithoutSubscriberBehaviour(parent, () -> {
+ updateAndMoveFileableImpl(initialNode, originalNode, resultingNode);
+ });
+ }
+
+ private void updateAndMoveFileableImpl(final RepositoryNode initialNode, final RepositoryNode originalNode,
+ final RepositoryNode resultingNode) {
+ // Ignore naming policy if node has working copy aspect
+ if (resultingNode.getAspects().contains(ContentModel.ASPECT_WORKING_COPY)) {
+ String name = originalNode.getProperty(ContentModel.PROP_NAME, String.class);
+ resultingNode.getProperties().put(ContentModel.PROP_NAME, name);
+ }
+ // Update node (ignore node name for now) if filer made changes
+ RepositoryNodeDifference originalDifference = updateFileable(originalNode, resultingNode);
+ if (!originalDifference.isEmpty()) {
+ LOGGER.debug("Node updated: " + originalDifference);
+ }
+ // Move and rename node
+ moveAndRenameFileable(originalNode, resultingNode);
+ // Update property inheritance on children
+ RepositoryNodeDifference initialDifference = new RepositoryNodeDifference(initialNode, resultingNode);
+ PropertyInheritancePayload inheritance = propertyInheritanceService.getPayload(initialDifference);
+ propertyInheritanceService.setInheritance(resultingNode.getNodeRef(), inheritance);
+ if (!inheritance.isEmpty()) {
+ LOGGER.debug("Node inheritance updated: " + inheritance);
+ }
+ }
+
+ private void moveAndRenameFileable(final RepositoryNode originalNode, final RepositoryNode resultingNode) {
+ NodeRef nodeRef = resultingNode.getNodeRef();
+ String name = resultingNode.getName().get();
+ boolean nameChanged = !name.equals(originalNode.getName().get());
+ // Move node if parent or name changed
+ if (!resultingNode.getParent().equals(originalNode.getParent()) || nameChanged) {
+ // We can't set the node's name to the new name at the same time as the move.
+ // To avoid incorrect violations of the name constraints, the cm:name is set to something random and will be reset
+ // to the correct name later.
+ String tempName = GUID.generate();
+ nodeService.setProperty(nodeRef, ContentModel.PROP_NAME, tempName);
+ AssociationCopyInfo targetInfo = getAssociationCopyInfo(nodeService, nodeRef, originalNode.getParent(), name, nameChanged);
+ QName typeQName = targetInfo.getSourceParentAssoc().getTypeQName();
+ nodeService.moveNode(nodeRef, resultingNode.getParent(), typeQName, targetInfo.getTargetAssocQName());
+ nodeService.setProperty(nodeRef, ContentModel.PROP_NAME, name);
+ }
+ }
+
+ private RepositoryNodeDifference updateFileable(final RepositoryNode originalNode, final RepositoryNode resultingNode) {
+ NodeRef nodeRef = resultingNode.getNodeRef();
+ RepositoryNodeDifference difference = new RepositoryNodeDifference(originalNode, resultingNode);
+ // Update properties
+ for (QName property : difference.getPropertiesToRemove()) {
+ nodeService.removeProperty(nodeRef, property);
+ }
+ // Do not update name, it will be updated after move
+ Map propertiesToAdd = new HashMap<>(difference.getPropertiesToAdd());
+ propertiesToAdd.remove(ContentModel.PROP_NAME);
+ nodeService.addProperties(nodeRef, propertiesToAdd);
+ // Update aspects when properties are already set
+ for (QName aspect : difference.getAspectsToRemove()) {
+ nodeService.removeAspect(nodeRef, aspect);
+ }
+ for (QName aspect : difference.getAspectsToAdd()) {
+ nodeService.addAspect(nodeRef, aspect, null);
+ }
+ // Update type at the end, so that mandatory aspects and properties are set
+ if (difference.getTypeToSet().isPresent()) {
+ nodeService.setType(nodeRef, difference.getTypeToSet().get());
+ }
+ return difference;
+ }
+
+ public void setFilerModelService(final FilerModelService filerModelService) {
+ this.filerModelService = filerModelService;
+ }
+
+ public void setPropertyInheritanceService(final PropertyInheritanceService propertyInheritanceService) {
+ this.propertyInheritanceService = propertyInheritanceService;
+ }
+
+ public void setNodeService(final NodeService nodeService) {
+ this.nodeService = nodeService;
+ }
+}
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/PropertyInheritanceServiceImpl.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/PropertyInheritanceServiceImpl.java
new file mode 100644
index 0000000..ceba83b
--- /dev/null
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/PropertyInheritanceServiceImpl.java
@@ -0,0 +1,214 @@
+package com.atolcd.alfresco.filer.core.service.impl;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import org.alfresco.model.ContentModel;
+import org.alfresco.repo.security.authentication.AuthenticationUtil;
+import org.alfresco.service.cmr.dictionary.DictionaryService;
+import org.alfresco.service.cmr.dictionary.PropertyDefinition;
+import org.alfresco.service.cmr.repository.ChildAssociationRef;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.repository.NodeService;
+import org.alfresco.service.namespace.QName;
+import org.alfresco.service.namespace.RegexQNamePattern;
+import org.springframework.beans.factory.InitializingBean;
+
+import com.atolcd.alfresco.filer.core.model.FilerException;
+import com.atolcd.alfresco.filer.core.model.PropertyInheritance;
+import com.atolcd.alfresco.filer.core.model.PropertyInheritancePayload;
+import com.atolcd.alfresco.filer.core.model.RepositoryNode;
+import com.atolcd.alfresco.filer.core.model.RepositoryNodeDifference;
+import com.atolcd.alfresco.filer.core.service.FilerModelService;
+import com.atolcd.alfresco.filer.core.service.PropertyInheritanceService;
+
+public class PropertyInheritanceServiceImpl implements PropertyInheritanceService, InitializingBean {
+
+ private FilerModelService filerModelService;
+ private NodeService nodeService;
+ private DictionaryService dictionaryService;
+
+ private Collection inheritedAspects;
+ private Map inheritedProperties;
+
+ @Override
+ public void afterPropertiesSet() {
+ Objects.requireNonNull(filerModelService);
+ Objects.requireNonNull(nodeService);
+ Objects.requireNonNull(dictionaryService);
+ inheritedAspects = dictionaryService.getSubAspects(filerModelService.getPropertyInheritanceAspect(), true);
+ inheritedProperties = getProperties(inheritedAspects);
+ }
+
+ private Map getProperties(final Collection aspects) {
+ Map result = new HashMap<>();
+ for (QName aspect : aspects) {
+ Map properties = dictionaryService.getAspect(aspect).getProperties().keySet().stream()
+ .collect(Collectors.toMap(Function.identity(), prop -> aspect));
+ result.putAll(properties);
+ }
+ return result;
+ }
+
+ @Override
+ public void computeAspectsAndProperties(final NodeRef nodeRef, final RepositoryNode result) {
+ Set aspects = nodeService.getAspects(nodeRef);
+ Map properties = nodeService.getProperties(nodeRef);
+ // Get inherited aspects and properties
+ Set inheritanceAspects = retainAspects(aspects, inheritedAspects);
+ Map inheritanceProperties = retainProperties(properties, inheritedProperties.keySet());
+ // Get unset properties from inheritance aspects for removal on the resulting node
+ List unknownProperties = inheritanceProperties.keySet().stream()
+ .map(property -> inheritedProperties.get(property)).distinct()
+ .flatMap(aspect -> dictionaryService.getAspect(aspect).getProperties().keySet().stream())
+ .filter(property -> !inheritanceProperties.containsKey(property))
+ .collect(Collectors.toList());
+ // Update resulting node aspects and properties
+ result.getAspects().addAll(inheritanceAspects);
+ result.getProperties().putAll(inheritanceProperties);
+ result.getProperties().keySet().removeAll(unknownProperties);
+ }
+
+ private static Set retainAspects(final Set from, final Collection without) {
+ // Create a new Set
+ Set result = new HashSet<>(from);
+ result.retainAll(without);
+ return result;
+ }
+
+ private static Map retainProperties(final Map properties,
+ final Set matchingProperties) {
+ // Create a new Map and retain only matching properties (property value can be null)
+ Map result = new HashMap<>(properties);
+ result.keySet().removeIf(property -> !matchingProperties.contains(property));
+ return result;
+ }
+
+ @Override
+ public void setProperties(final NodeRef nodeRef, final RepositoryNode payload, final PropertyInheritance inheritance) {
+ Set aspects = new HashSet<>();
+ aspects.addAll(inheritance.getMandatoryAspects());
+ // Retrieve all properties from aspects
+ Map propertyDefinitions = getPropertyDefinitions(aspects);
+ // Check for mandatory properties in mandatory aspects that do not have a value
+ Set unknownMandatoryProperties = propertyDefinitions.entrySet().stream()
+ .filter(property -> !payload.getProperties().containsKey(property.getKey()))
+ .filter(property -> property.getValue().isMandatory())
+ .map(Entry::getKey).collect(Collectors.toSet());
+ if (!unknownMandatoryProperties.isEmpty()) {
+ throw new FilerException("Unknown mandatory property value for: " + unknownMandatoryProperties);
+ }
+ aspects.addAll(inheritance.getOptionalAspects());
+ // Apply mandatory and optional aspects
+ Set properties = getPropertyDefinitions(aspects).keySet();
+ // Save inherited properties to repository (property value can be null)
+ Map propertyValues = new HashMap<>();
+ propertyValues.putAll(payload.getProperties());
+ propertyValues.keySet().removeIf(property -> !properties.contains(property));
+ nodeService.addProperties(nodeRef, propertyValues);
+ }
+
+ private Map getPropertyDefinitions(final Collection aspects) {
+ return aspects.stream()
+ .map(aspect -> dictionaryService.getAspect(aspect).getProperties())
+ .flatMap(m -> m.entrySet().stream()).collect(Collectors.toMap(Entry::getKey, Entry::getValue));
+ }
+
+ @Override
+ public PropertyInheritancePayload getPayload(final RepositoryNodeDifference difference) {
+ Set aspectsToAdd = new HashSet<>(difference.getAspectsToAdd());
+ aspectsToAdd.retainAll(inheritedAspects);
+ Map> added = aspectsToAdd.stream()
+ .collect(Collectors.toMap(Function.identity(), v -> new HashMap<>()));
+ for (Entry property : difference.getPropertiesToAdd().entrySet()) {
+ QName aspect = inheritedProperties.get(property.getKey());
+ if (aspect != null) {
+ added.computeIfAbsent(aspect, k -> new HashMap<>()) // NOPMD - default instantiation required
+ .put(property.getKey(), property.getValue());
+ }
+ }
+ Set aspectsToRemove = new HashSet<>(difference.getAspectsToRemove());
+ aspectsToRemove.retainAll(inheritedAspects);
+ Map> removed = aspectsToRemove.stream()
+ .collect(Collectors.toMap(Function.identity(), v -> new HashSet<>()));
+ for (QName property : difference.getPropertiesToRemove()) {
+ QName aspect = inheritedProperties.get(property);
+ // If aspect is already marked for removal, ignore associated property
+ if (aspect != null && !removed.containsKey(aspect)) {
+ removed.computeIfAbsent(aspect, k -> new HashSet<>()) // NOPMD - default instantiation required
+ .add(property);
+ }
+ }
+ return new PropertyInheritancePayload(added, removed);
+ }
+
+ @Override
+ public void setInheritance(final NodeRef root, final PropertyInheritancePayload payload) {
+ if (!payload.isEmpty()) {
+ // Run as System because current user may not have the permission to see all nodes nor to edit nodes properties
+ AuthenticationUtil.runAsSystem(() -> {
+ setInheritanceImpl(root, payload);
+ return null;
+ });
+ }
+ }
+
+ private void setInheritanceImpl(final NodeRef parent, final PropertyInheritancePayload payload) {
+ List children = nodeService.getChildAssocs(parent, ContentModel.ASSOC_CONTAINS,
+ RegexQNamePattern.MATCH_ALL);
+ for (ChildAssociationRef child : children) {
+ NodeRef nodeRef = child.getChildRef();
+ Set aspects = nodeService.getAspects(nodeRef);
+ boolean cascadeChildren = true;
+ if (aspects.contains(filerModelService.getFileableAspect())) {
+ filerModelService.runWithoutFileableBehaviour(nodeRef, () -> {
+ updateInheritance(nodeRef, payload);
+ });
+ } else if (aspects.contains(filerModelService.getSegmentAspect())) {
+ updateInheritance(nodeRef, payload);
+ } else {
+ cascadeChildren = false;
+ }
+ if (cascadeChildren) {
+ setInheritanceImpl(nodeRef, payload);
+ }
+ }
+ }
+
+ private void updateInheritance(final NodeRef nodeRef, final PropertyInheritancePayload payload) {
+ // Add aspects and associated properties
+ for (Entry> aspect : payload.getAdded().entrySet()) {
+ nodeService.addAspect(nodeRef, aspect.getKey(), aspect.getValue());
+ }
+ for (Entry> aspect : payload.getRemoved().entrySet()) {
+ if (aspect.getValue().isEmpty()) {
+ // Remove aspect
+ nodeService.removeAspect(nodeRef, aspect.getKey());
+ } else {
+ // Remove properties
+ aspect.getValue().stream().forEach(property -> nodeService.removeProperty(nodeRef, property));
+ }
+ }
+ }
+
+ public void setFilerModelService(final FilerModelService filerModelService) {
+ this.filerModelService = filerModelService;
+ }
+
+ public void setNodeService(final NodeService nodeService) {
+ this.nodeService = nodeService;
+ }
+
+ public void setDictionaryService(final DictionaryService dictionaryService) {
+ this.dictionaryService = dictionaryService;
+ }
+}
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/util/FilerNodeUtils.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/util/FilerNodeUtils.java
new file mode 100644
index 0000000..856af1f
--- /dev/null
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/util/FilerNodeUtils.java
@@ -0,0 +1,61 @@
+package com.atolcd.alfresco.filer.core.util;
+
+import java.util.Objects;
+import java.util.Optional;
+
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.site.SiteInfo;
+
+import com.atolcd.alfresco.filer.core.model.RepositoryNode;
+
+public final class FilerNodeUtils {
+
+ private static final String DISPLAY_PATH_KEY = "displayPath";
+ private static final String SITE_INFO_KEY = "siteInfo";
+ private static final String ORIGINAL_KEY = "original";
+ private static final String ORIGINAL_NODE_KEY = "originalNode";
+
+ public static Optional getSiteInfo(final RepositoryNode node) {
+ return Optional.ofNullable(node.getExtension(SITE_INFO_KEY, SiteInfo.class));
+ }
+
+ public static void setSiteInfo(final RepositoryNode node, final SiteInfo siteInfo) {
+ if (siteInfo != null) {
+ node.getExtensions().put(SITE_INFO_KEY, siteInfo);
+ }
+ }
+
+ public static NodeRef getSiteNodeRef(final RepositoryNode node) {
+ return getSiteInfo(node).get().getNodeRef();
+ }
+
+ public static Boolean isOriginal(final RepositoryNode node) {
+ return Optional.ofNullable(node.getExtension(ORIGINAL_KEY, Boolean.class)).orElse(Boolean.FALSE);
+ }
+
+ public static void setOriginal(final RepositoryNode node, final Boolean original) {
+ if (Boolean.TRUE.equals(original)) {
+ node.getExtensions().put(ORIGINAL_KEY, original);
+ }
+ }
+
+ public static RepositoryNode getOriginalNode(final RepositoryNode node) {
+ RepositoryNode originalNode = node.getExtension(ORIGINAL_NODE_KEY, RepositoryNode.class);
+ Objects.requireNonNull(originalNode);
+ return originalNode;
+ }
+
+ public static void setOriginalNode(final RepositoryNode node, final RepositoryNode originalNode) {
+ node.getExtensions().put(ORIGINAL_NODE_KEY, originalNode);
+ }
+
+ public static String getDisplayPath(final RepositoryNode node) {
+ return Optional.ofNullable(node.getExtension(DISPLAY_PATH_KEY, String.class)).orElse("");
+ }
+
+ public static void setDisplayPath(final RepositoryNode node, final String path) {
+ node.getExtensions().put(DISPLAY_PATH_KEY, path);
+ }
+
+ private FilerNodeUtils() {}
+}
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/util/FilerTransactionUtils.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/util/FilerTransactionUtils.java
new file mode 100644
index 0000000..0f1245d
--- /dev/null
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/util/FilerTransactionUtils.java
@@ -0,0 +1,58 @@
+package com.atolcd.alfresco.filer.core.util;
+
+import java.util.Map;
+import java.util.Optional;
+
+import org.alfresco.repo.transaction.TransactionalResourceHelper;
+import org.alfresco.service.cmr.repository.NodeRef;
+
+import com.atolcd.alfresco.filer.core.model.FilerEvent;
+import com.atolcd.alfresco.filer.core.model.RepositoryNode;
+import com.atolcd.alfresco.filer.core.policy.FilerSubscriberAspect;
+import com.atolcd.alfresco.filer.core.service.FilerService;
+import com.atolcd.alfresco.filer.core.service.FilerUpdateService;
+
+public final class FilerTransactionUtils {
+
+ private static final Class> TRANSACTION_EVENT_NODE_KEY = FilerService.class;
+ private static final Class> TRANSACTION_INITIAL_NODE_KEY = FilerUpdateService.class;
+ private static final Class> TRANSACTION_DELETED_ASSOC_KEY = FilerSubscriberAspect.class;
+
+ public static Optional getEventNode(final NodeRef nodeRef) {
+ return Optional.ofNullable(getEventNodeMap().get(nodeRef));
+ }
+
+ public static void putEventNode(final NodeRef nodeRef, final FilerEvent event) {
+ getEventNodeMap().put(nodeRef, event);
+ }
+
+ private static Map getEventNodeMap() {
+ return TransactionalResourceHelper.getMap(TRANSACTION_EVENT_NODE_KEY);
+ }
+
+ public static RepositoryNode getInitialNode(final NodeRef nodeRef) {
+ return Optional.ofNullable(getInitialNodeMap().get(nodeRef)).orElse(new RepositoryNode(nodeRef));
+ }
+
+ public static void putInitialNode(final NodeRef nodeRef, final RepositoryNode node) {
+ getInitialNodeMap().put(nodeRef, node);
+ }
+
+ private static Map getInitialNodeMap() {
+ return TransactionalResourceHelper.getMap(TRANSACTION_INITIAL_NODE_KEY);
+ }
+
+ public static Optional getDeletedAssoc(final NodeRef childRef) {
+ return Optional.ofNullable(getDeletedAssocMap().get(childRef));
+ }
+
+ public static void putDeletedAssoc(final NodeRef childRef, final NodeRef parentRef) {
+ getDeletedAssocMap().put(childRef, parentRef);
+ }
+
+ private static Map getDeletedAssocMap() {
+ return TransactionalResourceHelper.getMap(TRANSACTION_DELETED_ASSOC_KEY);
+ }
+
+ private FilerTransactionUtils() {}
+}
From d7057ac204a9cff4ff1017717798a1d25e548ea4 Mon Sep 17 00:00:00 2001
From: Corentin PIANA
Date: Tue, 6 Aug 2019 11:08:38 +0200
Subject: [PATCH 03/58] Fix: escaping braces within MessageFormat usage
To prevent unwanted expansion, braces need to be quoted with single
quotes within String formatted with MessageFormat.
Change-Id: I852eecfcf70cf67d3452fd76316be269ddc7761d
---
.../com/atolcd/alfresco/filer/core/model/InboundFilerEvent.java | 2 +-
.../atolcd/alfresco/filer/core/model/PropertyInheritance.java | 2 +-
.../alfresco/filer/core/model/PropertyInheritancePayload.java | 2 +-
.../com/atolcd/alfresco/filer/core/model/RepositoryNode.java | 2 +-
.../com/atolcd/alfresco/filer/core/model/UpdateFilerEvent.java | 2 +-
5 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/InboundFilerEvent.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/InboundFilerEvent.java
index 3b43cac..6b02035 100644
--- a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/InboundFilerEvent.java
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/InboundFilerEvent.java
@@ -16,7 +16,7 @@ public InboundFilerEvent(final NodeRef nodeRef, final boolean original) {
@Override
public String toString() {
- return MessageFormat.format("Inbound{action={0}, name={1}, node={2}, store={3}}",
+ return MessageFormat.format("Inbound'{'action={0}, name={1}, node={2}, store={3}'}'",
getAction().orElse(null),
getNode().getName().orElse(null),
getNode().getNodeRef().getId(),
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/PropertyInheritance.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/PropertyInheritance.java
index 115d5e2..3e4226f 100644
--- a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/PropertyInheritance.java
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/PropertyInheritance.java
@@ -28,7 +28,7 @@ public PropertyInheritance(final PropertyInheritance inheritance) {
@Override
public String toString() {
- return MessageFormat.format("{mandatory={0}, optional={1}}", mandatoryAspects, optionalAspects);
+ return MessageFormat.format("'{'mandatory={0}, optional={1}'}'", mandatoryAspects, optionalAspects);
}
public Set getMandatoryAspects() {
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/PropertyInheritancePayload.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/PropertyInheritancePayload.java
index 06f4a83..6681693 100644
--- a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/PropertyInheritancePayload.java
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/PropertyInheritancePayload.java
@@ -20,7 +20,7 @@ public PropertyInheritancePayload(final Map> add
@Override
public String toString() {
- return MessageFormat.format("{added={0}, removed={1}}", added, removed);
+ return MessageFormat.format("'{'added={0}, removed={1}'}'", added, removed);
}
public boolean isEmpty() {
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/RepositoryNode.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/RepositoryNode.java
index 86b78e5..21260dc 100644
--- a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/RepositoryNode.java
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/RepositoryNode.java
@@ -130,7 +130,7 @@ public int hashCode() {
@Override
public String toString() {
- return MessageFormat.format("{0}{type={1}, properties={2}, aspects={3}, parent={4}, nodeRef={5}}",
+ return MessageFormat.format("{0}'{'type={1}, properties={2}, aspects={3}, parent={4}, nodeRef={5}'}'",
getName().orElse(null), type, properties, aspects, parent, nodeRef);
}
}
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/UpdateFilerEvent.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/UpdateFilerEvent.java
index f1e83b2..4eee80e 100644
--- a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/UpdateFilerEvent.java
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/UpdateFilerEvent.java
@@ -19,7 +19,7 @@ public UpdateFilerEvent(final NodeRef nodeRef, final Map af
@Override
public String toString() {
- return MessageFormat.format("Update{action={0}, name={1}, node={2}, store={3}}",
+ return MessageFormat.format("Update'{'action={0}, name={1}, node={2}, store={3}'}'",
getAction().orElse(null),
getNode().getName().orElse(null),
getNode().getNodeRef().getId(),
From 56cba2bb321a42448a5dcb2266cb929297d9c17f Mon Sep 17 00:00:00 2001
From: Corentin PIANA
Date: Tue, 27 Aug 2019 12:28:03 +0200
Subject: [PATCH 04/58] Jenkins: Change number of jobs to retain
LogRotator will now keep the 20 most recents jobs instead of 10 before.
Change-Id: I1a3085b905f87dcc297af3cef27183c56cbc2d7f
---
Jenkinsfile | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Jenkinsfile b/Jenkinsfile
index f29a8c8..2bf22d2 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -13,8 +13,8 @@ pipeline {
pollSCM ''
}
options {
- // LogRotator will keep the 10 most recents jobs and retain the artifacts only for the 2 latest
- buildDiscarder(logRotator(numToKeepStr: '10', artifactNumToKeepStr: '2'))
+ // LogRotator will keep the 20 most recents jobs and retain the artifacts only for the 2 latest
+ buildDiscarder(logRotator(numToKeepStr: '20', artifactNumToKeepStr: '2'))
}
parameters {
string(name: 'MAVEN_OPTIONS', defaultValue: '', description: 'Optional parameters to be added to the mvn command line')
From e3937bcfabb69dd458f753fff782afe5e1933520 Mon Sep 17 00:00:00 2001
From: Corentin PIANA
Date: Wed, 14 Aug 2019 18:09:21 +0200
Subject: [PATCH 05/58] Fix: wait for complete model before getting props
When include in other project, the filer module content model is loaded
before the model of the project. Thus, on initialization, the filer
module could not fetch project aspects inheriting of filer
propertyInheritance aspect. This result in disabling property
inheritance.
Classes that need to fetch information from the complete content model
register themselves as DictionaryListener and process after the
dictionary initalization.
Change-Id: Idf2ef00bcf70fe57bafd71433a3d1a75d9ec1e2c
---
.../main/config/context/service-context.xml | 3 +-
.../impl/PropertyInheritanceServiceImpl.java | 32 ++++++++++++++++---
2 files changed, 29 insertions(+), 6 deletions(-)
diff --git a/alfresco-filer-core/src/main/config/context/service-context.xml b/alfresco-filer-core/src/main/config/context/service-context.xml
index 887e6ce..0d44ef3 100644
--- a/alfresco-filer-core/src/main/config/context/service-context.xml
+++ b/alfresco-filer-core/src/main/config/context/service-context.xml
@@ -48,11 +48,12 @@
-
+
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/PropertyInheritanceServiceImpl.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/PropertyInheritanceServiceImpl.java
index ceba83b..c70add2 100644
--- a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/PropertyInheritanceServiceImpl.java
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/PropertyInheritanceServiceImpl.java
@@ -4,7 +4,6 @@
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
@@ -13,6 +12,8 @@
import java.util.stream.Collectors;
import org.alfresco.model.ContentModel;
+import org.alfresco.repo.dictionary.DictionaryDAO;
+import org.alfresco.repo.dictionary.DictionaryListener;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.dictionary.PropertyDefinition;
@@ -31,11 +32,12 @@
import com.atolcd.alfresco.filer.core.service.FilerModelService;
import com.atolcd.alfresco.filer.core.service.PropertyInheritanceService;
-public class PropertyInheritanceServiceImpl implements PropertyInheritanceService, InitializingBean {
+public class PropertyInheritanceServiceImpl implements PropertyInheritanceService, InitializingBean, DictionaryListener {
private FilerModelService filerModelService;
private NodeService nodeService;
private DictionaryService dictionaryService;
+ private DictionaryDAO dictionaryDAO;
private Collection inheritedAspects;
private Map inheritedProperties;
@@ -45,6 +47,12 @@ public void afterPropertiesSet() {
Objects.requireNonNull(filerModelService);
Objects.requireNonNull(nodeService);
Objects.requireNonNull(dictionaryService);
+ Objects.requireNonNull(dictionaryDAO);
+ dictionaryDAO.registerListener(this);
+ }
+
+ @Override
+ public void afterDictionaryInit() {
inheritedAspects = dictionaryService.getSubAspects(filerModelService.getPropertyInheritanceAspect(), true);
inheritedProperties = getProperties(inheritedAspects);
}
@@ -67,11 +75,11 @@ public void computeAspectsAndProperties(final NodeRef nodeRef, final RepositoryN
Set inheritanceAspects = retainAspects(aspects, inheritedAspects);
Map inheritanceProperties = retainProperties(properties, inheritedProperties.keySet());
// Get unset properties from inheritance aspects for removal on the resulting node
- List unknownProperties = inheritanceProperties.keySet().stream()
+ Set unknownProperties = inheritanceProperties.keySet().stream()
.map(property -> inheritedProperties.get(property)).distinct()
.flatMap(aspect -> dictionaryService.getAspect(aspect).getProperties().keySet().stream())
.filter(property -> !inheritanceProperties.containsKey(property))
- .collect(Collectors.toList());
+ .collect(Collectors.toSet());
// Update resulting node aspects and properties
result.getAspects().addAll(inheritanceAspects);
result.getProperties().putAll(inheritanceProperties);
@@ -163,7 +171,7 @@ public void setInheritance(final NodeRef root, final PropertyInheritancePayload
}
private void setInheritanceImpl(final NodeRef parent, final PropertyInheritancePayload payload) {
- List children = nodeService.getChildAssocs(parent, ContentModel.ASSOC_CONTAINS,
+ Collection children = nodeService.getChildAssocs(parent, ContentModel.ASSOC_CONTAINS,
RegexQNamePattern.MATCH_ALL);
for (ChildAssociationRef child : children) {
NodeRef nodeRef = child.getChildRef();
@@ -200,6 +208,16 @@ private void updateInheritance(final NodeRef nodeRef, final PropertyInheritanceP
}
}
+ @Override
+ public void onDictionaryInit() { // NOPMD - default empty method, nothing to do on dictionary initialization
+ // no op
+ }
+
+ @Override
+ public void afterDictionaryDestroy() { // NOPMD - default empty method, nothing to do after dictionary deletion
+ // no op
+ }
+
public void setFilerModelService(final FilerModelService filerModelService) {
this.filerModelService = filerModelService;
}
@@ -211,4 +229,8 @@ public void setNodeService(final NodeService nodeService) {
public void setDictionaryService(final DictionaryService dictionaryService) {
this.dictionaryService = dictionaryService;
}
+
+ public void setDictionaryDAO(final DictionaryDAO dictionaryDAO) {
+ this.dictionaryDAO = dictionaryDAO;
+ }
}
From cbc99efe93e3d56c9cb31c17417cc9c107cc1631 Mon Sep 17 00:00:00 2001
From: Corentin PIANA
Date: Mon, 26 Aug 2019 17:14:56 +0200
Subject: [PATCH 06/58] Interface for application context aware tests
Test classes that need to load the application context can implement the
interface.
It load all the application context and test context files.
All new test context files must be added to the interface's context
configuration. This is to ensure that each test share the same context,
thus loading the context once for all tests.
Change-Id: Ie266930250b516782ae73908e7e3aea7916197b4
---
.../filer/core/test/content/ContextStartupTest.java | 12 ++----------
.../core/test/util/ApplicationContextAwareTest.java | 12 ++++++++++++
2 files changed, 14 insertions(+), 10 deletions(-)
create mode 100644 alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/util/ApplicationContextAwareTest.java
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/content/ContextStartupTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/content/ContextStartupTest.java
index 72acf96..6a25563 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/content/ContextStartupTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/content/ContextStartupTest.java
@@ -12,23 +12,15 @@
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
-import org.springframework.test.context.ContextConfiguration;
-import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronizationManager;
-import com.atolcd.alfresco.filer.core.test.util.PostgreSQLExtension;
+import com.atolcd.alfresco.filer.core.test.util.ApplicationContextAwareTest;
-@ExtendWith(PostgreSQLExtension.class)
-@ExtendWith(SpringExtension.class)
-@ContextConfiguration({
- "classpath:alfresco/application-context.xml"
-})
@Transactional
-public class ContextStartupTest {
+public class ContextStartupTest implements ApplicationContextAwareTest {
@Autowired
@Qualifier("NodeService")
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/util/ApplicationContextAwareTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/util/ApplicationContextAwareTest.java
new file mode 100644
index 0000000..74b6ee2
--- /dev/null
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/util/ApplicationContextAwareTest.java
@@ -0,0 +1,12 @@
+package com.atolcd.alfresco.filer.core.test.util;
+
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+@ExtendWith(PostgreSQLExtension.class)
+@ExtendWith(SpringExtension.class)
+@ContextConfiguration({
+ "classpath:alfresco/application-context.xml"
+})
+public interface ApplicationContextAwareTest {}
From 621f7a4f0e553ff74f967277d51b29bb3d23b394 Mon Sep 17 00:00:00 2001
From: Corentin PIANA
Date: Mon, 26 Aug 2019 17:21:03 +0200
Subject: [PATCH 07/58] Test model & filer actions, plus base test classes
Complete model and filer actions to create a small but complete
implementation of the filer module to perform integration testing.
Base test classes for transactionnal node manipulation and for site
based tests.
Change-Id: Ia94ca295b8703a41eba0d2e4e4d35e815b7a1a90
---
.../DepartmentContentFilerActionTest.java | 29 ++++++
.../action/AbstractFilerTestAction.java | 17 ++++
.../test/domain/action/DeniedFilerAction.java | 28 ++++++
.../action/DepartmentContentFilerAction.java | 29 ++++++
.../action/DepartmentFolderFilerAction.java | 24 +++++
.../content/model/FilerTestConstants.java | 48 +++++++++
.../service/FilerTestActionService.java | 12 +++
.../impl/FilerTestActionServiceImpl.java | 40 ++++++++
.../util/ApplicationContextAwareTest.java | 4 +-
.../filer/core/test/util/SiteBasedTest.java | 98 +++++++++++++++++++
.../test/util/TransactionalBasedTest.java | 93 ++++++++++++++++++
.../resources/context/test-action-context.xml | 28 ++++++
.../resources/context/test-model-context.xml | 14 +++
.../test/resources/model/filerTestModel.xml | 80 +++++++++++++++
14 files changed, 543 insertions(+), 1 deletion(-)
create mode 100644 alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DepartmentContentFilerActionTest.java
create mode 100644 alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/action/AbstractFilerTestAction.java
create mode 100644 alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/action/DeniedFilerAction.java
create mode 100644 alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/action/DepartmentContentFilerAction.java
create mode 100644 alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/action/DepartmentFolderFilerAction.java
create mode 100644 alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/content/model/FilerTestConstants.java
create mode 100644 alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/service/FilerTestActionService.java
create mode 100644 alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/service/impl/FilerTestActionServiceImpl.java
create mode 100644 alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/util/SiteBasedTest.java
create mode 100644 alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/util/TransactionalBasedTest.java
create mode 100644 alfresco-filer-core/src/test/resources/context/test-action-context.xml
create mode 100644 alfresco-filer-core/src/test/resources/context/test-model-context.xml
create mode 100644 alfresco-filer-core/src/test/resources/model/filerTestModel.xml
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DepartmentContentFilerActionTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DepartmentContentFilerActionTest.java
new file mode 100644
index 0000000..67f3cae
--- /dev/null
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DepartmentContentFilerActionTest.java
@@ -0,0 +1,29 @@
+package com.atolcd.alfresco.filer.core.test.domain;
+
+import static java.util.UUID.randomUUID;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.time.LocalDateTime;
+
+import org.junit.jupiter.api.Test;
+
+import com.atolcd.alfresco.filer.core.model.RepositoryNode;
+import com.atolcd.alfresco.filer.core.test.domain.content.model.FilerTestConstants;
+import com.atolcd.alfresco.filer.core.test.util.SiteBasedTest;
+
+public class DepartmentContentFilerActionTest extends SiteBasedTest {
+
+ @Test
+ public void departmentDocumentWithoutImportDate() {
+ String departmentName = randomUUID().toString();
+
+ RepositoryNode node = buildNode()
+ .type(FilerTestConstants.Department.DocumentType.NAME)
+ .property(FilerTestConstants.Department.Aspect.PROP_NAME, departmentName)
+ .build();
+
+ createNode(node);
+
+ assertThat(getPath(node)).isEqualTo(buildNodePath(departmentName, LocalDateTime.now()));
+ }
+}
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/action/AbstractFilerTestAction.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/action/AbstractFilerTestAction.java
new file mode 100644
index 0000000..0b1db91
--- /dev/null
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/action/AbstractFilerTestAction.java
@@ -0,0 +1,17 @@
+package com.atolcd.alfresco.filer.core.test.domain.action;
+
+import com.atolcd.alfresco.filer.core.model.impl.AbstractFilerAction;
+import com.atolcd.alfresco.filer.core.test.domain.service.FilerTestActionService;
+
+public abstract class AbstractFilerTestAction extends AbstractFilerAction {
+
+ private FilerTestActionService actionService;
+
+ protected FilerTestActionService actions() {
+ return actionService;
+ }
+
+ public void setActionService(final FilerTestActionService actionService) {
+ this.actionService = actionService;
+ }
+}
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/action/DeniedFilerAction.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/action/DeniedFilerAction.java
new file mode 100644
index 0000000..4e20aa5
--- /dev/null
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/action/DeniedFilerAction.java
@@ -0,0 +1,28 @@
+package com.atolcd.alfresco.filer.core.test.domain.action;
+
+import com.atolcd.alfresco.filer.core.model.FilerEvent;
+import com.atolcd.alfresco.filer.core.model.RepositoryNode;
+import com.atolcd.alfresco.filer.core.service.impl.FilerBuilder;
+
+public class DeniedFilerAction extends AbstractFilerTestAction {
+
+ @Override
+ public boolean supportsActionResolution(final FilerEvent event) {
+ return true;
+ }
+
+ @Override
+ public boolean supportsActionExecution(final RepositoryNode node) {
+ return true;
+ }
+
+ @Override
+ public int getOrder() {
+ return LOWEST_PRECEDENCE;
+ }
+
+ @Override
+ protected void execute(final FilerBuilder builder) {
+ deny(builder, node -> true);
+ }
+}
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/action/DepartmentContentFilerAction.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/action/DepartmentContentFilerAction.java
new file mode 100644
index 0000000..689fd0a
--- /dev/null
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/action/DepartmentContentFilerAction.java
@@ -0,0 +1,29 @@
+package com.atolcd.alfresco.filer.core.test.domain.action;
+
+import java.util.Arrays;
+
+import com.atolcd.alfresco.filer.core.model.FilerEvent;
+import com.atolcd.alfresco.filer.core.model.RepositoryNode;
+import com.atolcd.alfresco.filer.core.service.impl.FilerBuilder;
+import com.atolcd.alfresco.filer.core.test.domain.content.model.FilerTestConstants;
+
+public class DepartmentContentFilerAction extends AbstractFilerTestAction {
+
+ @Override
+ public boolean supportsActionResolution(final FilerEvent event) {
+ return event.getNode().getAspects().contains(FilerTestConstants.Department.Aspect.NAME)
+ && Arrays.asList(FilerTestConstants.Department.DocumentType.NAME, FilerTestConstants.SpecialDocumentType.NAME)
+ .contains(event.getNode().getType());
+ }
+
+ @Override
+ public boolean supportsActionExecution(final RepositoryNode node) {
+ return true;
+ }
+
+ @Override
+ protected void execute(final FilerBuilder builder) {
+ builder.with(actions()::departmentFolder).getOrCreate()
+ .tree(actions()::dateSegmentation).updateAndMove();
+ }
+}
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/action/DepartmentFolderFilerAction.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/action/DepartmentFolderFilerAction.java
new file mode 100644
index 0000000..0d4d62d
--- /dev/null
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/action/DepartmentFolderFilerAction.java
@@ -0,0 +1,24 @@
+package com.atolcd.alfresco.filer.core.test.domain.action;
+
+import com.atolcd.alfresco.filer.core.model.FilerEvent;
+import com.atolcd.alfresco.filer.core.model.RepositoryNode;
+import com.atolcd.alfresco.filer.core.service.impl.FilerBuilder;
+import com.atolcd.alfresco.filer.core.test.domain.content.model.FilerTestConstants;
+
+public class DepartmentFolderFilerAction extends AbstractFilerTestAction {
+
+ @Override
+ public boolean supportsActionResolution(final FilerEvent event) {
+ return event.getNode().getType().equals(FilerTestConstants.Department.FolderType.NAME);
+ }
+
+ @Override
+ public boolean supportsActionExecution(final RepositoryNode node) {
+ return true;
+ }
+
+ @Override
+ protected void execute(final FilerBuilder builder) {
+ builder.with(actions()::departmentFolder).updateAndMove();
+ }
+}
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/content/model/FilerTestConstants.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/content/model/FilerTestConstants.java
new file mode 100644
index 0000000..09e6eb5
--- /dev/null
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/content/model/FilerTestConstants.java
@@ -0,0 +1,48 @@
+package com.atolcd.alfresco.filer.core.test.domain.content.model;
+
+import org.alfresco.service.namespace.QName;
+
+public final class FilerTestConstants {
+
+ public static final String NAMESPACE_URI = "http://www.atolcd.com/model/filer/test/1.0";
+ public static final QName MODEL_NAME = QName.createQName(NAMESPACE_URI, "model");
+
+ public static final class Department {
+
+ public static final class FolderType {
+ public static final QName NAME = QName.createQName(NAMESPACE_URI, "departmentFolder");
+
+ private FolderType() {}
+ }
+
+ public static final class DocumentType {
+ public static final QName NAME = QName.createQName(NAMESPACE_URI, "departmentDocument");
+
+ private DocumentType() {}
+ }
+
+ public static final class Aspect { //NOPMD - name: not a utility class
+ public static final QName NAME = QName.createQName(NAMESPACE_URI, "department");
+ public static final QName PROP_NAME = QName.createQName(NAMESPACE_URI, "departmentName");
+
+ private Aspect() {}
+ }
+
+ private Department() {}
+ }
+
+ public static final class SpecialDocumentType {
+ public static final QName NAME = QName.createQName(NAMESPACE_URI, "specialDocument");
+
+ private SpecialDocumentType() {}
+ }
+
+ public static final class ImportedAspect {
+ public static final QName NAME = QName.createQName(NAMESPACE_URI, "imported");
+ public static final QName PROP_DATE = QName.createQName(NAMESPACE_URI, "importedDate");
+
+ private ImportedAspect() {}
+ }
+
+ private FilerTestConstants() {}
+}
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/service/FilerTestActionService.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/service/FilerTestActionService.java
new file mode 100644
index 0000000..8a5eecc
--- /dev/null
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/service/FilerTestActionService.java
@@ -0,0 +1,12 @@
+package com.atolcd.alfresco.filer.core.test.domain.service;
+
+import com.atolcd.alfresco.filer.core.service.impl.FilerBuilder;
+import com.atolcd.alfresco.filer.core.service.impl.FilerFolderBuilder;
+import com.atolcd.alfresco.filer.core.service.impl.FilerFolderTypeBuilder;
+
+public interface FilerTestActionService {
+
+ FilerFolderTypeBuilder departmentFolder(FilerBuilder builder);
+
+ FilerFolderBuilder dateSegmentation(FilerFolderBuilder builder);
+}
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/service/impl/FilerTestActionServiceImpl.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/service/impl/FilerTestActionServiceImpl.java
new file mode 100644
index 0000000..909f3c2
--- /dev/null
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/service/impl/FilerTestActionServiceImpl.java
@@ -0,0 +1,40 @@
+package com.atolcd.alfresco.filer.core.test.domain.service.impl;
+
+import org.alfresco.model.ContentModel;
+import org.alfresco.service.cmr.site.SiteService;
+
+import com.atolcd.alfresco.filer.core.service.impl.FilerBuilder;
+import com.atolcd.alfresco.filer.core.service.impl.FilerFolderBuilder;
+import com.atolcd.alfresco.filer.core.service.impl.FilerFolderTypeBuilder;
+import com.atolcd.alfresco.filer.core.test.domain.content.model.FilerTestConstants;
+import com.atolcd.alfresco.filer.core.test.domain.service.FilerTestActionService;
+import com.atolcd.alfresco.filer.core.util.FilerNodeUtils;
+
+public class FilerTestActionServiceImpl implements FilerTestActionService {
+
+ @Override
+ public FilerFolderTypeBuilder departmentFolder(final FilerBuilder builder) {
+ return builder.root(FilerNodeUtils::getSiteNodeRef)
+ .folder()
+ .named().with(SiteService.DOCUMENT_LIBRARY).get()
+ .folder(FilerTestConstants.Department.FolderType.NAME)
+ .named().withProperty(FilerTestConstants.Department.Aspect.PROP_NAME);
+ }
+
+ @Override
+ public FilerFolderBuilder dateSegmentation(final FilerFolderBuilder builder) {
+ return builder
+ // Segmentation on the imported aspect date or default to creation date
+ .condition(node -> node.getProperties().containsKey(FilerTestConstants.ImportedAspect.PROP_DATE))
+ .folder().asSegment()
+ .named().withPropertyDate(FilerTestConstants.ImportedAspect.PROP_DATE, "yyyy").getOrCreate()
+ .folder().asSegment()
+ .named().withPropertyDate(FilerTestConstants.ImportedAspect.PROP_DATE, "MM").getOrCreate()
+ .conditionReverse()
+ .folder().asSegment()
+ .named().withPropertyDate(ContentModel.PROP_CREATED, "yyyy").getOrCreate()
+ .folder().asSegment()
+ .named().withPropertyDate(ContentModel.PROP_CREATED, "MM").getOrCreate()
+ .conditionEnd();
+ }
+}
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/util/ApplicationContextAwareTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/util/ApplicationContextAwareTest.java
index 74b6ee2..8824c66 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/util/ApplicationContextAwareTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/util/ApplicationContextAwareTest.java
@@ -7,6 +7,8 @@
@ExtendWith(PostgreSQLExtension.class)
@ExtendWith(SpringExtension.class)
@ContextConfiguration({
- "classpath:alfresco/application-context.xml"
+ "classpath:alfresco/application-context.xml",
+ "classpath:context/test-model-context.xml",
+ "classpath:context/test-action-context.xml"
})
public interface ApplicationContextAwareTest {}
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/util/SiteBasedTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/util/SiteBasedTest.java
new file mode 100644
index 0000000..909c75c
--- /dev/null
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/util/SiteBasedTest.java
@@ -0,0 +1,98 @@
+package com.atolcd.alfresco.filer.core.test.util;
+
+import static java.util.UUID.randomUUID;
+
+import java.nio.file.Paths;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+
+import javax.annotation.PostConstruct;
+
+import org.alfresco.repo.security.authentication.AuthenticationUtil;
+import org.alfresco.repo.site.SiteServiceImpl;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.repository.NodeService;
+import org.alfresco.service.cmr.security.PermissionService;
+import org.alfresco.service.cmr.site.SiteService;
+import org.alfresco.service.cmr.site.SiteVisibility;
+import org.alfresco.service.cmr.tagging.TaggingService;
+import org.alfresco.service.transaction.TransactionService;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import com.atolcd.alfresco.filer.core.model.RepositoryNode;
+import com.atolcd.alfresco.filer.core.model.impl.RepositoryNodeBuilder;
+
+public abstract class SiteBasedTest extends TransactionalBasedTest {
+
+ private static final String NODE_PATH = SiteBasedTest.class + ".NODE_PATH";
+
+ @Autowired
+ private TransactionService transactionService;
+ @Autowired
+ private NodeService nodeService;
+ @Autowired
+ private PermissionService permissionService;
+ @Autowired
+ private SiteService siteService;
+ @Autowired
+ private TaggingService taggingService;
+
+ private NodeRef documentLibrary;
+
+ @PostConstruct
+ private void initSite() {
+ String siteName = getSiteName();
+
+ doInTransaction(() -> {
+ AuthenticationUtil.setAdminUserAsFullyAuthenticatedUser();
+
+ if (!siteService.hasSite(siteName)) {
+ siteService.createSite(siteName, siteName, siteName, siteName, SiteVisibility.PUBLIC);
+ }
+
+ documentLibrary = SiteServiceImpl.getSiteContainer(siteName, SiteService.DOCUMENT_LIBRARY, true, siteService,
+ transactionService, taggingService);
+ });
+ }
+
+ protected String getSiteName() {
+ return randomUUID().toString();
+ }
+
+ @Override
+ protected void fetchNodeImpl(final RepositoryNode node) {
+ super.fetchNodeImpl(node);
+ setPath(node, nodeService.getPath(node.getNodeRef()).toDisplayPath(nodeService, permissionService));
+ }
+
+ protected static String getPath(final RepositoryNode node) {
+ return node.getExtension(NODE_PATH, String.class);
+ }
+
+ private static void setPath(final RepositoryNode node, final String path) {
+ node.getExtensions().put(NODE_PATH, path);
+ }
+
+ protected String buildSitePath() {
+ return Paths
+ .get(nodeService.getPath(getDocumentLibrary()).toDisplayPath(nodeService, permissionService),
+ SiteService.DOCUMENT_LIBRARY)
+ .toString();
+ }
+
+ protected String buildNodePath(final String departmentName, final LocalDateTime date) {
+ return Paths
+ .get(buildSitePath(), departmentName, Integer.toString(date.getYear()), date.format(DateTimeFormatter.ofPattern("MM")))
+ .toString();
+ }
+
+ protected RepositoryNodeBuilder buildNode() {
+ return RepositoryNode.builder()
+ .parent(documentLibrary)
+ .named(randomUUID());
+ }
+
+ protected NodeRef getDocumentLibrary() {
+ return documentLibrary;
+ }
+}
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/util/TransactionalBasedTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/util/TransactionalBasedTest.java
new file mode 100644
index 0000000..b0c5752
--- /dev/null
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/util/TransactionalBasedTest.java
@@ -0,0 +1,93 @@
+package com.atolcd.alfresco.filer.core.test.util;
+
+import java.io.Serializable;
+import java.util.Map;
+
+import org.alfresco.model.ContentModel;
+import org.alfresco.repo.security.authentication.AuthenticationUtil;
+import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.repository.NodeService;
+import org.alfresco.service.namespace.NamespaceService;
+import org.alfresco.service.namespace.QName;
+import org.alfresco.service.transaction.TransactionService;
+import org.alfresco.util.transaction.TransactionListenerAdapter;
+import org.junit.jupiter.api.AfterEach;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import com.atolcd.alfresco.filer.core.model.RepositoryNode;
+
+public class TransactionalBasedTest implements ApplicationContextAwareTest {
+
+ @Autowired
+ private TransactionService transactionService;
+ @Autowired
+ private NodeService nodeService;
+
+ @AfterEach
+ public void clearAuthorization() {
+ AuthenticationUtil.clearCurrentSecurityContext();
+ }
+
+ protected final void createNode(final RepositoryNode node) {
+ doInTransaction(() -> {
+ QName assocQName = QName.createQNameWithValidLocalName(NamespaceService.CONTENT_MODEL_1_0_URI, node.getName().get());
+ NodeRef nodeRef = nodeService
+ .createNode(node.getParent(), ContentModel.ASSOC_CONTAINS, assocQName, node.getType(), node.getProperties())
+ .getChildRef();
+ node.getAspects().forEach(aspect -> nodeService.addAspect(nodeRef, aspect, null));
+
+ node.setNodeRef(nodeRef);
+ bindTransactionListener(node);
+ });
+ }
+
+ protected final void fetchNode(final RepositoryNode node) {
+ doInTransaction(() -> {
+ fetchNodeImpl(node);
+ }, true);
+ }
+
+ protected void fetchNodeImpl(final RepositoryNode node) {
+ node.setParent(nodeService.getPrimaryParent(node.getNodeRef()).getParentRef());
+ node.setType(nodeService.getType(node.getNodeRef()));
+ node.getAspects().clear();
+ node.getAspects().addAll(nodeService.getAspects(node.getNodeRef()));
+ node.getProperties().clear();
+ node.getProperties().putAll(nodeService.getProperties(node.getNodeRef()));
+ }
+
+ protected final void updateNode(final RepositoryNode node, final Map properties) {
+ doInTransaction(() -> {
+ nodeService.addProperties(node.getNodeRef(), properties);
+
+ bindTransactionListener(node);
+ });
+ }
+
+ protected final void deleteNode(final RepositoryNode node) {
+ doInTransaction(() -> {
+ nodeService.deleteNode(node.getNodeRef());
+ });
+ }
+
+ protected final void bindTransactionListener(final RepositoryNode node) {
+ AlfrescoTransactionSupport.bindListener(new TransactionListenerAdapter() {
+ @Override
+ public void afterCommit() {
+ fetchNode(node);
+ }
+ });
+ }
+
+ protected void doInTransaction(final Runnable callback) {
+ doInTransaction(callback, false);
+ }
+
+ protected void doInTransaction(final Runnable callback, final boolean readOnly) {
+ transactionService.getRetryingTransactionHelper().doInTransaction(() -> {
+ callback.run();
+ return null;
+ }, readOnly);
+ }
+}
diff --git a/alfresco-filer-core/src/test/resources/context/test-action-context.xml b/alfresco-filer-core/src/test/resources/context/test-action-context.xml
new file mode 100644
index 0000000..d492e90
--- /dev/null
+++ b/alfresco-filer-core/src/test/resources/context/test-action-context.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/alfresco-filer-core/src/test/resources/context/test-model-context.xml b/alfresco-filer-core/src/test/resources/context/test-model-context.xml
new file mode 100644
index 0000000..16b5f63
--- /dev/null
+++ b/alfresco-filer-core/src/test/resources/context/test-model-context.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+ model/filerTestModel.xml
+
+
+
+
+
diff --git a/alfresco-filer-core/src/test/resources/model/filerTestModel.xml b/alfresco-filer-core/src/test/resources/model/filerTestModel.xml
new file mode 100644
index 0000000..9a2c214
--- /dev/null
+++ b/alfresco-filer-core/src/test/resources/model/filerTestModel.xml
@@ -0,0 +1,80 @@
+
+
+
+ Filer test model
+ Atol CD
+ 1.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Department folder
+ cm:folder
+
+ filer:fileable
+ filer:subscriber
+ filerTest:department
+
+
+
+
+
+ Department document
+ cm:content
+
+ filer:fileable
+ filerTest:department
+ filerTest:imported
+
+
+
+
+
+ Special document
+ cm:content
+
+ filer:fileable
+
+
+
+
+
+
+
+
+ Department
+ filer:propertyInheritance
+
+
+ Department name
+ d:text
+ true
+
+
+
+
+
+ Imported
+
+
+ Imported date
+ d:datetime
+
+
+
+
+
+
From 3b9c68645f6a603872a1c0f099197d5496a6c640 Mon Sep 17 00:00:00 2001
From: Corentin PIANA
Date: Thu, 22 Aug 2019 12:22:17 +0200
Subject: [PATCH 08/58] Testing of test implementation's filer actions
Test that the right action is executed and that node filing and node
hierarchy is well handled
Change-Id: Ie914bac9e9a3b99f9dbaef0a01f9ed59030b4fa7
---
.../test/domain/DeniedFilerActionTest.java | 74 ++++++
.../DepartmentContentFilerActionTest.java | 217 +++++++++++++++++-
.../DepartmentFolderFilerActionTest.java | 72 ++++++
3 files changed, 361 insertions(+), 2 deletions(-)
create mode 100644 alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DeniedFilerActionTest.java
create mode 100644 alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DepartmentFolderFilerActionTest.java
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DeniedFilerActionTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DeniedFilerActionTest.java
new file mode 100644
index 0000000..b2bf979
--- /dev/null
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DeniedFilerActionTest.java
@@ -0,0 +1,74 @@
+package com.atolcd.alfresco.filer.core.test.domain;
+
+import static java.util.UUID.randomUUID;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.alfresco.error.AlfrescoRuntimeException;
+import org.alfresco.model.ContentModel;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.repository.NodeService;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import com.atolcd.alfresco.filer.core.model.FilerException;
+import com.atolcd.alfresco.filer.core.model.RepositoryNode;
+import com.atolcd.alfresco.filer.core.service.FilerModelService;
+import com.atolcd.alfresco.filer.core.test.util.SiteBasedTest;
+
+public class DeniedFilerActionTest extends SiteBasedTest {
+
+ @Autowired
+ private FilerModelService filerModelService;
+
+ @Autowired
+ private NodeService nodeService;
+
+ @Test
+ public void withTypeContent() {
+ String name = randomUUID().toString();
+
+ RepositoryNode node = buildNode()
+ .type(ContentModel.TYPE_CONTENT)
+ .aspect(filerModelService.getFileableAspect())
+ .named(name)
+ .build();
+
+ NodeRef parentNodeRef = node.getParent();
+
+ Throwable thrown = Assertions.catchThrowable(() -> createNode(node));
+
+ assertThat(thrown)
+ .isInstanceOf(AlfrescoRuntimeException.class)
+ .hasCauseInstanceOf(FilerException.class);
+ assertThat(thrown.getCause())
+ .hasMessageContaining("DENIED")
+ .hasMessageContaining("filer.action.denied");
+
+ assertThat(nodeService.getChildByName(parentNodeRef, ContentModel.ASSOC_CONTAINS, name)).isNull();
+ }
+
+ @Test
+ public void withTypeFolder() {
+ String name = randomUUID().toString();
+
+ RepositoryNode node = buildNode()
+ .type(ContentModel.TYPE_FOLDER)
+ .aspect(filerModelService.getFileableAspect())
+ .named(name)
+ .build();
+
+ NodeRef parentNodeRef = node.getParent();
+
+ Throwable thrown = Assertions.catchThrowable(() -> createNode(node));
+
+ assertThat(thrown)
+ .isInstanceOf(AlfrescoRuntimeException.class)
+ .hasCauseInstanceOf(FilerException.class);
+ assertThat(thrown.getCause())
+ .hasMessageContaining("DENIED")
+ .hasMessageContaining("filer.action.denied");
+
+ assertThat(nodeService.getChildByName(parentNodeRef, ContentModel.ASSOC_CONTAINS, name)).isNull();
+ }
+}
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DepartmentContentFilerActionTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DepartmentContentFilerActionTest.java
index 67f3cae..8d67c9e 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DepartmentContentFilerActionTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DepartmentContentFilerActionTest.java
@@ -3,27 +3,240 @@
import static java.util.UUID.randomUUID;
import static org.assertj.core.api.Assertions.assertThat;
+import java.io.Serializable;
import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Map;
+import org.alfresco.model.ContentModel;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.repository.NodeService;
+import org.alfresco.service.namespace.QName;
+import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
import com.atolcd.alfresco.filer.core.model.RepositoryNode;
+import com.atolcd.alfresco.filer.core.service.FilerModelService;
import com.atolcd.alfresco.filer.core.test.domain.content.model.FilerTestConstants;
+import com.atolcd.alfresco.filer.core.test.util.ApplicationContextAwareTest;
import com.atolcd.alfresco.filer.core.test.util.SiteBasedTest;
public class DepartmentContentFilerActionTest extends SiteBasedTest {
+ @Autowired
+ private FilerModelService filerModelService;
+
+ @Autowired
+ private NodeService nodeService;
+
+ @Nested
+ // Re-implement ApplicationContextAwareTest as Spring does not find the configuration of nested class from the enclosing class
+ // See https://github.com/spring-projects/spring-framework/issues/19930
+ public class DepartmentDocument implements ApplicationContextAwareTest {
+
+ @Test
+ public void filerAspectHierarchy() {
+ RepositoryNode node = buildNode()
+ .type(FilerTestConstants.Department.DocumentType.NAME)
+ .property(FilerTestConstants.Department.Aspect.PROP_NAME, randomUUID())
+ .build();
+
+ createNode(node);
+
+ assertThat(node.getAspects()).contains(filerModelService.getFileableAspect());
+
+ NodeRef parent = node.getParent();
+ assertThat(nodeService.getAspects(parent)).contains(filerModelService.getSegmentAspect());
+
+ NodeRef grandParent = nodeService.getPrimaryParent(parent).getParentRef();
+ assertThat(nodeService.getAspects(grandParent)).contains(filerModelService.getSegmentAspect());
+
+ NodeRef greatGrandParent = nodeService.getPrimaryParent(grandParent).getParentRef();
+ assertThat(nodeService.getAspects(greatGrandParent)).contains(filerModelService.getSubscriberAspect());
+ }
+
+ @Test
+ public void typeHierarchy() {
+ QName type = FilerTestConstants.Department.DocumentType.NAME;
+
+ RepositoryNode node = buildNode()
+ .type(FilerTestConstants.Department.DocumentType.NAME)
+ .property(FilerTestConstants.Department.Aspect.PROP_NAME, randomUUID())
+ .build();
+
+ createNode(node);
+
+ assertThat(node.getType()).isEqualTo(type);
+ assertThat(nodeService.getType(node.getParent())).isEqualTo(ContentModel.TYPE_FOLDER);
+
+ NodeRef grandParent = nodeService.getPrimaryParent(node.getParent()).getParentRef();
+ assertThat(nodeService.getType(grandParent)).isEqualTo(ContentModel.TYPE_FOLDER);
+
+ NodeRef greatGrandParent = nodeService.getPrimaryParent(grandParent).getParentRef();
+ assertThat(nodeService.getType(greatGrandParent)).isEqualTo(FilerTestConstants.Department.FolderType.NAME);
+ }
+
+ @Test
+ public void withImportDate() {
+ String departmentName = randomUUID().toString();
+ LocalDateTime date = LocalDateTime.of(2004, 8, 12, 0, 0, 0);
+
+ RepositoryNode node = buildNode()
+ .type(FilerTestConstants.Department.DocumentType.NAME)
+ .property(FilerTestConstants.Department.Aspect.PROP_NAME, departmentName)
+ .property(FilerTestConstants.ImportedAspect.PROP_DATE, date.atZone(ZoneId.systemDefault()))
+ .build();
+
+ createNode(node);
+
+ assertThat(getPath(node)).isEqualTo(buildNodePath(departmentName, date));
+ }
+
+ @Test
+ public void withoutImportDate() {
+ String departmentName = randomUUID().toString();
+
+ RepositoryNode node = buildNode()
+ .type(FilerTestConstants.Department.DocumentType.NAME)
+ .property(FilerTestConstants.Department.Aspect.PROP_NAME, departmentName)
+ .build();
+
+ createNode(node);
+
+ assertThat(getPath(node)).isEqualTo(buildNodePath(departmentName, LocalDateTime.now()));
+ }
+
+ @Test
+ public void withImportDateInWrongSegment() {
+ String departmentNom = randomUUID().toString();
+ LocalDateTime wrongDate = LocalDateTime.of(2004, 8, 12, 0, 0, 0);
+ LocalDateTime targetDate = LocalDateTime.of(2002, 4, 6, 0, 0, 0);
+
+ // Create a node with the wrong date to create corresponding folder
+ RepositoryNode wrongSegmentNode = buildNode()
+ .type(FilerTestConstants.Department.DocumentType.NAME)
+ .property(FilerTestConstants.Department.Aspect.PROP_NAME, departmentNom)
+ .property(FilerTestConstants.ImportedAspect.PROP_DATE, wrongDate.atZone(ZoneId.systemDefault()))
+ .build();
+
+ createNode(wrongSegmentNode);
+
+ // Create node with the target date in the wrong folder
+ RepositoryNode node = buildNode()
+ .type(FilerTestConstants.Department.DocumentType.NAME)
+ .parent(wrongSegmentNode.getParent())
+ .property(FilerTestConstants.Department.Aspect.PROP_NAME, departmentNom)
+ .property(FilerTestConstants.ImportedAspect.PROP_DATE, targetDate.atZone(ZoneId.systemDefault()))
+ .build();
+
+ createNode(node);
+
+ assertThat(getPath(node)).isEqualTo(buildNodePath(departmentNom, targetDate));
+ }
+
+ @Test
+ public void updateImportDate() {
+ String departmentNom = randomUUID().toString();
+ LocalDateTime sourceDate = LocalDateTime.of(2004, 8, 12, 0, 0, 0);
+ LocalDateTime targetDate = LocalDateTime.of(2002, 4, 6, 0, 0, 0);
+
+ // Create node that will be updated
+ RepositoryNode node = buildNode()
+ .type(FilerTestConstants.Department.DocumentType.NAME)
+ .property(FilerTestConstants.Department.Aspect.PROP_NAME, departmentNom)
+ .property(FilerTestConstants.ImportedAspect.PROP_DATE, sourceDate.atZone(ZoneId.systemDefault()))
+ .build();
+
+ createNode(node);
+
+ assertThat(getPath(node)).isEqualTo(buildNodePath(departmentNom, sourceDate));
+
+ // Get ancestors before updating node as they will change
+ NodeRef oldParent = node.getParent();
+ NodeRef oldGrandParent = nodeService.getPrimaryParent(node.getParent()).getParentRef();
+
+ // Set updated property
+ Map dateProperty = Collections.singletonMap(FilerTestConstants.ImportedAspect.PROP_DATE,
+ Date.from(targetDate.atZone(ZoneId.systemDefault()).toInstant()));
+
+ updateNode(node, dateProperty);
+
+ assertThat(nodeService.exists(oldParent)).isFalse();
+ assertThat(nodeService.exists(oldGrandParent)).isFalse();
+ assertThat(getPath(node)).isEqualTo(buildNodePath(departmentNom, targetDate));
+ }
+
+ @Test
+ public void deleteDocument() {
+ String departmentNom = randomUUID().toString();
+ LocalDateTime date = LocalDateTime.of(2004, 8, 12, 0, 0, 0);
+
+ // Create node that will be deleted
+ RepositoryNode node = buildNode()
+ .type(FilerTestConstants.Department.DocumentType.NAME)
+ .property(FilerTestConstants.Department.Aspect.PROP_NAME, departmentNom)
+ .property(FilerTestConstants.ImportedAspect.PROP_DATE, date.atZone(ZoneId.systemDefault()))
+ .build();
+
+ createNode(node);
+
+ assertThat(getPath(node)).isEqualTo(buildNodePath(departmentNom, date));
+
+ // Get ancestors before deleting node
+ NodeRef grandParent = nodeService.getPrimaryParent(node.getParent()).getParentRef();
+ NodeRef greatGrandParent = nodeService.getPrimaryParent(grandParent).getParentRef();
+
+ deleteNode(node);
+
+ assertThat(nodeService.exists(node.getNodeRef())).isFalse();
+ assertThat(nodeService.exists(node.getParent())).isFalse();
+ assertThat(nodeService.exists(grandParent)).isFalse();
+ assertThat(nodeService.exists(greatGrandParent)).isTrue();
+ }
+
+ @Test
+ public void multipleDocumentWithSameImportDate() {
+ String departmentName = randomUUID().toString();
+ LocalDateTime date = LocalDateTime.of(2004, 8, 12, 0, 0, 0);
+
+ RepositoryNode firstNode = buildNode()
+ .type(FilerTestConstants.Department.DocumentType.NAME)
+ .property(FilerTestConstants.Department.Aspect.PROP_NAME, departmentName)
+ .property(FilerTestConstants.ImportedAspect.PROP_DATE, date.atZone(ZoneId.systemDefault()))
+ .build();
+
+ RepositoryNode secondNode = buildNode()
+ .type(FilerTestConstants.Department.DocumentType.NAME)
+ .property(FilerTestConstants.Department.Aspect.PROP_NAME, departmentName)
+ .property(FilerTestConstants.ImportedAspect.PROP_DATE, date.atZone(ZoneId.systemDefault()))
+ .build();
+
+ createNode(firstNode);
+ createNode(secondNode);
+
+ String nodePath = buildNodePath(departmentName, date);
+
+ assertThat(getPath(firstNode)).isEqualTo(nodePath);
+ assertThat(getPath(secondNode)).isEqualTo(nodePath);
+ }
+ }
+
@Test
- public void departmentDocumentWithoutImportDate() {
+ public void specialDocumentTypeWithoutImportDate() {
+ QName type = FilerTestConstants.SpecialDocumentType.NAME;
String departmentName = randomUUID().toString();
RepositoryNode node = buildNode()
- .type(FilerTestConstants.Department.DocumentType.NAME)
+ .type(type)
.property(FilerTestConstants.Department.Aspect.PROP_NAME, departmentName)
.build();
createNode(node);
+ assertThat(node.getType()).isEqualTo(type);
assertThat(getPath(node)).isEqualTo(buildNodePath(departmentName, LocalDateTime.now()));
}
}
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DepartmentFolderFilerActionTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DepartmentFolderFilerActionTest.java
new file mode 100644
index 0000000..14c69a0
--- /dev/null
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DepartmentFolderFilerActionTest.java
@@ -0,0 +1,72 @@
+package com.atolcd.alfresco.filer.core.test.domain;
+
+import static java.util.UUID.randomUUID;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.Collections;
+import java.util.Map;
+
+import org.alfresco.service.namespace.QName;
+import org.junit.jupiter.api.Test;
+
+import com.atolcd.alfresco.filer.core.model.RepositoryNode;
+import com.atolcd.alfresco.filer.core.test.domain.content.model.FilerTestConstants;
+import com.atolcd.alfresco.filer.core.test.util.SiteBasedTest;
+
+public class DepartmentFolderFilerActionTest extends SiteBasedTest {
+
+ @Test
+ public void withDepartmentName() {
+ String departmentName = randomUUID().toString();
+
+ RepositoryNode node = buildNode()
+ .type(FilerTestConstants.Department.FolderType.NAME)
+ .property(FilerTestConstants.Department.Aspect.PROP_NAME, departmentName)
+ .build();
+
+ createNode(node);
+
+ assertThat(node.getProperty(FilerTestConstants.Department.Aspect.PROP_NAME, String.class)).isEqualTo(departmentName);
+ assertThat(getPath(node)).isEqualTo(buildSitePath());
+ }
+
+ @Test
+ public void updateDepartmentName() {
+ // Create folder node
+ RepositoryNode departmentFolderNode = buildNode()
+ .type(FilerTestConstants.Department.FolderType.NAME)
+ .property(FilerTestConstants.Department.Aspect.PROP_NAME, randomUUID())
+ .build();
+
+ createNode(departmentFolderNode);
+
+ // Create document node in folder
+ LocalDateTime date = LocalDateTime.of(2004, 8, 12, 0, 0, 0);
+ RepositoryNode documentNode = buildNode()
+ .type(FilerTestConstants.Department.DocumentType.NAME)
+ .property(FilerTestConstants.Department.Aspect.PROP_NAME,
+ departmentFolderNode.getProperty(FilerTestConstants.Department.Aspect.PROP_NAME, String.class))
+ .property(FilerTestConstants.ImportedAspect.PROP_DATE, date.atZone(ZoneId.systemDefault()))
+ .build();
+
+ createNode(documentNode);
+
+ assertThat(getPath(documentNode)).isEqualTo(
+ buildNodePath(departmentFolderNode.getProperty(FilerTestConstants.Department.Aspect.PROP_NAME, String.class), date));
+
+ // Update folder's name
+ Map departmentNameProperty = Collections.singletonMap(FilerTestConstants.Department.Aspect.PROP_NAME,
+ randomUUID().toString());
+
+ updateNode(departmentFolderNode, departmentNameProperty);
+
+ // Get document node and check if it has been updated automatically
+ fetchNode(documentNode);
+
+ assertThat(getPath(documentNode)).isEqualTo(
+ buildNodePath(departmentFolderNode.getProperty(FilerTestConstants.Department.Aspect.PROP_NAME, String.class), date));
+ }
+}
From 4b4ca11e1a0b867874a319b3a226136c058a3e4b Mon Sep 17 00:00:00 2001
From: Corentin PIANA
Date: Thu, 22 Aug 2019 11:15:25 +0200
Subject: [PATCH 09/58] PMD: @RepeatedTest annotated method as test case
To supress warning on test class containing only @RepeatedTest methods
and no @Test method.
Change-Id: I9485a97eb8e868ac2edb806617bcfc688a0a82ee
---
.../src/main/resources/filer/pmd-ruleset.xml | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/alfresco-filer-quality/src/main/resources/filer/pmd-ruleset.xml b/alfresco-filer-quality/src/main/resources/filer/pmd-ruleset.xml
index b0b548d..2ffe8cd 100644
--- a/alfresco-filer-quality/src/main/resources/filer/pmd-ruleset.xml
+++ b/alfresco-filer-quality/src/main/resources/filer/pmd-ruleset.xml
@@ -47,7 +47,7 @@
+ value="ancestor::ClassOrInterfaceBodyDeclaration/Annotation[@AnnotationName='PostConstruct']"/>
@@ -70,4 +70,11 @@
value="ancestor::ClassOrInterfaceDeclaration[ends-with(@Image, 'Builder')]"/>
+
+
+
+
+
+
From 16f3948a18af373fd7ec7943c6b9831887fec1e2 Mon Sep 17 00:00:00 2001
From: Corentin PIANA
Date: Thu, 29 Aug 2019 15:56:51 +0200
Subject: [PATCH 10/58] PMD: number of import for test classes
- Do not limit import number in test classes
- Do not count Mockito static import
Change-Id: Ia92d26d864c1d1c1f80228d5d34318eb2190fe4b
---
.../src/main/resources/filer/pmd-ruleset.xml | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
diff --git a/alfresco-filer-quality/src/main/resources/filer/pmd-ruleset.xml b/alfresco-filer-quality/src/main/resources/filer/pmd-ruleset.xml
index 2ffe8cd..c99cacb 100644
--- a/alfresco-filer-quality/src/main/resources/filer/pmd-ruleset.xml
+++ b/alfresco-filer-quality/src/main/resources/filer/pmd-ruleset.xml
@@ -77,4 +77,21 @@
value="ClassOrInterfaceBody/ClassOrInterfaceBodyDeclaration/Annotation[@AnnotationName='RepeatedTest']"/>
+
+
+
+
+
+
+
+
+
+
+ $maximumStaticImports]
+ ]]>
+
+
+
From a1cdfaa50ceb65ef94a9bd9f3b262d222f991acb Mon Sep 17 00:00:00 2001
From: Corentin PIANA
Date: Thu, 22 Aug 2019 12:23:17 +0200
Subject: [PATCH 11/58] Testing parallel execution of the filer
Do multiple node creations, updating and deletions at once and
simultaneously time to ensure that the filing is well done and that
concurrency is correctly handle by the module.
Change-Id: Ia1ec9030de20fd1933ea9a11cd0c78ce81ee0b6b
---
.../test/domain/AbstractParallelTest.java | 59 +++
.../domain/BinaryOperationParallelTest.java | 347 ++++++++++++++++++
.../domain/UnaryOperationParallelTest.java | 209 +++++++++++
3 files changed, 615 insertions(+)
create mode 100644 alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/AbstractParallelTest.java
create mode 100644 alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/BinaryOperationParallelTest.java
create mode 100644 alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/UnaryOperationParallelTest.java
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/AbstractParallelTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/AbstractParallelTest.java
new file mode 100644
index 0000000..6d9d621
--- /dev/null
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/AbstractParallelTest.java
@@ -0,0 +1,59 @@
+package com.atolcd.alfresco.filer.core.test.domain;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import org.alfresco.repo.security.authentication.AuthenticationUtil;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.parallel.Execution;
+import org.junit.jupiter.api.parallel.ExecutionMode;
+
+import com.atolcd.alfresco.filer.core.model.impl.RepositoryNodeBuilder;
+import com.atolcd.alfresco.filer.core.test.domain.content.model.FilerTestConstants;
+import com.atolcd.alfresco.filer.core.test.util.SiteBasedTest;
+
+/**
+ * Provide base class for parallel tests of {@linkplain com.atolcd.alfresco.filer.core.model.FilerAction Filer actions}. Assert
+ * parallelism is available on test platform.
+ *
+ *
+ * Parallel tests are not executed concurrently to ensure most resources are available for the test, including multiple physical
+ * threads to be sure parallelism is achieved during test.
+ *
+ * Each test creates a thread by task to be launched exactly at the same time and waits until each task is performed before
+ * checking assertions.
+ * All tests are repeated multiple times (10) because errors occur randomly, depending on the actual level of parallelization.
+ * Therefore, even if there is a problem, the test can succeed multiple times before the issue arises.
+ *
+ */
+public class BinaryOperationParallelTest extends AbstractParallelTest {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(BinaryOperationParallelTest.class);
+
+ private static final int MAIN_TASK = 1;
+ private static final int CREATE_TASK = 1;
+ private static final int UPDATE_TASK = 1;
+ private static final int DELETE_TASK = 1;
+
+ @Autowired
+ private NodeService nodeService;
+
+ /**
+ * Test parallel creation and deletion of filed nodes within the same segment folder.
+ *
+ *
+ * Creates a node that is filed in the same folder as the one that is deleted. Each task is executed at the same time in its own
+ * transaction.
+ * In this case, a newly created node could be deleted by mistake in another transaction running simultaneously that was
+ * deleting, in the same filer segment, the single node at the time it was checked. This would
+ * result in deleting the segment which was evaluated as empty and thus create the issue.
+ *
+ */
+ @RepeatedTest(10)
+ public void createAndDeleteNodes() throws InterruptedException, BrokenBarrierException {
+ String departmentName = randomUUID().toString();
+ LocalDateTime date = LocalDateTime.of(2004, 8, 12, 0, 0, 0);
+
+ CyclicBarrier startingBarrier = new CyclicBarrier(CREATE_TASK + DELETE_TASK);
+ CyclicBarrier preparationAssertBarrier = new CyclicBarrier(MAIN_TASK + DELETE_TASK);
+ CountDownLatch endingLatch = new CountDownLatch(CREATE_TASK + DELETE_TASK);
+ AtomicReference createdNode = new AtomicReference<>();
+ AtomicReference nodeToDelete = new AtomicReference<>();
+
+ execute(() -> {
+ LOGGER.debug("Create task: task started");
+ RepositoryNode node = buildNode(departmentName, date).build();
+
+ try {
+ // Wait for every task to be ready for launching parallel task execution
+ startingBarrier.await(10, TimeUnit.SECONDS);
+
+ LOGGER.debug("Create task: node creation start");
+ createNode(node);
+ LOGGER.debug("Create task: node creation end");
+ createdNode.set(node);
+ } catch (Exception e) { //NOPMD Catch all exceptions that might occur in thread as they will not be thrown to main thread
+ LOGGER.error("Create task: could not create node", e);
+ }
+ }, endingLatch);
+
+ execute(() -> {
+ LOGGER.debug("Delete task: task started");
+ RepositoryNode node = buildNode(departmentName, date)
+ // Do not archive node, this could generate contention on creating user trashcan
+ .aspect(ContentModel.ASPECT_TEMPORARY)
+ .build();
+
+ try {
+ LOGGER.debug("Delete task: creating node that will be deleted");
+ createNode(node);
+ nodeToDelete.set(node);
+
+ preparationAssertBarrier.await(10, TimeUnit.SECONDS);
+ // Wait for assertion on created nodes taking place in main task
+ preparationAssertBarrier.await(10, TimeUnit.SECONDS);
+
+ // Wait for every task to be ready for launching parallel task execution
+ startingBarrier.await(10, TimeUnit.SECONDS);
+
+ LOGGER.debug("Delete task: node deletion start");
+ deleteNode(node);
+ LOGGER.debug("Delete task: node deletion end");
+ } catch (Exception e) { //NOPMD Catch all exceptions that might occur in thread as they will not be thrown to main thread
+ LOGGER.error("Delete task: could not delete node", e);
+ }
+ }, endingLatch);
+
+ // Wait for node creation to finish and then assert node is indeed created
+ preparationAssertBarrier.await();
+ assertThat(getPath(nodeToDelete.get())).isEqualTo(buildNodePath(departmentName, date));
+ preparationAssertBarrier.await();
+
+ // Wait for every task to finish job before asserting results
+ endingLatch.await();
+
+ LOGGER.debug("All tasks are done, starting assertions");
+
+ // Assert all tasks were ready for parallel task execution
+ assertThat(startingBarrier.isBroken()).isFalse();
+
+ assertThat(getPath(createdNode.get())).isEqualTo(buildNodePath(departmentName, date));
+
+ assertThat(nodeService.exists(nodeToDelete.get().getNodeRef())).isFalse();
+ NodeRef nodeToDeleteParent = nodeToDelete.get().getParent();
+ if (nodeToDeleteParent.equals(createdNode.get().getParent())) {
+ assertThat(nodeService.exists(nodeToDeleteParent)).isTrue();
+ NodeRef nodeToDeleteGrandParent = nodeService.getPrimaryParent(nodeToDeleteParent).getParentRef();
+ assertThat(nodeService.exists(nodeToDeleteGrandParent)).isTrue();
+ } else {
+ assertThat(nodeService.exists(nodeToDeleteParent)).isFalse();
+ }
+ }
+
+ /**
+ * Test parallel updating and deletion of filed nodes within the same segment folder.
+ *
+ *
+ * Update a node that is filled (and which filling requires the node to be moved) from the same folder as the node that is
+ * deleted. Each task is executed at the same time in its own transaction.
+ * In this case, an empty segment may not be deleted if an update moves a node from a filer segment (source) to another and at
+ * the same time another transaction is deleting, in the same source filer segment, the only other
+ * remaining node. Both transactions could evaluate that there is still a node in the segment at the time it is checked
+ * and would keep the segment and thus create the issue.
+ *
+ */
+ @RepeatedTest(10)
+ public void updateAndDeleteNodesInSourceFolder() throws InterruptedException, BrokenBarrierException {
+ String departmentName = randomUUID().toString();
+ LocalDateTime sourceDate = LocalDateTime.of(2004, 8, 12, 0, 0, 0);
+ LocalDateTime targetDate = LocalDateTime.of(2002, 4, 6, 0, 0, 0);
+
+ CyclicBarrier startingBarrier = new CyclicBarrier(UPDATE_TASK + DELETE_TASK);
+ CyclicBarrier preparationAssertBarrier = new CyclicBarrier(MAIN_TASK + UPDATE_TASK + DELETE_TASK);
+ CountDownLatch endingLatch = new CountDownLatch(UPDATE_TASK + DELETE_TASK);
+ AtomicReference nodeToUpdate = new AtomicReference<>();
+ AtomicReference nodeToDelete = new AtomicReference<>();
+
+ execute(() -> {
+ LOGGER.debug("Update task: task started");
+ RepositoryNode node = buildNode(departmentName, sourceDate).build();
+
+ try {
+ LOGGER.debug("Update task: creating node that will be updated");
+ createNode(node);
+ nodeToUpdate.set(node);
+
+ preparationAssertBarrier.await(10, TimeUnit.SECONDS);
+ // Wait for assertion on created nodes taking place in main task
+ preparationAssertBarrier.await(10, TimeUnit.SECONDS);
+
+ Map dateProperty = Collections.singletonMap(FilerTestConstants.ImportedAspect.PROP_DATE,
+ Date.from(targetDate.atZone(ZoneId.systemDefault()).toInstant()));
+
+ // Wait for every task to be ready for launching parallel task execution
+ startingBarrier.await(10, TimeUnit.SECONDS);
+
+ LOGGER.debug("Update task: node creation start");
+ updateNode(node, dateProperty);
+ LOGGER.debug("Update task: node creation end");
+ } catch (Exception e) { //NOPMD Catch all exceptions that might occur in thread as they will not be thrown to main thread
+ LOGGER.error("Update task: could not update node", e);
+ }
+ }, endingLatch);
+
+ execute(() -> {
+ LOGGER.debug("Delete task: task started");
+ RepositoryNode node = buildNode(departmentName, sourceDate)
+ // Do not archive node, this could generate contention on creating user trashcan
+ .aspect(ContentModel.ASPECT_TEMPORARY)
+ .build();
+
+ try {
+ LOGGER.debug("Delete task: creating node that will be deleted");
+ createNode(node);
+ nodeToDelete.set(node);
+
+ preparationAssertBarrier.await(10, TimeUnit.SECONDS);
+ // Wait for assertion on created nodes taking place in main task
+ preparationAssertBarrier.await(10, TimeUnit.SECONDS);
+
+ // Wait for every task to be ready for launching parallel task execution
+ startingBarrier.await(10, TimeUnit.SECONDS);
+
+ LOGGER.debug("Delete task: node deletion start");
+ deleteNode(node);
+ LOGGER.debug("Delete task: node deletion end");
+ } catch (Exception e) { //NOPMD Catch all exceptions that might occur in thread as they will not be thrown to main thread
+ LOGGER.error("Delete task: could not delete node", e);
+ }
+ }, endingLatch);
+
+ // Wait for node creation to finish and then assert all nodes are indeed created
+ preparationAssertBarrier.await();
+ assertThat(getPath(nodeToUpdate.get())).isEqualTo(buildNodePath(departmentName, sourceDate));
+ assertThat(getPath(nodeToDelete.get())).isEqualTo(buildNodePath(departmentName, sourceDate));
+ preparationAssertBarrier.await();
+
+ // Wait for every task to finish job before asserting results
+ endingLatch.await();
+
+ LOGGER.debug("All tasks are done, starting assertions");
+
+ // Assert all tasks were ready for parallel task execution
+ assertThat(startingBarrier.isBroken()).isFalse();
+
+ assertThat(getPath(nodeToUpdate.get())).isEqualTo(buildNodePath(departmentName, targetDate));
+
+ assertThat(nodeService.exists(nodeToDelete.get().getNodeRef())).isFalse();
+ assertThat(nodeService.exists(nodeToDelete.get().getParent())).isFalse();
+ }
+
+ /**
+ * Test parallel updating and deletion of filed nodes within the same segment folder.
+ *
+ *
+ * Update a node that is filled (and which filling requires the node to be moved) from/to the same folder as the node that is
+ * deleted. Each task is executed at the same time in its own transaction.
+ * In this case, if an update moves a node to a filer segment (target), it could be deleted by mistake in another transaction
+ * running simultaneously that was deleting, in the same filer segment, the single node at the
+ * time it was checked. This would result in deleting the segment which was evaluated as empty and thus create the issue.
+ *
+ */
+ @RepeatedTest(10)
+ public void updateAndDeleteNodesInTargetFolder() throws InterruptedException, BrokenBarrierException {
+ String departmentName = randomUUID().toString();
+ LocalDateTime sourceDate = LocalDateTime.of(2004, 8, 12, 0, 0, 0);
+ LocalDateTime targetDate = LocalDateTime.of(2002, 4, 6, 0, 0, 0);
+
+ CyclicBarrier startingBarrier = new CyclicBarrier(UPDATE_TASK + DELETE_TASK);
+ CyclicBarrier preparationAssertBarrier = new CyclicBarrier(MAIN_TASK + UPDATE_TASK + DELETE_TASK);
+ CountDownLatch endingLatch = new CountDownLatch(UPDATE_TASK + DELETE_TASK);
+ AtomicReference nodeToUpdate = new AtomicReference<>();
+ AtomicReference nodeToDelete = new AtomicReference<>();
+
+ execute(() -> {
+ LOGGER.debug("Update task: task started");
+ RepositoryNode node = buildNode(departmentName, sourceDate).build();
+
+ try {
+ LOGGER.debug("Update task: creating node that will be updated");
+ createNode(node);
+ nodeToUpdate.set(node);
+
+ preparationAssertBarrier.await(10, TimeUnit.SECONDS);
+ // Wait for assertion on created nodes taking place in main task
+ preparationAssertBarrier.await(10, TimeUnit.SECONDS);
+
+ Map dateProperty = Collections.singletonMap(FilerTestConstants.ImportedAspect.PROP_DATE,
+ Date.from(targetDate.atZone(ZoneId.systemDefault()).toInstant()));
+
+ // Wait for every task to be ready for launching parallel task execution
+ startingBarrier.await(10, TimeUnit.SECONDS);
+
+ LOGGER.debug("Update task: node creation start");
+ updateNode(node, dateProperty);
+ LOGGER.debug("Update task: node creation end");
+ } catch (Exception e) { //NOPMD Catch all exceptions that might occur in thread as they will not be thrown to main thread
+ LOGGER.error("Update task: could not update node", e);
+ }
+ }, endingLatch);
+
+ execute(() -> {
+ LOGGER.debug("Delete task: task started");
+ RepositoryNode node = buildNode(departmentName, targetDate)
+ // Do not archive node, this could generate contention on creating user trashcan
+ .aspect(ContentModel.ASPECT_TEMPORARY)
+ .build();
+
+ try {
+ LOGGER.debug("Delete task: creating node that will be deleted");
+ createNode(node);
+ nodeToDelete.set(node);
+
+ preparationAssertBarrier.await(10, TimeUnit.SECONDS);
+ // Wait for assertion on created nodes taking place in main task
+ preparationAssertBarrier.await(10, TimeUnit.SECONDS);
+
+ // Wait for every task to be ready for launching parallel task execution
+ startingBarrier.await(10, TimeUnit.SECONDS);
+
+ LOGGER.debug("Delete task: node deletion start");
+ deleteNode(node);
+ LOGGER.debug("Delete task: node deletion end");
+ } catch (Exception e) { //NOPMD Catch all exceptions that might occur in thread as they will not be thrown to main thread
+ LOGGER.error("Delete task: could not delete node", e);
+ }
+ }, endingLatch);
+
+ // Wait for node creation to finish and then assert all nodes are indeed created
+ preparationAssertBarrier.await();
+ assertThat(getPath(nodeToUpdate.get())).isEqualTo(buildNodePath(departmentName, sourceDate));
+ assertThat(getPath(nodeToDelete.get())).isEqualTo(buildNodePath(departmentName, targetDate));
+ preparationAssertBarrier.await();
+
+ // Wait for every task to finish job before asserting results
+ endingLatch.await();
+
+ LOGGER.debug("All tasks are done, starting assertions");
+
+ // Assert all tasks were ready for parallel task execution
+ assertThat(startingBarrier.isBroken()).isFalse();
+
+ assertThat(getPath(nodeToUpdate.get())).isEqualTo(buildNodePath(departmentName, targetDate));
+
+ assertThat(nodeService.exists(nodeToDelete.get().getNodeRef())).isFalse();
+ NodeRef nodeToDeleteParent = nodeToDelete.get().getParent();
+ if (nodeToDeleteParent.equals(nodeToUpdate.get().getParent())) {
+ // Move occurred before deletion and target segment have not been removed
+ assertThat(nodeService.exists(nodeToDeleteParent)).isTrue();
+ NodeRef nodeToDeleteGrandParent = nodeService.getPrimaryParent(nodeToDeleteParent).getParentRef();
+ assertThat(nodeService.exists(nodeToDeleteGrandParent)).isTrue();
+ } else {
+ // Deletion performed before node updating, filer have recreated new segment for target
+ assertThat(nodeService.exists(nodeToDeleteParent)).isFalse();
+ }
+ }
+}
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/UnaryOperationParallelTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/UnaryOperationParallelTest.java
new file mode 100644
index 0000000..a526e9a
--- /dev/null
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/UnaryOperationParallelTest.java
@@ -0,0 +1,209 @@
+package com.atolcd.alfresco.filer.core.test.domain;
+
+import static java.util.UUID.randomUUID;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+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.concurrent.BrokenBarrierException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.CyclicBarrier;
+import java.util.concurrent.TimeUnit;
+
+import org.alfresco.model.ContentModel;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.repository.NodeService;
+import org.alfresco.service.namespace.QName;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import com.atolcd.alfresco.filer.core.model.RepositoryNode;
+import com.atolcd.alfresco.filer.core.test.domain.content.model.FilerTestConstants;
+import com.atolcd.alfresco.filer.core.test.util.SiteBasedTest;
+
+/**
+ * Test multiple executions in parallel of one operation
+ */
+public class UnaryOperationParallelTest extends AbstractParallelTest {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(UnaryOperationParallelTest.class);
+
+ @Autowired
+ private NodeService nodeService;
+
+ @Test
+ public void createMultipleNodes() throws InterruptedException {
+ String departmentName = randomUUID().toString();
+ LocalDateTime date = LocalDateTime.of(2004, 8, 12, 0, 0, 0);
+
+ CyclicBarrier startingBarrier = new CyclicBarrier(NUM_THREAD_TO_LAUNCH);
+ CountDownLatch endingLatch = new CountDownLatch(NUM_THREAD_TO_LAUNCH);
+ List results = Collections.synchronizedList(new ArrayList<>());
+
+ for (int i = 0; i < NUM_THREAD_TO_LAUNCH; i++) {
+ execute(() -> {
+ RepositoryNode node = buildNode(departmentName, date).build();
+
+ try {
+ // Wait for every thread to be ready to launch parallel createNode
+ startingBarrier.await(10, TimeUnit.SECONDS);
+
+ createNode(node);
+ results.add(node);
+ } catch (Exception e) { //NOPMD Catch all exceptions that might occur in thread as they will not be thrown to main thread
+ LOGGER.error("Could not create node", e);
+ }
+ }, endingLatch);
+ }
+
+ // Wait for every thread to finish job before asserting results
+ endingLatch.await();
+
+ // Assert all threads were ready for parallel createNode
+ assertThat(startingBarrier.isBroken()).isFalse();
+
+ assertThat(results.stream().map(SiteBasedTest::getPath))
+ .hasSize(NUM_THREAD_TO_LAUNCH)
+ .containsOnly(buildNodePath(departmentName, date));
+ }
+
+ @Test
+ public void updateMultipleNodes() throws InterruptedException, BrokenBarrierException {
+ String departmentName = randomUUID().toString();
+ LocalDateTime sourceDate = LocalDateTime.of(2004, 8, 12, 0, 0, 0);
+ LocalDateTime targetDate = LocalDateTime.of(2002, 4, 6, 0, 0, 0);
+
+ CyclicBarrier startingBarrier = new CyclicBarrier(NUM_THREAD_TO_LAUNCH);
+ CyclicBarrier preparationAssertBarrier = new CyclicBarrier(NUM_THREAD_TO_LAUNCH + 1); // Test threads plus main thread
+ CountDownLatch endingLatch = new CountDownLatch(NUM_THREAD_TO_LAUNCH);
+ List results = Collections.synchronizedList(new ArrayList<>());
+ Set segmentAncestors = Collections.synchronizedSet(new HashSet<>());
+ Set closestNonSegmentAncestor = Collections.synchronizedSet(new HashSet<>());
+
+ for (int i = 0; i < NUM_THREAD_TO_LAUNCH; i++) {
+ execute(() -> {
+ RepositoryNode node = buildNode(departmentName, sourceDate).build();
+
+ try {
+ createNode(node);
+ results.add(node);
+
+ preparationAssertBarrier.await(10, TimeUnit.SECONDS);
+ // Wait for assertion on created nodes taking place in main thread
+ preparationAssertBarrier.await(10, TimeUnit.SECONDS);
+
+ segmentAncestors.add(node.getParent());
+ NodeRef grandParentRef = nodeService.getPrimaryParent(node.getParent()).getParentRef();
+ segmentAncestors.add(grandParentRef);
+ NodeRef greatGrandParentRef = nodeService.getPrimaryParent(grandParentRef).getParentRef();
+ closestNonSegmentAncestor.add(greatGrandParentRef);
+
+ Map dateProperty = Collections.singletonMap(FilerTestConstants.ImportedAspect.PROP_DATE,
+ Date.from(targetDate.atZone(ZoneId.systemDefault()).toInstant()));
+
+ // Wait for every thread to be ready to launch parallel updateNode
+ startingBarrier.await(10, TimeUnit.SECONDS);
+
+ updateNode(node, dateProperty);
+ } catch (Exception e) { //NOPMD Catch all exceptions that might occur in thread as they will not be thrown to main thread
+ LOGGER.error("Could not update node", e);
+ }
+ }, endingLatch);
+ }
+
+ // Wait for node creation to finish and then assert all nodes are well created
+ preparationAssertBarrier.await();
+ assertThat(results).hasSize(NUM_THREAD_TO_LAUNCH);
+ for (RepositoryNode node : results) {
+ assertThat(getPath(node)).isEqualTo(buildNodePath(departmentName, sourceDate));
+ }
+ preparationAssertBarrier.await();
+
+ // Wait for every thread to finish job before asserting results
+ endingLatch.await();
+
+ // Assert all threads were ready for parallel updateNode
+ assertThat(startingBarrier.isBroken()).isFalse();
+
+ assertThat(results.stream().map(SiteBasedTest::getPath))
+ .hasSize(NUM_THREAD_TO_LAUNCH)
+ .containsOnly(buildNodePath(departmentName, targetDate));
+
+ assertThat(segmentAncestors.stream().map(nodeService::exists)).isNotEmpty().containsOnly(false);
+ assertThat(closestNonSegmentAncestor.stream().map(nodeService::exists)).isNotEmpty().containsOnly(true);
+ }
+
+ @Test
+ public void deleteMultipleNodes() throws InterruptedException, BrokenBarrierException {
+ String departmentName = randomUUID().toString();
+ LocalDateTime date = LocalDateTime.of(2004, 8, 12, 0, 0, 0);
+
+ CyclicBarrier startingBarrier = new CyclicBarrier(NUM_THREAD_TO_LAUNCH);
+ CyclicBarrier preparationAssertBarrier = new CyclicBarrier(NUM_THREAD_TO_LAUNCH + 1); // Test threads plus main thread
+ CountDownLatch endingLatch = new CountDownLatch(NUM_THREAD_TO_LAUNCH);
+ List results = Collections.synchronizedList(new ArrayList<>());
+ Set segmentAncestors = Collections.synchronizedSet(new HashSet<>());
+ Set closestNonSegmentAncestor = Collections.synchronizedSet(new HashSet<>());
+
+ for (int i = 0; i < NUM_THREAD_TO_LAUNCH; i++) {
+ execute(() -> {
+ RepositoryNode node = buildNode(departmentName, date)
+ .aspect(ContentModel.ASPECT_TEMPORARY) // Do not archive node, this could generate contention on creating user trashcan
+ .build();
+
+ try {
+ createNode(node);
+ results.add(node);
+
+ preparationAssertBarrier.await(10, TimeUnit.SECONDS);
+ // Wait for assertion on created nodes taking place in main thread
+ preparationAssertBarrier.await(10, TimeUnit.SECONDS);
+
+ segmentAncestors.add(node.getParent());
+ NodeRef grandParentRef = nodeService.getPrimaryParent(node.getParent()).getParentRef();
+ segmentAncestors.add(grandParentRef);
+ NodeRef greatGrandParentRef = nodeService.getPrimaryParent(grandParentRef).getParentRef();
+ closestNonSegmentAncestor.add(greatGrandParentRef);
+
+ // Wait for every thread to be ready to launch parallel deleteNode
+ startingBarrier.await(10, TimeUnit.SECONDS);
+
+ deleteNode(node);
+ } catch (Exception e) { //NOPMD Catch all exceptions that might occur in thread as they will not be thrown to main thread
+ LOGGER.error("Could not delete node", e);
+ }
+ }, endingLatch);
+ }
+
+ // Wait for node creation to finish and then assert all nodes are well created
+ preparationAssertBarrier.await();
+ assertThat(results).hasSize(NUM_THREAD_TO_LAUNCH);
+ for (RepositoryNode node : results) {
+ assertThat(getPath(node)).isEqualTo(buildNodePath(departmentName, date));
+ }
+ preparationAssertBarrier.await();
+
+ // Wait for every thread to finish job before asserting results
+ endingLatch.await();
+
+ // Assert all threads were ready for parallel deleteNode
+ assertThat(startingBarrier.isBroken()).isFalse();
+
+ assertThat(results.stream().map(RepositoryNode::getNodeRef).map(nodeService::exists))
+ .hasSize(NUM_THREAD_TO_LAUNCH)
+ .containsOnly(false);
+
+ assertThat(segmentAncestors.stream().map(nodeService::exists)).isNotEmpty().containsOnly(false);
+ assertThat(closestNonSegmentAncestor.stream().map(nodeService::exists)).isNotEmpty().containsOnly(true);
+ }
+}
From 0b15fec5eb0eb6c6a1262a1fb5004f1fbf6c5396 Mon Sep 17 00:00:00 2001
From: Corentin PIANA
Date: Fri, 23 Aug 2019 11:27:16 +0200
Subject: [PATCH 12/58] Extension to ensure corect usage of injected mocks
Check that mocks/spies ijected by Spring in test classes are correctly
used, i.e. no unnecessary stubbing.
Injected mocks are reseted after each test.
Change-Id: Ib15389da2ac77a373333d2f3f62827ad9e6648b7
---
.../AutowiredMockAwareMockitoExtension.java | 68 +++++++++++++++++++
1 file changed, 68 insertions(+)
create mode 100644 alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/util/AutowiredMockAwareMockitoExtension.java
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/util/AutowiredMockAwareMockitoExtension.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/util/AutowiredMockAwareMockitoExtension.java
new file mode 100644
index 0000000..4580115
--- /dev/null
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/util/AutowiredMockAwareMockitoExtension.java
@@ -0,0 +1,68 @@
+package com.atolcd.alfresco.filer.core.test.util;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.mockito.Mockito;
+import org.mockito.internal.util.MockUtil;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * Ensure correct use of mock/spy objects injected by Spring
+ *
+ *
+ * Calling {@link Mockito#spy(Object)} on those {@linkplain Autowired autowired}
+ * mocks make them known of Mockito, without other effect.
+ * That way, Mockito will check for unnecessary stubbing of the injected mocks.
+ *
+ *
+ *
+ * The injected mocks are reseted after each test.
+ *
+ */
+public class AutowiredMockAwareMockitoExtension extends MockitoExtension {
+
+ private List
+
+
+ com.atolcd.alfresco.filer.core.service.impl.FilerOperationService
+
+
+
+
+
+
@@ -35,13 +45,33 @@
+
+
+ com.atolcd.alfresco.filer.core.service.impl.FilerFolderService
+
+
+
+
+
+
-
+
+
+ com.atolcd.alfresco.filer.core.service.impl.FilerUpdateService
+
+
+
+
+
+
@@ -49,6 +79,16 @@
+
+
+ com.atolcd.alfresco.filer.core.service.impl.PropertyInheritanceService
+
+
+
+
+
+
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/util/ApplicationContextAwareTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/util/ApplicationContextAwareTest.java
index 8824c66..0de6d2f 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/util/ApplicationContextAwareTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/util/ApplicationContextAwareTest.java
@@ -8,6 +8,7 @@
@ExtendWith(SpringExtension.class)
@ContextConfiguration({
"classpath:alfresco/application-context.xml",
+ "classpath:context/test-service-context.xml",
"classpath:context/test-model-context.xml",
"classpath:context/test-action-context.xml"
})
diff --git a/alfresco-filer-core/src/test/resources/context/test-service-context.xml b/alfresco-filer-core/src/test/resources/context/test-service-context.xml
new file mode 100644
index 0000000..ac3fd52
--- /dev/null
+++ b/alfresco-filer-core/src/test/resources/context/test-service-context.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
From 677b8fbce32752fa511b419fc3ddf12fa98829d8 Mon Sep 17 00:00:00 2001
From: Corentin PIANA
Date: Fri, 23 Aug 2019 15:11:20 +0200
Subject: [PATCH 14/58] Refactoring node deletion
Transfer code that effectively delete a segment folder from
'FilerOperationService' to 'FilerFolderService'
Change-Id: I650649a21f7732910533f2a291b6861dc2d860ea
---
.../core/service/FilerFolderService.java | 2 ++
.../service/impl/FilerFolderServiceImpl.java | 11 ++++++++
.../impl/FilerOperationServiceImpl.java | 28 ++++++++-----------
3 files changed, 24 insertions(+), 17 deletions(-)
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/FilerFolderService.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/FilerFolderService.java
index 67a829d..4b41e28 100644
--- a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/FilerFolderService.java
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/FilerFolderService.java
@@ -14,5 +14,7 @@ public interface FilerFolderService {
void updateFolder(RepositoryNode node, Consumer onGet, Consumer onCreate);
+ void deleteFolder(NodeRef nodeRef);
+
void lockFolder(NodeRef nodeRef);
}
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerFolderServiceImpl.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerFolderServiceImpl.java
index 2558bd5..8dee5a0 100644
--- a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerFolderServiceImpl.java
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerFolderServiceImpl.java
@@ -64,6 +64,17 @@ private void fetchOrCreateFolderImpl(final RepositoryNode node, final Consumer {
+ nodeService.deleteNode(nodeRef);
+ });
+ }
+ }
+
@Override
public void lockFolder(final NodeRef nodeRef) {
Long nodeId = nodeDAO.getNodePair(nodeRef).getFirst();
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerOperationServiceImpl.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerOperationServiceImpl.java
index d768db6..093aeb3 100644
--- a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerOperationServiceImpl.java
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerOperationServiceImpl.java
@@ -125,32 +125,26 @@ private void deleteSegmentRecursively(final NodeRef nodeRef) {
// Lock it, to prevent any concurrent removal that may fail to find that it became empty
// Indeed, another thread can be deleting the last child but may have not committed yet
filerFolderService.lockFolder(nodeRef);
- // Check that it has no child anymore
- if (nodeService.getChildAssocs(nodeRef, ContentModel.ASSOC_CONTAINS, RegexQNamePattern.MATCH_ALL).isEmpty()) {
- // Node could be part of a hierarchy deletion, in this case it will be deleted that way
- doDeleteSegment(nodeRef);
- }
+ deleteEmptySegment(nodeRef);
}
}
- private void doDeleteSegment(final NodeRef nodeRef) {
- // Get parent nodeRef before deleting child... so the association still exists
- NodeRef parent = nodeService.getPrimaryParent(nodeRef).getParentRef();
- if (!nodeService.hasAspect(nodeRef, ContentModel.ASPECT_PENDING_DELETE)) {
+ private void deleteEmptySegment(final NodeRef nodeRef) {
+ // Check that it has no child anymore
+ if (nodeService.getChildAssocs(nodeRef, ContentModel.ASSOC_CONTAINS, RegexQNamePattern.MATCH_ALL).isEmpty()) {
+ // Get parent nodeRef before deleting child... so the association still exists
+ NodeRef parent = nodeService.getPrimaryParent(nodeRef).getParentRef();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Deleting empty filer segment: {}{node={}, path=\"{}\"}",
nodeService.getProperty(nodeRef, ContentModel.PROP_NAME),
nodeRef.getId(),
nodeService.getPath(nodeRef).toDisplayPath(nodeService, permissionService));
}
- // In case filerSegment is a fileable too
- filerModelService.runWithoutFileableBehaviour(nodeRef, () -> {
- nodeService.deleteNode(nodeRef);
- });
- }
- // Check if parent is now also an empty filer (policies are not applied recursively)
- if (parent != null) {
- deleteSegmentRecursively(parent);
+ filerFolderService.deleteFolder(nodeRef);
+ // Check if parent is now also an empty filer (policies are not applied recursively)
+ if (parent != null) {
+ deleteSegmentRecursively(parent);
+ }
}
}
From 6b2e8d73b9906d57c6fb1f346cdd58573c245330 Mon Sep 17 00:00:00 2001
From: Corentin PIANA
Date: Fri, 23 Aug 2019 15:13:07 +0200
Subject: [PATCH 15/58] Fix: lock for parallel move and rename fileable
Lock the parent node of a fileable node before attempting to move or
rename the fileable node. If the parent is not locked, it could be
deleted be another transaction running simultaneously. This would
result in attempting to move a node in a non existant folder.
Change-Id: I9ef246c3e7d20b9df4c69353e4b7e605705273c0
---
.../main/config/context/service-context.xml | 1 +
.../service/impl/FilerUpdateServiceImpl.java | 8 ++
.../test/domain/AbstractParallelTest.java | 102 ++++++++++++++++++
.../domain/BinaryOperationParallelTest.java | 83 +-------------
...UpdateAndMoveFileableLockParallelTest.java | 55 ++++++++++
5 files changed, 167 insertions(+), 82 deletions(-)
create mode 100644 alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/UpdateAndMoveFileableLockParallelTest.java
diff --git a/alfresco-filer-core/src/main/config/context/service-context.xml b/alfresco-filer-core/src/main/config/context/service-context.xml
index 184f142..f09713e 100644
--- a/alfresco-filer-core/src/main/config/context/service-context.xml
+++ b/alfresco-filer-core/src/main/config/context/service-context.xml
@@ -74,6 +74,7 @@
+
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerUpdateServiceImpl.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerUpdateServiceImpl.java
index 981b7f4..0efe1b9 100644
--- a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerUpdateServiceImpl.java
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerUpdateServiceImpl.java
@@ -16,6 +16,7 @@
import com.atolcd.alfresco.filer.core.model.PropertyInheritancePayload;
import com.atolcd.alfresco.filer.core.model.RepositoryNode;
import com.atolcd.alfresco.filer.core.model.RepositoryNodeDifference;
+import com.atolcd.alfresco.filer.core.service.FilerFolderService;
import com.atolcd.alfresco.filer.core.service.FilerModelService;
import com.atolcd.alfresco.filer.core.service.FilerUpdateService;
import com.atolcd.alfresco.filer.core.service.PropertyInheritanceService;
@@ -25,6 +26,7 @@ public class FilerUpdateServiceImpl extends AbstractBaseCopyService implements F
private static final Logger LOGGER = LoggerFactory.getLogger(FilerUpdateServiceImpl.class);
private FilerModelService filerModelService;
+ private FilerFolderService filerFolderService;
private PropertyInheritanceService propertyInheritanceService;
private NodeService nodeService;
@@ -50,6 +52,8 @@ private void updateAndMoveFileableImpl(final RepositoryNode initialNode, final R
if (!originalDifference.isEmpty()) {
LOGGER.debug("Node updated: " + originalDifference);
}
+ // Lock target segment to prevent its deletion by another transaction
+ filerFolderService.lockFolder(resultingNode.getParent());
// Move and rename node
moveAndRenameFileable(originalNode, resultingNode);
// Update property inheritance on children
@@ -108,6 +112,10 @@ public void setFilerModelService(final FilerModelService filerModelService) {
this.filerModelService = filerModelService;
}
+ public void setFilerFolderService(final FilerFolderService filerFolderService) {
+ this.filerFolderService = filerFolderService;
+ }
+
public void setPropertyInheritanceService(final PropertyInheritanceService propertyInheritanceService) {
this.propertyInheritanceService = propertyInheritanceService;
}
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/AbstractParallelTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/AbstractParallelTest.java
index 6d9d621..030f614 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/AbstractParallelTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/AbstractParallelTest.java
@@ -1,18 +1,30 @@
package com.atolcd.alfresco.filer.core.test.domain;
+import static java.util.UUID.randomUUID;
import static org.assertj.core.api.Assertions.assertThat;
import java.time.LocalDateTime;
import java.time.ZoneId;
+import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+import org.alfresco.model.ContentModel;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.repository.NodeService;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ExecutionMode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import com.atolcd.alfresco.filer.core.model.RepositoryNode;
import com.atolcd.alfresco.filer.core.model.impl.RepositoryNodeBuilder;
import com.atolcd.alfresco.filer.core.test.domain.content.model.FilerTestConstants;
import com.atolcd.alfresco.filer.core.test.util.SiteBasedTest;
@@ -29,8 +41,18 @@
@Execution(ExecutionMode.SAME_THREAD)
public abstract class AbstractParallelTest extends SiteBasedTest {
+ private static final Logger LOGGER = LoggerFactory.getLogger(AbstractParallelTest.class);
+
protected static final int NUM_THREAD_TO_LAUNCH = Runtime.getRuntime().availableProcessors() * 2;
+ protected static final int MAIN_TASK = 1;
+ protected static final int CREATE_TASK = 1;
+ protected static final int UPDATE_TASK = 1;
+ protected static final int DELETE_TASK = 1;
+
+ @Autowired
+ private NodeService nodeService;
+
private final ExecutorService executor = Executors.newFixedThreadPool(NUM_THREAD_TO_LAUNCH);
@BeforeAll
@@ -56,4 +78,84 @@ protected RepositoryNodeBuilder buildNode(final String departmentName, final Loc
.property(FilerTestConstants.Department.Aspect.PROP_NAME, departmentName)
.property(FilerTestConstants.ImportedAspect.PROP_DATE, date.atZone(ZoneId.systemDefault()));
}
+
+ protected void createAndDeleteNodesImpl() throws InterruptedException, BrokenBarrierException {
+ String departmentName = randomUUID().toString();
+ LocalDateTime date = LocalDateTime.of(2004, 8, 12, 0, 0, 0);
+
+ CyclicBarrier startingBarrier = new CyclicBarrier(CREATE_TASK + DELETE_TASK);
+ CyclicBarrier preparationAssertBarrier = new CyclicBarrier(MAIN_TASK + DELETE_TASK);
+ CountDownLatch endingLatch = new CountDownLatch(CREATE_TASK + DELETE_TASK);
+ AtomicReference createdNode = new AtomicReference<>();
+ AtomicReference nodeToDelete = new AtomicReference<>();
+
+ execute(() -> {
+ LOGGER.debug("Create task: task started");
+ RepositoryNode node = buildNode(departmentName, date).build();
+
+ try {
+ // Wait for every task to be ready for launching parallel task execution
+ startingBarrier.await(10, TimeUnit.SECONDS);
+
+ LOGGER.debug("Create task: node creation start");
+ createNode(node);
+ LOGGER.debug("Create task: node creation end");
+ createdNode.set(node);
+ } catch (Exception e) { //NOPMD Catch all exceptions that might occur in thread as they will not be thrown to main thread
+ LOGGER.error("Create task: could not create node", e);
+ }
+ }, endingLatch);
+
+ execute(() -> {
+ LOGGER.debug("Delete task: task started");
+ RepositoryNode node = buildNode(departmentName, date)
+ // Do not archive node, this could generate contention on creating user trashcan
+ .aspect(ContentModel.ASPECT_TEMPORARY)
+ .build();
+
+ try {
+ LOGGER.debug("Delete task: creating node that will be deleted");
+ createNode(node);
+ nodeToDelete.set(node);
+
+ preparationAssertBarrier.await(10, TimeUnit.SECONDS);
+ // Wait for assertion on created nodes taking place in main task
+ preparationAssertBarrier.await(10, TimeUnit.SECONDS);
+
+ // Wait for every task to be ready for launching parallel task execution
+ startingBarrier.await(10, TimeUnit.SECONDS);
+
+ LOGGER.debug("Delete task: node deletion start");
+ deleteNode(node);
+ LOGGER.debug("Delete task: node deletion end");
+ } catch (Exception e) { //NOPMD Catch all exceptions that might occur in thread as they will not be thrown to main thread
+ LOGGER.error("Delete task: could not delete node", e);
+ }
+ }, endingLatch);
+
+ // Wait for node creation to finish and then assert node is indeed created
+ preparationAssertBarrier.await();
+ assertThat(getPath(nodeToDelete.get())).isEqualTo(buildNodePath(departmentName, date));
+ preparationAssertBarrier.await();
+
+ // Wait for every task to finish job before asserting results
+ endingLatch.await();
+
+ LOGGER.debug("All tasks are done, starting assertions");
+
+ // Assert all tasks were ready for parallel task execution
+ assertThat(startingBarrier.isBroken()).isFalse();
+
+ assertThat(getPath(createdNode.get())).isEqualTo(buildNodePath(departmentName, date));
+
+ assertThat(nodeService.exists(nodeToDelete.get().getNodeRef())).isFalse();
+ NodeRef nodeToDeleteParent = nodeToDelete.get().getParent();
+ if (nodeToDeleteParent.equals(createdNode.get().getParent())) {
+ assertThat(nodeService.exists(nodeToDeleteParent)).isTrue();
+ NodeRef nodeToDeleteGrandParent = nodeService.getPrimaryParent(nodeToDeleteParent).getParentRef();
+ assertThat(nodeService.exists(nodeToDeleteGrandParent)).isTrue();
+ } else {
+ assertThat(nodeService.exists(nodeToDeleteParent)).isFalse();
+ }
+ }
}
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/BinaryOperationParallelTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/BinaryOperationParallelTest.java
index e8b78b1..b04c217 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/BinaryOperationParallelTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/BinaryOperationParallelTest.java
@@ -41,11 +41,6 @@ public class BinaryOperationParallelTest extends AbstractParallelTest {
private static final Logger LOGGER = LoggerFactory.getLogger(BinaryOperationParallelTest.class);
- private static final int MAIN_TASK = 1;
- private static final int CREATE_TASK = 1;
- private static final int UPDATE_TASK = 1;
- private static final int DELETE_TASK = 1;
-
@Autowired
private NodeService nodeService;
@@ -62,83 +57,7 @@ public class BinaryOperationParallelTest extends AbstractParallelTest {
*/
@RepeatedTest(10)
public void createAndDeleteNodes() throws InterruptedException, BrokenBarrierException {
- String departmentName = randomUUID().toString();
- LocalDateTime date = LocalDateTime.of(2004, 8, 12, 0, 0, 0);
-
- CyclicBarrier startingBarrier = new CyclicBarrier(CREATE_TASK + DELETE_TASK);
- CyclicBarrier preparationAssertBarrier = new CyclicBarrier(MAIN_TASK + DELETE_TASK);
- CountDownLatch endingLatch = new CountDownLatch(CREATE_TASK + DELETE_TASK);
- AtomicReference createdNode = new AtomicReference<>();
- AtomicReference nodeToDelete = new AtomicReference<>();
-
- execute(() -> {
- LOGGER.debug("Create task: task started");
- RepositoryNode node = buildNode(departmentName, date).build();
-
- try {
- // Wait for every task to be ready for launching parallel task execution
- startingBarrier.await(10, TimeUnit.SECONDS);
-
- LOGGER.debug("Create task: node creation start");
- createNode(node);
- LOGGER.debug("Create task: node creation end");
- createdNode.set(node);
- } catch (Exception e) { //NOPMD Catch all exceptions that might occur in thread as they will not be thrown to main thread
- LOGGER.error("Create task: could not create node", e);
- }
- }, endingLatch);
-
- execute(() -> {
- LOGGER.debug("Delete task: task started");
- RepositoryNode node = buildNode(departmentName, date)
- // Do not archive node, this could generate contention on creating user trashcan
- .aspect(ContentModel.ASPECT_TEMPORARY)
- .build();
-
- try {
- LOGGER.debug("Delete task: creating node that will be deleted");
- createNode(node);
- nodeToDelete.set(node);
-
- preparationAssertBarrier.await(10, TimeUnit.SECONDS);
- // Wait for assertion on created nodes taking place in main task
- preparationAssertBarrier.await(10, TimeUnit.SECONDS);
-
- // Wait for every task to be ready for launching parallel task execution
- startingBarrier.await(10, TimeUnit.SECONDS);
-
- LOGGER.debug("Delete task: node deletion start");
- deleteNode(node);
- LOGGER.debug("Delete task: node deletion end");
- } catch (Exception e) { //NOPMD Catch all exceptions that might occur in thread as they will not be thrown to main thread
- LOGGER.error("Delete task: could not delete node", e);
- }
- }, endingLatch);
-
- // Wait for node creation to finish and then assert node is indeed created
- preparationAssertBarrier.await();
- assertThat(getPath(nodeToDelete.get())).isEqualTo(buildNodePath(departmentName, date));
- preparationAssertBarrier.await();
-
- // Wait for every task to finish job before asserting results
- endingLatch.await();
-
- LOGGER.debug("All tasks are done, starting assertions");
-
- // Assert all tasks were ready for parallel task execution
- assertThat(startingBarrier.isBroken()).isFalse();
-
- assertThat(getPath(createdNode.get())).isEqualTo(buildNodePath(departmentName, date));
-
- assertThat(nodeService.exists(nodeToDelete.get().getNodeRef())).isFalse();
- NodeRef nodeToDeleteParent = nodeToDelete.get().getParent();
- if (nodeToDeleteParent.equals(createdNode.get().getParent())) {
- assertThat(nodeService.exists(nodeToDeleteParent)).isTrue();
- NodeRef nodeToDeleteGrandParent = nodeService.getPrimaryParent(nodeToDeleteParent).getParentRef();
- assertThat(nodeService.exists(nodeToDeleteGrandParent)).isTrue();
- } else {
- assertThat(nodeService.exists(nodeToDeleteParent)).isFalse();
- }
+ createAndDeleteNodesImpl();
}
/**
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/UpdateAndMoveFileableLockParallelTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/UpdateAndMoveFileableLockParallelTest.java
new file mode 100644
index 0000000..a01a541
--- /dev/null
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/UpdateAndMoveFileableLockParallelTest.java
@@ -0,0 +1,55 @@
+package com.atolcd.alfresco.filer.core.test.domain;
+
+import java.util.concurrent.BrokenBarrierException;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mockito;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import com.atolcd.alfresco.filer.core.service.FilerFolderService;
+import com.atolcd.alfresco.filer.core.service.FilerUpdateService;
+import com.atolcd.alfresco.filer.core.test.util.AutowiredMockAwareMockitoExtension;
+
+/**
+ * This test check that the (possibly future) parent node of a fileable node is effectively locked before attempting to move or
+ * rename the fileable node.
+ * If the parent is not locked, it could be deleted by another transaction running simultaneously. This would result in attempting
+ * to move a node in a non existant folder.
+ */
+@ExtendWith(AutowiredMockAwareMockitoExtension.class)
+public class UpdateAndMoveFileableLockParallelTest extends AbstractParallelTest {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(UpdateAndMoveFileableLockParallelTest.class);
+
+ @Autowired
+ private FilerFolderService filerFolderService;
+ @Autowired
+ private FilerUpdateService filerUpdateService;
+
+ @BeforeEach
+ public void setUpSpiedDependencies() {
+ Mockito.doAnswer(invocation -> {
+ LOGGER.debug("filerFolderService.deleteFolder : Waiting before call");
+ TimeUnit.SECONDS.sleep(2);
+ invocation.callRealMethod();
+ return null;
+ }).when(filerFolderService).deleteFolder(Mockito.any());
+
+ Mockito.doAnswer(invocation -> {
+ LOGGER.debug("filerUpdateService.updateAndMoveFileable : Waiting before call");
+ TimeUnit.SECONDS.sleep(1);
+ invocation.callRealMethod();
+ return null;
+ }).when(filerUpdateService).updateAndMoveFileable(Mockito.any(), Mockito.any(), Mockito.any());
+ }
+
+ @Test
+ public void createAndDeleteNodes() throws InterruptedException, BrokenBarrierException {
+ createAndDeleteNodesImpl();
+ }
+}
From 4b8c4e813cfc8ddd33f3ae748e3eaa7278beab86 Mon Sep 17 00:00:00 2001
From: Corentin PIANA
Date: Fri, 23 Aug 2019 15:21:30 +0200
Subject: [PATCH 16/58] Fix: check if node exists before trying to lock it
If the node does not exist, the transaction is retried.
Change-Id: I8ba538366860d9e10fb0c9cbd6adabe1be68f655
---
.../service/impl/FilerFolderServiceImpl.java | 10 +++-
.../domain/LockFolderNodeParallelTest.java | 53 +++++++++++++++++++
2 files changed, 62 insertions(+), 1 deletion(-)
create mode 100644 alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/LockFolderNodeParallelTest.java
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerFolderServiceImpl.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerFolderServiceImpl.java
index 8dee5a0..73356dc 100644
--- a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerFolderServiceImpl.java
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerFolderServiceImpl.java
@@ -11,6 +11,8 @@
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
+import org.alfresco.util.Pair;
+import org.springframework.dao.ConcurrencyFailureException;
import com.atolcd.alfresco.filer.core.model.FilerException;
import com.atolcd.alfresco.filer.core.model.RepositoryNode;
@@ -77,7 +79,13 @@ public void deleteFolder(final NodeRef nodeRef) {
@Override
public void lockFolder(final NodeRef nodeRef) {
- Long nodeId = nodeDAO.getNodePair(nodeRef).getFirst();
+ Pair nodePair = nodeDAO.getNodePair(nodeRef);
+ // Node could have been deleted in another concurrent transaction
+ if (nodePair == null) {
+ throw new ConcurrencyFailureException("Could not lock node. Node does not exist: " + nodeRef);
+ }
+
+ Long nodeId = nodePair.getFirst();
filerModelService.runWithoutBehaviours(nodeRef, () -> {
// This will effectively lock the node preventing other transactions to go further
// They will be blocked here and when they become free, they will throw a ConcurrencyFailureException
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/LockFolderNodeParallelTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/LockFolderNodeParallelTest.java
new file mode 100644
index 0000000..2d25d05
--- /dev/null
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/LockFolderNodeParallelTest.java
@@ -0,0 +1,53 @@
+package com.atolcd.alfresco.filer.core.test.domain;
+
+import java.util.concurrent.BrokenBarrierException;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mockito;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import com.atolcd.alfresco.filer.core.service.FilerOperationService;
+import com.atolcd.alfresco.filer.core.service.FilerUpdateService;
+import com.atolcd.alfresco.filer.core.test.util.AutowiredMockAwareMockitoExtension;
+
+/**
+ * During node creation or updating, when trying to lock the parent node, this parent one could have been already deleted in
+ * another transaction running simultaneously. This would result in failing to acquire the lock and put the code out of action.
+ */
+@ExtendWith(AutowiredMockAwareMockitoExtension.class)
+public class LockFolderNodeParallelTest extends AbstractParallelTest {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(LockFolderNodeParallelTest.class);
+
+ @Autowired
+ private FilerOperationService filerOperationService;
+ @Autowired
+ private FilerUpdateService filerUpdateService;
+
+ @BeforeEach
+ public void setUpSpiedDependencies() {
+ Mockito.doAnswer(invocation -> {
+ LOGGER.debug("filerOperationService.deleteSegment : Waiting before call");
+ TimeUnit.SECONDS.sleep(1);
+ invocation.callRealMethod();
+ return null;
+ }).when(filerOperationService).deleteSegment(Mockito.any());
+
+ Mockito.doAnswer(invocation -> {
+ LOGGER.debug("filerUpdateService.updateAndMoveFileable : Waiting before call");
+ TimeUnit.SECONDS.sleep(2);
+ invocation.callRealMethod();
+ return null;
+ }).when(filerUpdateService).updateAndMoveFileable(Mockito.any(), Mockito.any(), Mockito.any());
+ }
+
+ @Test
+ public void createAndDeleteNodes() throws InterruptedException, BrokenBarrierException {
+ createAndDeleteNodesImpl();
+ }
+}
From 963947a56ff6f77d86fbfd0fafdb365d48776637 Mon Sep 17 00:00:00 2001
From: Corentin PIANA
Date: Mon, 26 Aug 2019 18:12:34 +0200
Subject: [PATCH 17/58] Fix: parallel creation of node with duplicate name
During parallel node creation or updating, name generation could produce
duplicate node name.
If that the case, we catch the corresponding exception and retry the
transaction by throwing a 'ConcurrencyFailureException'.
Change-Id: I4cf9270dee95e4a7f92e5233caba6a8af2b6a649
---
.../service/impl/FilerUpdateServiceImpl.java | 16 +++-
.../domain/DuplicateNameParallelTest.java | 88 +++++++++++++++++++
.../test/util/TransactionalBasedTest.java | 20 +++--
3 files changed, 115 insertions(+), 9 deletions(-)
create mode 100644 alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DuplicateNameParallelTest.java
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerUpdateServiceImpl.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerUpdateServiceImpl.java
index 0efe1b9..2b56275 100644
--- a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerUpdateServiceImpl.java
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerUpdateServiceImpl.java
@@ -6,12 +6,14 @@
import org.alfresco.model.ContentModel;
import org.alfresco.repo.copy.AbstractBaseCopyService;
+import org.alfresco.service.cmr.repository.DuplicateChildNodeNameException;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.namespace.QName;
import org.alfresco.util.GUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.springframework.dao.ConcurrencyFailureException;
import com.atolcd.alfresco.filer.core.model.PropertyInheritancePayload;
import com.atolcd.alfresco.filer.core.model.RepositoryNode;
@@ -79,7 +81,19 @@ private void moveAndRenameFileable(final RepositoryNode originalNode, final Repo
AssociationCopyInfo targetInfo = getAssociationCopyInfo(nodeService, nodeRef, originalNode.getParent(), name, nameChanged);
QName typeQName = targetInfo.getSourceParentAssoc().getTypeQName();
nodeService.moveNode(nodeRef, resultingNode.getParent(), typeQName, targetInfo.getTargetAssocQName());
- nodeService.setProperty(nodeRef, ContentModel.PROP_NAME, name);
+ // During concurrent node update, name generation can produce multiple identical node names.
+ // If the problem occurs, we catch the specific exception related to duplicate node name
+ // and throw a ConcurrencyFailureException which will cause a retry of the whole transaction
+ // in RetryingTransactionHelper, that will run a name generation.
+ try {
+ nodeService.setProperty(nodeRef, ContentModel.PROP_NAME, name);
+ } catch (DuplicateChildNodeNameException e) {
+ // We only pass the cause of the DuplicateChildNodeNameException to the ConcurrencyFailureException
+ // because DuplicateChildNodeNameException implements DoNotRetryException which would not trigger
+ // the retrying transaction mechanism.
+ throw new ConcurrencyFailureException("Could not rename node to: " + name, e.getCause()); // NOPMD - Preserve stack trace:
+ // above comment
+ }
}
}
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DuplicateNameParallelTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DuplicateNameParallelTest.java
new file mode 100644
index 0000000..0ec0eac
--- /dev/null
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DuplicateNameParallelTest.java
@@ -0,0 +1,88 @@
+package com.atolcd.alfresco.filer.core.test.domain;
+
+import static java.util.UUID.randomUUID;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.BrokenBarrierException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.CyclicBarrier;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.alfresco.model.ContentModel;
+import org.alfresco.service.cmr.repository.NodeService;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import com.atolcd.alfresco.filer.core.model.RepositoryNode;
+
+public class DuplicateNameParallelTest extends AbstractParallelTest {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(DuplicateNameParallelTest.class);
+
+ @Autowired
+ private NodeService nodeService;
+
+ @Test
+ public void createAndDeleteNodes() throws InterruptedException, BrokenBarrierException {
+ String departmentName = randomUUID().toString();
+ LocalDateTime date = LocalDateTime.of(2004, 8, 12, 0, 0, 0);
+
+ CyclicBarrier startingBarrier = new CyclicBarrier(NUM_THREAD_TO_LAUNCH);
+ CountDownLatch endingLatch = new CountDownLatch(NUM_THREAD_TO_LAUNCH);
+ List results = Collections.synchronizedList(new ArrayList<>());
+ AtomicInteger nodeNameSuffix = new AtomicInteger();
+
+ for (int i = 0; i < NUM_THREAD_TO_LAUNCH; i++) {
+ final int taskNumber = i;
+ execute(() -> {
+ LOGGER.debug("Task {}: task started", taskNumber);
+ RepositoryNode node = buildNode(departmentName, date).build();
+
+ try {
+ // Wait for every task to be ready for launching parallel task execution
+ startingBarrier.await(10, TimeUnit.SECONDS);
+
+ LOGGER.debug("Task {}: node creation start", taskNumber);
+ // Name generation and node creation must be in the same transaction as the whole transaction execution will be retried
+ // when there is an attempt to create nodes with duplicate name.
+ doInTransaction(() -> {
+ // Generate name based on a variable shared between all test threads
+ node.getProperties().put(ContentModel.PROP_NAME, "x" + Integer.toString(nodeNameSuffix.get()));
+ createNodeImpl(node);
+ });
+ LOGGER.debug("Task {}: node creation end", taskNumber);
+
+ results.add(node);
+
+ // Wait before incrementing suffix to let more chance to other thread to generate duplicate node name
+ TimeUnit.MILLISECONDS.sleep(250);
+ nodeNameSuffix.getAndIncrement();
+ } catch (Exception e) { //NOPMD Catch all exceptions that might occur in thread as they will not be thrown to main thread
+ LOGGER.error("Task " + taskNumber + ": could not create node", e);
+ }
+ }, endingLatch);
+ }
+
+ // Wait for every task to finish job before asserting results
+ endingLatch.await();
+
+ LOGGER.debug("All tasks are done, starting assertions");
+
+ // Assert all tasks were ready for parallel task execution
+ assertThat(startingBarrier.isBroken()).isFalse();
+
+ assertThat(results.stream().map(RepositoryNode::getName).map(Optional::get))
+ .doesNotHaveDuplicates();
+ assertThat(results.stream().map(RepositoryNode::getNodeRef).map(nodeService::exists))
+ .hasSize(NUM_THREAD_TO_LAUNCH)
+ .containsOnly(true);
+ }
+}
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/util/TransactionalBasedTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/util/TransactionalBasedTest.java
index b0c5752..ac96ce7 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/util/TransactionalBasedTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/util/TransactionalBasedTest.java
@@ -31,17 +31,21 @@ public void clearAuthorization() {
protected final void createNode(final RepositoryNode node) {
doInTransaction(() -> {
- QName assocQName = QName.createQNameWithValidLocalName(NamespaceService.CONTENT_MODEL_1_0_URI, node.getName().get());
- NodeRef nodeRef = nodeService
- .createNode(node.getParent(), ContentModel.ASSOC_CONTAINS, assocQName, node.getType(), node.getProperties())
- .getChildRef();
- node.getAspects().forEach(aspect -> nodeService.addAspect(nodeRef, aspect, null));
-
- node.setNodeRef(nodeRef);
- bindTransactionListener(node);
+ createNodeImpl(node);
});
}
+ protected void createNodeImpl(final RepositoryNode node) {
+ QName assocQName = QName.createQNameWithValidLocalName(NamespaceService.CONTENT_MODEL_1_0_URI, node.getName().get());
+ NodeRef nodeRef = nodeService
+ .createNode(node.getParent(), ContentModel.ASSOC_CONTAINS, assocQName, node.getType(), node.getProperties())
+ .getChildRef();
+ node.getAspects().forEach(aspect -> nodeService.addAspect(nodeRef, aspect, null));
+
+ node.setNodeRef(nodeRef);
+ bindTransactionListener(node);
+ }
+
protected final void fetchNode(final RepositoryNode node) {
doInTransaction(() -> {
fetchNodeImpl(node);
From 6b66708e638468059099582a364d9c90dd84cbd7 Mon Sep 17 00:00:00 2001
From: Corentin PIANA
Date: Tue, 27 Aug 2019 17:50:55 +0200
Subject: [PATCH 18/58] Testing database transaction isolation
The test trigger an integrity violation. Some database (e.g. H2Database)
are failing at this test even with highest level of isolation.
Change-Id: If6cd73d346260a1fa0c71e54eb908c85df2827c7
---
.../DatabaseTransactionIsolationTest.java | 55 +++++++++++++++++++
1 file changed, 55 insertions(+)
create mode 100644 alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DatabaseTransactionIsolationTest.java
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DatabaseTransactionIsolationTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DatabaseTransactionIsolationTest.java
new file mode 100644
index 0000000..69fc49a
--- /dev/null
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DatabaseTransactionIsolationTest.java
@@ -0,0 +1,55 @@
+package com.atolcd.alfresco.filer.core.test.domain;
+
+import java.util.concurrent.BrokenBarrierException;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mockito;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import com.atolcd.alfresco.filer.core.service.FilerOperationService;
+import com.atolcd.alfresco.filer.core.test.util.AutowiredMockAwareMockitoExtension;
+
+/**
+ * Verify database's transaction isolation
+ *
+ *
+ * This test were to failed when trying H2database for testing, before using PostgreSQL for this very reason.
+ * Transactions were not rollbacked even though an integrity violation was triggered by this test case.
+ * Highest level of isolation available with H2database did not solve the problem.
+ *
+ */
+@ExtendWith(AutowiredMockAwareMockitoExtension.class)
+public class DatabaseTransactionIsolationTest extends AbstractParallelTest {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(DatabaseTransactionIsolationTest.class);
+
+ @Autowired
+ private FilerOperationService filerOperationService;
+
+ @BeforeEach
+ public void setUpSpiedDependencies() {
+ Mockito.doAnswer(invocation -> {
+ LOGGER.debug("filerOperationService.deleteFilerSegment : Waiting before call");
+ TimeUnit.SECONDS.sleep(1);
+ invocation.callRealMethod();
+ return null;
+ }).when(filerOperationService).deleteSegment(Mockito.any());
+
+ Mockito.doAnswer(invocation -> {
+ invocation.callRealMethod();
+ LOGGER.debug("filerOperationService.execute : Waiting after call");
+ TimeUnit.SECONDS.sleep(2);
+ return null;
+ }).when(filerOperationService).execute(Mockito.any(), Mockito.any());
+ }
+
+ @Test
+ public void createAndDeleteNodes() throws InterruptedException, BrokenBarrierException {
+ createAndDeleteNodesImpl();
+ }
+}
From bb72363ec960096c4ae50b6e8cab0bfd065f2d1e Mon Sep 17 00:00:00 2001
From: Corentin PIANA
Date: Thu, 29 Aug 2019 15:52:14 +0200
Subject: [PATCH 19/58] Test: Utility class for random NodeRef
Build valid NodeRef with random ID in chosed Sapces store
Change-Id: Ica55c2d90d47e1ce2c36cab0ad4ab21ecadc46af
---
.../filer/core/test/util/NodeRefUtils.java | 23 +++++++++++++++++++
1 file changed, 23 insertions(+)
create mode 100644 alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/util/NodeRefUtils.java
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/util/NodeRefUtils.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/util/NodeRefUtils.java
new file mode 100644
index 0000000..40c6eff
--- /dev/null
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/util/NodeRefUtils.java
@@ -0,0 +1,23 @@
+package com.atolcd.alfresco.filer.core.test.util;
+
+import java.util.UUID;
+
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.repository.StoreRef;
+
+public final class NodeRefUtils {
+
+ public static NodeRef randomNodeRef() {
+ return randomWorkspaceSpacesStoreNodeRef();
+ }
+
+ public static NodeRef randomWorkspaceSpacesStoreNodeRef() {
+ return new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, UUID.randomUUID().toString());
+ }
+
+ public static NodeRef randomArchiveSpacesStoreNodeRef() {
+ return new NodeRef(StoreRef.STORE_REF_ARCHIVE_SPACESSTORE, UUID.randomUUID().toString());
+ }
+
+ private NodeRefUtils() {}
+}
From dede851940e319ca7ada68059d5b69f13b374027 Mon Sep 17 00:00:00 2001
From: Corentin PIANA
Date: Thu, 29 Aug 2019 15:54:33 +0200
Subject: [PATCH 20/58] Unit test of FilerBuilder
Check:
- file hierarchy can be created
- creation conditions work as expected
- properties inheritance
Change-Id: Iae8400a455e8ed7c86a3fdb0d677092d091690fb
---
.../service/AbstractFilerBuilderTest.java | 80 ++++++++
.../FilerBuilderConditionReverseTest.java | 65 ++++++
.../FilerBuilderMultipleConditionTest.java | 104 ++++++++++
...rBuilderSingleConditionPlusFolderTest.java | 65 ++++++
.../FilerBuilderSingleConditionTest.java | 59 ++++++
.../core/test/service/FilerBuilderTest.java | 138 +++++++++++++
.../service/impl/FilerFolderBuilderTest.java | 117 +++++++++++
.../impl/FilerFolderTypeBuilderTest.java | 189 ++++++++++++++++++
8 files changed, 817 insertions(+)
create mode 100644 alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/AbstractFilerBuilderTest.java
create mode 100644 alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/FilerBuilderConditionReverseTest.java
create mode 100644 alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/FilerBuilderMultipleConditionTest.java
create mode 100644 alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/FilerBuilderSingleConditionPlusFolderTest.java
create mode 100644 alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/FilerBuilderSingleConditionTest.java
create mode 100644 alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/FilerBuilderTest.java
create mode 100644 alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/impl/FilerFolderBuilderTest.java
create mode 100644 alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/impl/FilerFolderTypeBuilderTest.java
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/AbstractFilerBuilderTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/AbstractFilerBuilderTest.java
new file mode 100644
index 0000000..1475a0c
--- /dev/null
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/AbstractFilerBuilderTest.java
@@ -0,0 +1,80 @@
+package com.atolcd.alfresco.filer.core.test.service;
+
+import static com.atolcd.alfresco.filer.core.test.util.NodeRefUtils.randomNodeRef;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+
+import java.util.List;
+
+import org.alfresco.model.ContentModel;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.site.SiteService;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.mockito.verification.VerificationMode;
+
+import com.atolcd.alfresco.filer.core.service.FilerOperationService;
+import com.atolcd.alfresco.filer.core.service.FilerService;
+import com.atolcd.alfresco.filer.core.service.impl.FilerFolderBuilder;
+
+@ExtendWith(MockitoExtension.class)
+public class AbstractFilerBuilderTest {
+
+ protected static final String TEST_PROP_TITLE_A = "prop_title_A";
+ protected static final String TEST_PROP_TITLE_B = "prop_title_B";
+
+ @Mock
+ private FilerService filerService;
+ @Mock
+ private FilerOperationService filerOperationService;
+
+ @Captor
+ private ArgumentCaptor nodeRefCaptor;
+
+ @BeforeEach
+ public void initMock() {
+ Mockito.when(filerService.operations()).thenReturn(filerOperationService);
+ Mockito.when(filerOperationService.getFolder(any(), any(), any()))
+ .thenReturn(randomNodeRef());
+ }
+
+ protected void stubCreateFolder() {
+ Mockito.when(filerOperationService.getOrCreateFolder(nodeRefCaptor.capture(), any(), any(), any(), any()))
+ .thenReturn(randomNodeRef());
+ }
+
+ protected void verifyCreateFolder(final VerificationMode times, final String folderName) {
+ Mockito.verify(filerOperationService, times).getOrCreateFolder(any(), eq(ContentModel.TYPE_FOLDER), eq(folderName), any(),
+ any());
+ }
+
+ protected void verifyCreateFolder(final VerificationMode times, final String folderName, final NodeRef parent) {
+ Mockito.verify(filerOperationService, times).getOrCreateFolder(eq(parent), eq(ContentModel.TYPE_FOLDER), eq(folderName),
+ any(), any());
+ }
+
+ protected static FilerFolderBuilder buildDocumentLibrary(final FilerFolderBuilder filerFolderBuilder) {
+ return filerFolderBuilder.folder().named().with(SiteService.DOCUMENT_LIBRARY).get();
+ }
+
+ public FilerService getFilerService() {
+ return filerService;
+ }
+
+ public FilerOperationService getFilerOperationService() {
+ return filerOperationService;
+ }
+
+ protected NodeRef getCaptedParentNodeRefValue() {
+ return nodeRefCaptor.getValue();
+ }
+
+ protected List getAllCaptedParentNodeRefValues() {
+ return nodeRefCaptor.getAllValues();
+ }
+}
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/FilerBuilderConditionReverseTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/FilerBuilderConditionReverseTest.java
new file mode 100644
index 0000000..2d9f973
--- /dev/null
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/FilerBuilderConditionReverseTest.java
@@ -0,0 +1,65 @@
+package com.atolcd.alfresco.filer.core.test.service;
+
+import static com.atolcd.alfresco.filer.core.test.util.NodeRefUtils.randomNodeRef;
+import static java.util.UUID.randomUUID;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+
+import org.alfresco.model.ContentModel;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import com.atolcd.alfresco.filer.core.model.RepositoryNode;
+import com.atolcd.alfresco.filer.core.service.impl.FilerBuilder;
+import com.atolcd.alfresco.filer.core.service.impl.FilerFolderBuilder;
+
+public class FilerBuilderConditionReverseTest extends AbstractFilerBuilderTest {
+
+ private String conditionFolderName;
+ private String reverseFolderName;
+
+ @BeforeEach
+ public void folderPrecondition() {
+ stubCreateFolder();
+
+ conditionFolderName = randomUUID().toString();
+ reverseFolderName = randomUUID().toString();
+ }
+
+ @Test
+ public void conditionTrue() {
+ RepositoryNode node = RepositoryNode.builder()
+ .property(ContentModel.PROP_TITLE, TEST_PROP_TITLE_A)
+ .build();
+ FilerBuilder builder = new FilerBuilder(getFilerService(), node);
+
+ builder.root(randomNodeRef())
+ .tree(this::buildConditionTree);
+
+ verifyCreateFolder(times(1), conditionFolderName, getCaptedParentNodeRefValue());
+ verifyCreateFolder(never(), reverseFolderName, getCaptedParentNodeRefValue());
+ }
+
+ @Test
+ public void conditionFalse() {
+ RepositoryNode node = RepositoryNode.builder()
+ .property(ContentModel.PROP_TITLE, TEST_PROP_TITLE_B)
+ .build();
+ FilerBuilder builder = new FilerBuilder(getFilerService(), node);
+
+ builder.root(randomNodeRef())
+ .tree(this::buildConditionTree);
+
+ verifyCreateFolder(never(), conditionFolderName, getCaptedParentNodeRefValue());
+ verifyCreateFolder(times(1), reverseFolderName, getCaptedParentNodeRefValue());
+ }
+
+ private FilerFolderBuilder buildConditionTree(final FilerFolderBuilder filerFolderBuilder) {
+ return filerFolderBuilder
+ .tree(AbstractFilerBuilderTest::buildDocumentLibrary)
+ .condition(x -> x.getProperty(ContentModel.PROP_TITLE, String.class).equals(TEST_PROP_TITLE_A))
+ .folder().asSegment().named().with(conditionFolderName).getOrCreate()
+ .conditionReverse()
+ .folder().asSegment().named().with(reverseFolderName).getOrCreate();
+ }
+}
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/FilerBuilderMultipleConditionTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/FilerBuilderMultipleConditionTest.java
new file mode 100644
index 0000000..07694f6
--- /dev/null
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/FilerBuilderMultipleConditionTest.java
@@ -0,0 +1,104 @@
+package com.atolcd.alfresco.filer.core.test.service;
+
+import static com.atolcd.alfresco.filer.core.test.util.NodeRefUtils.randomNodeRef;
+import static java.util.UUID.randomUUID;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+
+import org.alfresco.model.ContentModel;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import com.atolcd.alfresco.filer.core.model.RepositoryNode;
+import com.atolcd.alfresco.filer.core.service.impl.FilerBuilder;
+import com.atolcd.alfresco.filer.core.service.impl.FilerFolderBuilder;
+
+public class FilerBuilderMultipleConditionTest extends AbstractFilerBuilderTest {
+
+ private static final String TEST_PROP_DESCRIPTION_A = "prop_desc_A";
+ private static final String TEST_PROP_DESCRIPTION_B = "prop_desc_B";
+
+ private String firstFolderName;
+ private String secondFolderName;
+
+ @BeforeEach
+ public void folderPrecondition() {
+ firstFolderName = randomUUID().toString();
+ secondFolderName = randomUUID().toString();
+ }
+
+ @Test
+ public void firstFalseSecondFalse() {
+ RepositoryNode node = RepositoryNode.builder()
+ .property(ContentModel.PROP_TITLE, TEST_PROP_TITLE_B)
+ .property(ContentModel.PROP_DESCRIPTION, TEST_PROP_DESCRIPTION_B)
+ .build();
+ FilerBuilder builder = new FilerBuilder(getFilerService(), node);
+
+ builder.root(randomNodeRef())
+ .tree(this::buildMultipleConditionTree);
+
+ verifyCreateFolder(never(), firstFolderName);
+ verifyCreateFolder(never(), secondFolderName);
+ }
+
+ @Test
+ public void firstFalseSecondTrue() {
+ stubCreateFolder();
+
+ RepositoryNode node = RepositoryNode.builder()
+ .property(ContentModel.PROP_TITLE, TEST_PROP_TITLE_B)
+ .property(ContentModel.PROP_DESCRIPTION, TEST_PROP_DESCRIPTION_A)
+ .build();
+ FilerBuilder builder = new FilerBuilder(getFilerService(), node);
+
+ builder.root(randomNodeRef())
+ .tree(this::buildMultipleConditionTree);
+
+ verifyCreateFolder(never(), firstFolderName, getCaptedParentNodeRefValue());
+ verifyCreateFolder(times(1), secondFolderName, getCaptedParentNodeRefValue());
+ }
+
+ @Test
+ public void firstTrueSecondFalse() {
+ stubCreateFolder();
+
+ RepositoryNode node = RepositoryNode.builder()
+ .property(ContentModel.PROP_TITLE, TEST_PROP_TITLE_A)
+ .property(ContentModel.PROP_DESCRIPTION, TEST_PROP_DESCRIPTION_B)
+ .build();
+ FilerBuilder builder = new FilerBuilder(getFilerService(), node);
+
+ builder.root(randomNodeRef())
+ .tree(this::buildMultipleConditionTree);
+
+ verifyCreateFolder(times(1), firstFolderName, getCaptedParentNodeRefValue());
+ verifyCreateFolder(never(), secondFolderName, getCaptedParentNodeRefValue());
+ }
+
+ @Test
+ public void firstTrueSecondTrue() {
+ stubCreateFolder();
+
+ RepositoryNode node = RepositoryNode.builder()
+ .property(ContentModel.PROP_TITLE, TEST_PROP_TITLE_A)
+ .property(ContentModel.PROP_DESCRIPTION, TEST_PROP_DESCRIPTION_A)
+ .build();
+ FilerBuilder builder = new FilerBuilder(getFilerService(), node);
+
+ builder.root(randomNodeRef())
+ .tree(this::buildMultipleConditionTree);
+
+ verifyCreateFolder(times(1), firstFolderName, getAllCaptedParentNodeRefValues().get(0));
+ verifyCreateFolder(times(1), secondFolderName, getAllCaptedParentNodeRefValues().get(1));
+ }
+
+ private FilerFolderBuilder buildMultipleConditionTree(final FilerFolderBuilder filerFolderBuilder) {
+ return filerFolderBuilder
+ .tree(AbstractFilerBuilderTest::buildDocumentLibrary)
+ .condition(x -> x.getProperty(ContentModel.PROP_TITLE, String.class).equals(TEST_PROP_TITLE_A))
+ .folder().asSegment().named().with(firstFolderName).getOrCreate()
+ .condition(x -> x.getProperty(ContentModel.PROP_DESCRIPTION, String.class).equals(TEST_PROP_DESCRIPTION_A))
+ .folder().asSegment().named().with(secondFolderName).getOrCreate();
+ }
+}
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/FilerBuilderSingleConditionPlusFolderTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/FilerBuilderSingleConditionPlusFolderTest.java
new file mode 100644
index 0000000..7e443ca
--- /dev/null
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/FilerBuilderSingleConditionPlusFolderTest.java
@@ -0,0 +1,65 @@
+package com.atolcd.alfresco.filer.core.test.service;
+
+import static com.atolcd.alfresco.filer.core.test.util.NodeRefUtils.randomNodeRef;
+import static java.util.UUID.randomUUID;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+
+import org.alfresco.model.ContentModel;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import com.atolcd.alfresco.filer.core.model.RepositoryNode;
+import com.atolcd.alfresco.filer.core.service.impl.FilerBuilder;
+import com.atolcd.alfresco.filer.core.service.impl.FilerFolderBuilder;
+
+public class FilerBuilderSingleConditionPlusFolderTest extends AbstractFilerBuilderTest {
+
+ private String conditionFolderName;
+ private String folderName;
+
+ @BeforeEach
+ public void folderPrecondition() {
+ stubCreateFolder();
+
+ conditionFolderName = randomUUID().toString();
+ folderName = randomUUID().toString();
+ }
+
+ @Test
+ public void conditionTrue() {
+ RepositoryNode node = RepositoryNode.builder()
+ .property(ContentModel.PROP_TITLE, TEST_PROP_TITLE_A)
+ .build();
+ FilerBuilder builder = new FilerBuilder(getFilerService(), node);
+
+ builder.root(randomNodeRef())
+ .tree(this::buildConditionPlusFolderTree);
+
+ verifyCreateFolder(times(1), conditionFolderName, getAllCaptedParentNodeRefValues().get(0));
+ verifyCreateFolder(times(1), folderName, getAllCaptedParentNodeRefValues().get(1));
+ }
+
+ @Test
+ public void conditionFalse() {
+ RepositoryNode node = RepositoryNode.builder()
+ .property(ContentModel.PROP_TITLE, TEST_PROP_TITLE_B)
+ .build();
+ FilerBuilder builder = new FilerBuilder(getFilerService(), node);
+
+ builder.root(randomNodeRef())
+ .tree(this::buildConditionPlusFolderTree);
+
+ verifyCreateFolder(never(), conditionFolderName, getCaptedParentNodeRefValue());
+ verifyCreateFolder(times(1), folderName, getCaptedParentNodeRefValue());
+ }
+
+ private FilerFolderBuilder buildConditionPlusFolderTree(final FilerFolderBuilder filerFolderBuilder) {
+ return filerFolderBuilder
+ .tree(AbstractFilerBuilderTest::buildDocumentLibrary)
+ .condition(x -> x.getProperty(ContentModel.PROP_TITLE, String.class).equals(TEST_PROP_TITLE_A))
+ .folder().asSegment().named().with(conditionFolderName).getOrCreate()
+ .conditionEnd()
+ .folder().asSegment().named().with(folderName).getOrCreate();
+ }
+}
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/FilerBuilderSingleConditionTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/FilerBuilderSingleConditionTest.java
new file mode 100644
index 0000000..7725122
--- /dev/null
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/FilerBuilderSingleConditionTest.java
@@ -0,0 +1,59 @@
+package com.atolcd.alfresco.filer.core.test.service;
+
+import static com.atolcd.alfresco.filer.core.test.util.NodeRefUtils.randomNodeRef;
+import static java.util.UUID.randomUUID;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+
+import org.alfresco.model.ContentModel;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import com.atolcd.alfresco.filer.core.model.RepositoryNode;
+import com.atolcd.alfresco.filer.core.service.impl.FilerBuilder;
+import com.atolcd.alfresco.filer.core.service.impl.FilerFolderBuilder;
+
+public class FilerBuilderSingleConditionTest extends AbstractFilerBuilderTest {
+
+ private String folderName;
+
+ @BeforeEach
+ public void folderPrecondition() {
+ folderName = randomUUID().toString();
+ }
+
+ @Test
+ public void conditionTrue() {
+ stubCreateFolder();
+
+ RepositoryNode node = RepositoryNode.builder()
+ .property(ContentModel.PROP_TITLE, TEST_PROP_TITLE_A)
+ .build();
+ FilerBuilder builder = new FilerBuilder(getFilerService(), node);
+
+ builder.root(randomNodeRef())
+ .tree(this::buildConditionTree);
+
+ verifyCreateFolder(times(1), folderName, getCaptedParentNodeRefValue());
+ }
+
+ @Test
+ public void conditionFalse() {
+ RepositoryNode node = RepositoryNode.builder()
+ .property(ContentModel.PROP_TITLE, TEST_PROP_TITLE_B)
+ .build();
+ FilerBuilder builder = new FilerBuilder(getFilerService(), node);
+
+ builder.root(randomNodeRef())
+ .tree(this::buildConditionTree);
+
+ verifyCreateFolder(never(), folderName);
+ }
+
+ private FilerFolderBuilder buildConditionTree(final FilerFolderBuilder filerFolderBuilder) {
+ return filerFolderBuilder
+ .tree(AbstractFilerBuilderTest::buildDocumentLibrary)
+ .condition(x -> x.getProperty(ContentModel.PROP_TITLE, String.class).equals(TEST_PROP_TITLE_A))
+ .folder().asSegment().named().with(folderName).getOrCreate();
+ }
+}
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/FilerBuilderTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/FilerBuilderTest.java
new file mode 100644
index 0000000..c73c7ab
--- /dev/null
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/FilerBuilderTest.java
@@ -0,0 +1,138 @@
+package com.atolcd.alfresco.filer.core.test.service;
+
+import static com.atolcd.alfresco.filer.core.test.util.NodeRefUtils.randomNodeRef;
+import static java.util.UUID.randomUUID;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.refEq;
+import static org.mockito.Mockito.times;
+
+import java.util.Locale;
+import java.util.function.Consumer;
+
+import org.alfresco.model.ContentModel;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.site.SiteService;
+import org.alfresco.service.namespace.QName;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+
+import com.atolcd.alfresco.filer.core.model.PropertyInheritance;
+import com.atolcd.alfresco.filer.core.model.RepositoryNode;
+import com.atolcd.alfresco.filer.core.service.PropertyInheritanceService;
+import com.atolcd.alfresco.filer.core.service.impl.FilerBuilder;
+
+public class FilerBuilderTest extends AbstractFilerBuilderTest {
+
+ @Mock
+ private PropertyInheritanceService propertyInheritanceService;
+
+ @Test
+ public void documentLibraryFolder() {
+ NodeRef rootNodeRef = randomNodeRef();
+ FilerBuilder builder = new FilerBuilder(getFilerService(), new RepositoryNode());
+
+ builder.root(rootNodeRef)
+ .tree(AbstractFilerBuilderTest::buildDocumentLibrary);
+
+ Mockito.verify(getFilerOperationService()).getFolder(eq(rootNodeRef), eq(SiteService.DOCUMENT_LIBRARY), any());
+ }
+
+ @Test
+ public void nodeInDocumentLibrary() {
+ RepositoryNode node = RepositoryNode.builder()
+ .named(randomUUID())
+ .build();
+ FilerBuilder builder = new FilerBuilder(getFilerService(), node);
+
+ NodeRef documentLibraryNode = randomNodeRef();
+ Mockito.when(getFilerOperationService().getFolder(any(), any(), any())).thenReturn(documentLibraryNode);
+
+ builder.root(randomNodeRef())
+ .tree(AbstractFilerBuilderTest::buildDocumentLibrary)
+ .updateAndMove();
+
+ Mockito.verify(getFilerOperationService()).updateFileable(node, documentLibraryNode, node.getName().get());
+ }
+
+ @Test
+ public void folderWithNameBasedOnNodeProperty() {
+ stubCreateFolder();
+
+ RepositoryNode node = RepositoryNode.builder()
+ .property(ContentModel.PROP_TITLE, "x" + randomUUID())
+ .build();
+ FilerBuilder builder = new FilerBuilder(getFilerService(), node);
+
+ builder.root(randomNodeRef())
+ .tree(AbstractFilerBuilderTest::buildDocumentLibrary)
+ .folder().asSegment()
+ // A..Z
+ .named().with(x -> {
+ String name = x.getProperty(ContentModel.PROP_TITLE, String.class);
+ return name.substring(0, 1).toUpperCase(Locale.getDefault());
+ }).getOrCreate();
+
+ verifyCreateFolder(times(1), "X", getCaptedParentNodeRefValue());
+ }
+
+ @Test
+ public void multipleFolder() {
+ stubCreateFolder();
+
+ FilerBuilder builder = new FilerBuilder(getFilerService(), new RepositoryNode());
+
+ String firstFolderName = randomUUID().toString();
+ String secondFolderName = randomUUID().toString();
+
+ builder.root(randomNodeRef())
+ .tree(AbstractFilerBuilderTest::buildDocumentLibrary)
+ .folder().asSegment()
+ .named().with(firstFolderName).getOrCreate()
+ .folder().asSegment()
+ .named().with(secondFolderName).getOrCreate();
+
+ verifyCreateFolder(times(1), firstFolderName, getAllCaptedParentNodeRefValues().get(0));
+ verifyCreateFolder(times(1), secondFolderName, getAllCaptedParentNodeRefValues().get(1));
+ }
+
+ @Test
+ public void propertyInheritence() {
+ stubCreateFolder();
+
+ Mockito.when(getFilerService().propertyInheritance()).thenReturn(propertyInheritanceService);
+
+ RepositoryNode node = RepositoryNode.builder()
+ .build();
+ FilerBuilder builder = new FilerBuilder(getFilerService(), node);
+
+ String name = randomUUID().toString();
+ QName aspectMandatory = ContentModel.ASPECT_TAGGABLE;
+ QName aspectOptional = ContentModel.ASPECT_INCOMPLETE;
+
+ builder.root(randomNodeRef())
+ .tree(AbstractFilerBuilderTest::buildDocumentLibrary)
+ .folder().asSegment()
+ .mandatoryPropertyInheritance(aspectMandatory)
+ .optionalPropertyInheritance(aspectOptional)
+ .named().with(name).getOrCreate();
+
+ ArgumentCaptor> captor = buildNodeRefConsumerCaptor();
+ Mockito.verify(getFilerOperationService()).getOrCreateFolder(eq(getCaptedParentNodeRefValue()), eq(ContentModel.TYPE_FOLDER),
+ eq(name), any(), captor.capture());
+
+ captor.getValue().accept(randomNodeRef());
+
+ PropertyInheritance expectedPropertyInheritance = new PropertyInheritance();
+ expectedPropertyInheritance.getMandatoryAspects().add(aspectMandatory);
+ expectedPropertyInheritance.getOptionalAspects().add(aspectOptional);
+ Mockito.verify(propertyInheritanceService).setProperties(any(), any(), refEq(expectedPropertyInheritance));
+ }
+
+ @SuppressWarnings("unchecked")
+ private static ArgumentCaptor> buildNodeRefConsumerCaptor() {
+ return ArgumentCaptor.forClass(Consumer.class);
+ }
+}
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/impl/FilerFolderBuilderTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/impl/FilerFolderBuilderTest.java
new file mode 100644
index 0000000..737c824
--- /dev/null
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/impl/FilerFolderBuilderTest.java
@@ -0,0 +1,117 @@
+package com.atolcd.alfresco.filer.core.test.service.impl;
+
+import static com.atolcd.alfresco.filer.core.test.util.NodeRefUtils.randomNodeRef;
+import static java.util.UUID.randomUUID;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import com.atolcd.alfresco.filer.core.model.FilerFolderContext;
+import com.atolcd.alfresco.filer.core.model.RepositoryNode;
+import com.atolcd.alfresco.filer.core.service.FilerService;
+import com.atolcd.alfresco.filer.core.service.impl.FilerFolderBuilder;
+
+// Could be executed in parallel but Mockito JUnit Jupiter extension does not correctly support parallel test execution yet
+// See https://github.com/mockito/mockito/issues/1630
+//@Execution(ExecutionMode.CONCURRENT)
+@ExtendWith(MockitoExtension.class)
+public class FilerFolderBuilderTest {
+
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private FilerService filerService;
+
+ @Test
+ public void condition() {
+ FilerFolderBuilder filerFolderBuilder = new FilerFolderBuilder(filerService, new FilerFolderContext(new RepositoryNode()),
+ randomNodeRef());
+
+ filerFolderBuilder.condition(x -> false);
+ assertThat(filerFolderBuilder.getContext().isEnabled()).isFalse();
+
+ filerFolderBuilder.condition(x -> true);
+ assertThat(filerFolderBuilder.getContext().isEnabled()).isTrue();
+ }
+
+ @Test
+ public void conditionReverse() {
+ FilerFolderBuilder filerFolderBuilder = new FilerFolderBuilder(filerService, new FilerFolderContext(new RepositoryNode()),
+ randomNodeRef());
+
+ filerFolderBuilder.conditionReverse();
+ assertThat(filerFolderBuilder.getContext().isEnabled()).isFalse();
+
+ filerFolderBuilder.conditionReverse();
+ assertThat(filerFolderBuilder.getContext().isEnabled()).isTrue();
+ }
+
+ @Test
+ public void conditionEnd() {
+ FilerFolderContext context = new FilerFolderContext(new RepositoryNode());
+ context.enable(false);
+
+ FilerFolderBuilder filerFolderBuilder = new FilerFolderBuilder(filerService, context,
+ randomNodeRef());
+
+ filerFolderBuilder.conditionEnd();
+ assertThat(filerFolderBuilder.getContext().isEnabled()).isTrue();
+ }
+
+ @Test
+ public void updateAndMoveWithContextEnabled() {
+ FilerFolderBuilder filerFolderBuilder = new FilerFolderBuilder(filerService, new FilerFolderContext(new RepositoryNode()),
+ randomNodeRef());
+
+ filerFolderBuilder.rename().with(randomUUID().toString());
+
+ filerFolderBuilder.updateAndMove();
+
+ Mockito.verify(filerService).operations();
+ Mockito.verify(filerService.operations()).updateFileable(Mockito.any(), Mockito.any(), Mockito.any());
+ }
+
+ @Test
+ public void updateAndMoveWithContextDisabled() {
+ FilerFolderContext context = new FilerFolderContext(new RepositoryNode());
+ context.enable(false);
+
+ FilerFolderBuilder filerFolderBuilder = new FilerFolderBuilder(filerService, context,
+ randomNodeRef());
+
+ filerFolderBuilder.updateAndMove();
+
+ Mockito.verifyZeroInteractions(filerService);
+ }
+
+ @Test
+ public void contextWithContextEnabled() {
+ FilerFolderBuilder filerFolderBuilder = new FilerFolderBuilder(filerService, new FilerFolderContext(new RepositoryNode()),
+ randomNodeRef());
+
+ NodeRef nodeRef = randomNodeRef();
+
+ filerFolderBuilder.contextFrom(context -> context.getNode().setNodeRef(nodeRef));
+
+ assertThat(filerFolderBuilder.getContext().getNode().getNodeRef()).isEqualTo(nodeRef);
+ }
+
+ @Test
+ public void contextWithContextDisabled() {
+ FilerFolderContext contextDisabled = new FilerFolderContext(new RepositoryNode());
+ contextDisabled.enable(false);
+
+ FilerFolderBuilder filerFolderBuilder = new FilerFolderBuilder(filerService, contextDisabled,
+ randomNodeRef());
+
+ NodeRef nodeRef = randomNodeRef();
+
+ filerFolderBuilder.contextFrom(context -> context.getNode().setNodeRef(nodeRef));
+
+ assertThat(filerFolderBuilder.getContext().getNode().getNodeRef()).isNotEqualTo(nodeRef);
+ }
+}
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/impl/FilerFolderTypeBuilderTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/impl/FilerFolderTypeBuilderTest.java
new file mode 100644
index 0000000..ae61956
--- /dev/null
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/impl/FilerFolderTypeBuilderTest.java
@@ -0,0 +1,189 @@
+package com.atolcd.alfresco.filer.core.test.service.impl;
+
+import static java.util.UUID.randomUUID;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import org.alfresco.model.ContentModel;
+import org.alfresco.service.namespace.QName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import com.atolcd.alfresco.filer.core.model.FilerFolderContext;
+import com.atolcd.alfresco.filer.core.model.RepositoryNode;
+import com.atolcd.alfresco.filer.core.service.FilerService;
+import com.atolcd.alfresco.filer.core.service.impl.FilerFolderTypeBuilder;
+
+// Could be executed in parallel but Mockito JUnit Jupiter extension does not correctly support parallel test execution yet
+// See https://github.com/mockito/mockito/issues/1630
+//@Execution(ExecutionMode.CONCURRENT)
+@ExtendWith(MockitoExtension.class)
+public class FilerFolderTypeBuilderTest {
+
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private FilerService filerService;
+
+ @Test
+ public void getOrCreateWithContextEnabled() { //NOPMD - name: not a getter
+ FilerFolderTypeBuilder filerFolderTypeBuilder = new FilerFolderTypeBuilder(filerService,
+ new FilerFolderContext(new RepositoryNode()), ContentModel.TYPE_FOLDER);
+
+ filerFolderTypeBuilder.named().with(randomUUID().toString());
+
+ filerFolderTypeBuilder.getOrCreate();
+
+ verify(filerService).operations();
+ verify(filerService.operations()).getOrCreateFolder(any(), any(), any(), any(), any());
+ }
+
+ @Test
+ public void getOrCreateWithContextDisabled() { //NOPMD - name: not a getter
+ FilerFolderContext context = new FilerFolderContext(new RepositoryNode());
+ context.enable(false);
+
+ FilerFolderTypeBuilder filerFolderTypeBuilder = new FilerFolderTypeBuilder(filerService, context, ContentModel.TYPE_FOLDER);
+
+ filerFolderTypeBuilder.getOrCreate();
+
+ verifyZeroInteractions(filerService);
+ }
+
+ @Test
+ public void getWithContextEnabled() { //NOPMD - name: not a getter
+ FilerFolderTypeBuilder filerFolderTypeBuilder = new FilerFolderTypeBuilder(filerService,
+ new FilerFolderContext(new RepositoryNode()), ContentModel.TYPE_FOLDER);
+
+ filerFolderTypeBuilder.named().with(randomUUID().toString());
+
+ filerFolderTypeBuilder.get();
+
+ verify(filerService).operations();
+ verify(filerService.operations()).getFolder(any(), any(), any());
+ }
+
+ @Test
+ public void getWithContextDisabled() { //NOPMD - name: not a getter
+ FilerFolderContext context = new FilerFolderContext(new RepositoryNode());
+ context.enable(false);
+
+ FilerFolderTypeBuilder filerFolderTypeBuilder = new FilerFolderTypeBuilder(filerService, context, ContentModel.TYPE_FOLDER);
+
+ filerFolderTypeBuilder.get();
+
+ verifyZeroInteractions(filerService);
+ }
+
+ @Test
+ public void updateAndMoveWithContextEnabled() {
+ FilerFolderTypeBuilder filerFolderTypeBuilder = new FilerFolderTypeBuilder(filerService,
+ new FilerFolderContext(new RepositoryNode()), ContentModel.TYPE_FOLDER);
+
+ filerFolderTypeBuilder.named().with(randomUUID().toString());
+
+ filerFolderTypeBuilder.updateAndMove();
+
+ verify(filerService, Mockito.times(2)).operations();
+ verify(filerService.operations()).updateFileable(any(), any(), any());
+ verify(filerService.operations()).updateFolder(any(), any(), any());
+ }
+
+ @Test
+ public void updateAndMoveWithContextDisabled() {
+ FilerFolderContext context = new FilerFolderContext(new RepositoryNode());
+ context.enable(false);
+
+ FilerFolderTypeBuilder filerFolderTypeBuilder = new FilerFolderTypeBuilder(filerService, context, ContentModel.TYPE_FOLDER);
+
+ filerFolderTypeBuilder.updateAndMove();
+
+ verifyZeroInteractions(filerService);
+ }
+
+ @Test
+ public void addingPropertyInheritanceWithContextEnabled() {
+ FilerFolderTypeBuilder filerFolderTypeBuilder = new FilerFolderTypeBuilder(filerService,
+ new FilerFolderContext(new RepositoryNode()), ContentModel.TYPE_FOLDER);
+
+ QName aspect = ContentModel.ASPECT_WORKING_COPY;
+
+ filerFolderTypeBuilder.mandatoryPropertyInheritance(aspect);
+ filerFolderTypeBuilder.optionalPropertyInheritance(aspect);
+
+ // FilerFolderTypeBuilder does not provide a method to directly get context.
+ // We use get() method to get a filerFolderBuilder and get context from it.
+ filerFolderTypeBuilder.named().with(randomUUID().toString());
+ FilerFolderContext context = filerFolderTypeBuilder.get().getContext();
+
+ assertThat(context.getPropertyInheritance().getMandatoryAspects()).contains(aspect);
+ assertThat(context.getPropertyInheritance().getOptionalAspects()).contains(aspect);
+ }
+
+ @Test
+ public void addingPropertyInheritanceWithContextDisabled() {
+ FilerFolderContext contextDisabled = new FilerFolderContext(new RepositoryNode());
+ contextDisabled.enable(false);
+
+ FilerFolderTypeBuilder filerFolderTypeBuilder = new FilerFolderTypeBuilder(filerService, contextDisabled,
+ ContentModel.TYPE_FOLDER);
+
+ QName aspect = ContentModel.ASPECT_WORKING_COPY;
+
+ filerFolderTypeBuilder.mandatoryPropertyInheritance(aspect);
+ filerFolderTypeBuilder.optionalPropertyInheritance(aspect);
+
+ // FilerFolderTypeBuilder does not provide a method to directly get context.
+ // We use get() method to get a filerFolderBuilder and get context from it.
+ FilerFolderContext context = filerFolderTypeBuilder.get().getContext();
+
+ assertThat(context.getPropertyInheritance().getMandatoryAspects()).doesNotContain(aspect);
+ assertThat(context.getPropertyInheritance().getOptionalAspects()).doesNotContain(aspect);
+ }
+
+ @Test
+ public void clearingPropertyInheritanceWithContextEnabled() {
+ QName aspect = ContentModel.ASPECT_WORKING_COPY;
+
+ FilerFolderContext initialContext = new FilerFolderContext(new RepositoryNode());
+ initialContext.getPropertyInheritance().getMandatoryAspects().add(aspect);
+ initialContext.getPropertyInheritance().getOptionalAspects().add(aspect);
+
+ FilerFolderTypeBuilder filerFolderTypeBuilder = new FilerFolderTypeBuilder(filerService, initialContext,
+ ContentModel.TYPE_FOLDER);
+
+ filerFolderTypeBuilder.clearPropertyInheritance();
+
+ // FilerFolderTypeBuilder does not provide a method to directly get context.
+ // We use get() method to get a filerFolderBuilder and get context from it.
+ filerFolderTypeBuilder.named().with(randomUUID().toString());
+ FilerFolderContext context = filerFolderTypeBuilder.get().getContext();
+
+ assertThat(context.getPropertyInheritance().getMandatoryAspects()).isEmpty();
+ assertThat(context.getPropertyInheritance().getOptionalAspects()).isEmpty();
+ }
+
+ @Test
+ public void clearingPropertyInheritanceWithContextDisabled() {
+ QName aspect = ContentModel.ASPECT_WORKING_COPY;
+
+ FilerFolderContext initialContext = new FilerFolderContext(new RepositoryNode());
+ initialContext.getPropertyInheritance().getMandatoryAspects().add(aspect);
+ initialContext.getPropertyInheritance().getOptionalAspects().add(aspect);
+ initialContext.enable(false);
+
+ FilerFolderTypeBuilder filerFolderTypeBuilder = new FilerFolderTypeBuilder(filerService, initialContext,
+ ContentModel.TYPE_FOLDER);
+
+ // FilerFolderTypeBuilder does not provide a method to directly get context.
+ // We use get() method to get a filerFolderBuilder and get context from it.
+ FilerFolderContext context = filerFolderTypeBuilder.get().getContext();
+
+ assertThat(context.getPropertyInheritance().getMandatoryAspects()).contains(aspect);
+ assertThat(context.getPropertyInheritance().getOptionalAspects()).contains(aspect);
+ }
+}
From 513c4b849598174eff6a58c0ea7e4f76456e3879 Mon Sep 17 00:00:00 2001
From: Corentin PIANA
Date: Mon, 2 Sep 2019 16:12:44 +0200
Subject: [PATCH 21/58] Test package layout & context as meta-annotation
Base test classes, utility classes and JUnit extensions lie in package
'Framework'.
Application context is now carried by a meta-annotation instead of an
interface (Still 'ApplicationContextAwareTest').
Also renaming context startup test methods.
Change-Id: Iec5d6baaf158f2f2bc67026b5e80b8bec6409130
---
.../filer/core/test/content/ContextStartupTest.java | 9 +++++----
.../filer/core/test/domain/AbstractParallelTest.java | 2 +-
.../test/domain/DatabaseTransactionIsolationTest.java | 2 +-
.../filer/core/test/domain/DeniedFilerActionTest.java | 2 +-
.../test/domain/DepartmentContentFilerActionTest.java | 9 +++++----
.../test/domain/DepartmentFolderFilerActionTest.java | 2 +-
.../core/test/domain/LockFolderNodeParallelTest.java | 2 +-
.../core/test/domain/UnaryOperationParallelTest.java | 2 +-
.../domain/UpdateAndMoveFileableLockParallelTest.java | 2 +-
.../ApplicationContextAwareTest.java | 11 +++++++++--
.../AutowiredMockAwareMockitoExtension.java | 2 +-
.../test/{util => framework}/PostgreSQLExtension.java | 2 +-
.../core/test/{util => framework}/SiteBasedTest.java | 2 +-
.../{util => framework}/TransactionalBasedTest.java | 5 +++--
.../core/test/{ => framework}/util/NodeRefUtils.java | 2 +-
.../core/test/service/AbstractFilerBuilderTest.java | 2 +-
.../service/FilerBuilderConditionReverseTest.java | 2 +-
.../service/FilerBuilderMultipleConditionTest.java | 2 +-
.../FilerBuilderSingleConditionPlusFolderTest.java | 2 +-
.../test/service/FilerBuilderSingleConditionTest.java | 2 +-
.../filer/core/test/service/FilerBuilderTest.java | 2 +-
.../test/service/impl/FilerFolderBuilderTest.java | 2 +-
22 files changed, 40 insertions(+), 30 deletions(-)
rename alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/{util => framework}/ApplicationContextAwareTest.java (59%)
rename alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/{util => framework}/AutowiredMockAwareMockitoExtension.java (97%)
rename alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/{util => framework}/PostgreSQLExtension.java (98%)
rename alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/{util => framework}/SiteBasedTest.java (98%)
rename alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/{util => framework}/TransactionalBasedTest.java (96%)
rename alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/{ => framework}/util/NodeRefUtils.java (91%)
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/content/ContextStartupTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/content/ContextStartupTest.java
index 6a25563..9a4405d 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/content/ContextStartupTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/content/ContextStartupTest.java
@@ -17,10 +17,11 @@
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronizationManager;
-import com.atolcd.alfresco.filer.core.test.util.ApplicationContextAwareTest;
+import com.atolcd.alfresco.filer.core.test.framework.ApplicationContextAwareTest;
+@ApplicationContextAwareTest
@Transactional
-public class ContextStartupTest implements ApplicationContextAwareTest {
+public class ContextStartupTest {
@Autowired
@Qualifier("NodeService")
@@ -30,12 +31,12 @@ public class ContextStartupTest implements ApplicationContextAwareTest {
private AuthenticationComponent authenticationComponent;
@BeforeEach
- public void setUpTest() {
+ public void initAuthorization() {
authenticationComponent.setSystemUserAsCurrentUser();
}
@AfterEach
- public void tearDown() {
+ public void clearAuthorization() {
authenticationComponent.clearCurrentSecurityContext();
}
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/AbstractParallelTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/AbstractParallelTest.java
index 030f614..22212a6 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/AbstractParallelTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/AbstractParallelTest.java
@@ -27,7 +27,7 @@
import com.atolcd.alfresco.filer.core.model.RepositoryNode;
import com.atolcd.alfresco.filer.core.model.impl.RepositoryNodeBuilder;
import com.atolcd.alfresco.filer.core.test.domain.content.model.FilerTestConstants;
-import com.atolcd.alfresco.filer.core.test.util.SiteBasedTest;
+import com.atolcd.alfresco.filer.core.test.framework.SiteBasedTest;
/**
* Provide base class for parallel tests of {@linkplain com.atolcd.alfresco.filer.core.model.FilerAction Filer actions}. Assert
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DatabaseTransactionIsolationTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DatabaseTransactionIsolationTest.java
index 69fc49a..42c1d52 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DatabaseTransactionIsolationTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DatabaseTransactionIsolationTest.java
@@ -12,7 +12,7 @@
import org.springframework.beans.factory.annotation.Autowired;
import com.atolcd.alfresco.filer.core.service.FilerOperationService;
-import com.atolcd.alfresco.filer.core.test.util.AutowiredMockAwareMockitoExtension;
+import com.atolcd.alfresco.filer.core.test.framework.AutowiredMockAwareMockitoExtension;
/**
* Verify database's transaction isolation
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DeniedFilerActionTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DeniedFilerActionTest.java
index b2bf979..93c7fbb 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DeniedFilerActionTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DeniedFilerActionTest.java
@@ -14,7 +14,7 @@
import com.atolcd.alfresco.filer.core.model.FilerException;
import com.atolcd.alfresco.filer.core.model.RepositoryNode;
import com.atolcd.alfresco.filer.core.service.FilerModelService;
-import com.atolcd.alfresco.filer.core.test.util.SiteBasedTest;
+import com.atolcd.alfresco.filer.core.test.framework.SiteBasedTest;
public class DeniedFilerActionTest extends SiteBasedTest {
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DepartmentContentFilerActionTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DepartmentContentFilerActionTest.java
index 8d67c9e..fbc6dce 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DepartmentContentFilerActionTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DepartmentContentFilerActionTest.java
@@ -21,8 +21,8 @@
import com.atolcd.alfresco.filer.core.model.RepositoryNode;
import com.atolcd.alfresco.filer.core.service.FilerModelService;
import com.atolcd.alfresco.filer.core.test.domain.content.model.FilerTestConstants;
-import com.atolcd.alfresco.filer.core.test.util.ApplicationContextAwareTest;
-import com.atolcd.alfresco.filer.core.test.util.SiteBasedTest;
+import com.atolcd.alfresco.filer.core.test.framework.ApplicationContextAwareTest;
+import com.atolcd.alfresco.filer.core.test.framework.SiteBasedTest;
public class DepartmentContentFilerActionTest extends SiteBasedTest {
@@ -33,9 +33,10 @@ public class DepartmentContentFilerActionTest extends SiteBasedTest {
private NodeService nodeService;
@Nested
- // Re-implement ApplicationContextAwareTest as Spring does not find the configuration of nested class from the enclosing class
+ // @ApplicationContextAwareTest is necessary as Spring does not find the configuration of nested class from the enclosing class
// See https://github.com/spring-projects/spring-framework/issues/19930
- public class DepartmentDocument implements ApplicationContextAwareTest {
+ @ApplicationContextAwareTest
+ public class DepartmentDocument {
@Test
public void filerAspectHierarchy() {
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DepartmentFolderFilerActionTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DepartmentFolderFilerActionTest.java
index 14c69a0..0d5be47 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DepartmentFolderFilerActionTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DepartmentFolderFilerActionTest.java
@@ -14,7 +14,7 @@
import com.atolcd.alfresco.filer.core.model.RepositoryNode;
import com.atolcd.alfresco.filer.core.test.domain.content.model.FilerTestConstants;
-import com.atolcd.alfresco.filer.core.test.util.SiteBasedTest;
+import com.atolcd.alfresco.filer.core.test.framework.SiteBasedTest;
public class DepartmentFolderFilerActionTest extends SiteBasedTest {
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/LockFolderNodeParallelTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/LockFolderNodeParallelTest.java
index 2d25d05..275e0c4 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/LockFolderNodeParallelTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/LockFolderNodeParallelTest.java
@@ -13,7 +13,7 @@
import com.atolcd.alfresco.filer.core.service.FilerOperationService;
import com.atolcd.alfresco.filer.core.service.FilerUpdateService;
-import com.atolcd.alfresco.filer.core.test.util.AutowiredMockAwareMockitoExtension;
+import com.atolcd.alfresco.filer.core.test.framework.AutowiredMockAwareMockitoExtension;
/**
* During node creation or updating, when trying to lock the parent node, this parent one could have been already deleted in
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/UnaryOperationParallelTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/UnaryOperationParallelTest.java
index a526e9a..023ab5d 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/UnaryOperationParallelTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/UnaryOperationParallelTest.java
@@ -29,7 +29,7 @@
import com.atolcd.alfresco.filer.core.model.RepositoryNode;
import com.atolcd.alfresco.filer.core.test.domain.content.model.FilerTestConstants;
-import com.atolcd.alfresco.filer.core.test.util.SiteBasedTest;
+import com.atolcd.alfresco.filer.core.test.framework.SiteBasedTest;
/**
* Test multiple executions in parallel of one operation
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/UpdateAndMoveFileableLockParallelTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/UpdateAndMoveFileableLockParallelTest.java
index a01a541..b75d151 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/UpdateAndMoveFileableLockParallelTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/UpdateAndMoveFileableLockParallelTest.java
@@ -13,7 +13,7 @@
import com.atolcd.alfresco.filer.core.service.FilerFolderService;
import com.atolcd.alfresco.filer.core.service.FilerUpdateService;
-import com.atolcd.alfresco.filer.core.test.util.AutowiredMockAwareMockitoExtension;
+import com.atolcd.alfresco.filer.core.test.framework.AutowiredMockAwareMockitoExtension;
/**
* This test check that the (possibly future) parent node of a fileable node is effectively locked before attempting to move or
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/util/ApplicationContextAwareTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/ApplicationContextAwareTest.java
similarity index 59%
rename from alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/util/ApplicationContextAwareTest.java
rename to alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/ApplicationContextAwareTest.java
index 0de6d2f..06be280 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/util/ApplicationContextAwareTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/ApplicationContextAwareTest.java
@@ -1,9 +1,16 @@
-package com.atolcd.alfresco.filer.core.test.util;
+package com.atolcd.alfresco.filer.core.test.framework;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
@ExtendWith(PostgreSQLExtension.class)
@ExtendWith(SpringExtension.class)
@ContextConfiguration({
@@ -12,4 +19,4 @@
"classpath:context/test-model-context.xml",
"classpath:context/test-action-context.xml"
})
-public interface ApplicationContextAwareTest {}
+public @interface ApplicationContextAwareTest {}
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/util/AutowiredMockAwareMockitoExtension.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/AutowiredMockAwareMockitoExtension.java
similarity index 97%
rename from alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/util/AutowiredMockAwareMockitoExtension.java
rename to alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/AutowiredMockAwareMockitoExtension.java
index 4580115..a60e356 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/util/AutowiredMockAwareMockitoExtension.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/AutowiredMockAwareMockitoExtension.java
@@ -1,4 +1,4 @@
-package com.atolcd.alfresco.filer.core.test.util;
+package com.atolcd.alfresco.filer.core.test.framework;
import java.lang.reflect.Field;
import java.util.ArrayList;
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/util/PostgreSQLExtension.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/PostgreSQLExtension.java
similarity index 98%
rename from alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/util/PostgreSQLExtension.java
rename to alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/PostgreSQLExtension.java
index ec926c2..e4058d9 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/util/PostgreSQLExtension.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/PostgreSQLExtension.java
@@ -1,4 +1,4 @@
-package com.atolcd.alfresco.filer.core.test.util;
+package com.atolcd.alfresco.filer.core.test.framework;
import java.io.IOException;
import java.io.UncheckedIOException;
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/util/SiteBasedTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/SiteBasedTest.java
similarity index 98%
rename from alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/util/SiteBasedTest.java
rename to alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/SiteBasedTest.java
index 909c75c..6f40bb6 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/util/SiteBasedTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/SiteBasedTest.java
@@ -1,4 +1,4 @@
-package com.atolcd.alfresco.filer.core.test.util;
+package com.atolcd.alfresco.filer.core.test.framework;
import static java.util.UUID.randomUUID;
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/util/TransactionalBasedTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/TransactionalBasedTest.java
similarity index 96%
rename from alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/util/TransactionalBasedTest.java
rename to alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/TransactionalBasedTest.java
index ac96ce7..8ebb1b7 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/util/TransactionalBasedTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/TransactionalBasedTest.java
@@ -1,4 +1,4 @@
-package com.atolcd.alfresco.filer.core.test.util;
+package com.atolcd.alfresco.filer.core.test.framework;
import java.io.Serializable;
import java.util.Map;
@@ -17,7 +17,8 @@
import com.atolcd.alfresco.filer.core.model.RepositoryNode;
-public class TransactionalBasedTest implements ApplicationContextAwareTest {
+@ApplicationContextAwareTest
+public class TransactionalBasedTest {
@Autowired
private TransactionService transactionService;
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/util/NodeRefUtils.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/util/NodeRefUtils.java
similarity index 91%
rename from alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/util/NodeRefUtils.java
rename to alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/util/NodeRefUtils.java
index 40c6eff..a9c89e1 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/util/NodeRefUtils.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/util/NodeRefUtils.java
@@ -1,4 +1,4 @@
-package com.atolcd.alfresco.filer.core.test.util;
+package com.atolcd.alfresco.filer.core.test.framework.util;
import java.util.UUID;
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/AbstractFilerBuilderTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/AbstractFilerBuilderTest.java
index 1475a0c..ff001ea 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/AbstractFilerBuilderTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/AbstractFilerBuilderTest.java
@@ -1,6 +1,6 @@
package com.atolcd.alfresco.filer.core.test.service;
-import static com.atolcd.alfresco.filer.core.test.util.NodeRefUtils.randomNodeRef;
+import static com.atolcd.alfresco.filer.core.test.framework.util.NodeRefUtils.randomNodeRef;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/FilerBuilderConditionReverseTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/FilerBuilderConditionReverseTest.java
index 2d9f973..bbd837c 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/FilerBuilderConditionReverseTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/FilerBuilderConditionReverseTest.java
@@ -1,6 +1,6 @@
package com.atolcd.alfresco.filer.core.test.service;
-import static com.atolcd.alfresco.filer.core.test.util.NodeRefUtils.randomNodeRef;
+import static com.atolcd.alfresco.filer.core.test.framework.util.NodeRefUtils.randomNodeRef;
import static java.util.UUID.randomUUID;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/FilerBuilderMultipleConditionTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/FilerBuilderMultipleConditionTest.java
index 07694f6..cae11a9 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/FilerBuilderMultipleConditionTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/FilerBuilderMultipleConditionTest.java
@@ -1,6 +1,6 @@
package com.atolcd.alfresco.filer.core.test.service;
-import static com.atolcd.alfresco.filer.core.test.util.NodeRefUtils.randomNodeRef;
+import static com.atolcd.alfresco.filer.core.test.framework.util.NodeRefUtils.randomNodeRef;
import static java.util.UUID.randomUUID;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/FilerBuilderSingleConditionPlusFolderTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/FilerBuilderSingleConditionPlusFolderTest.java
index 7e443ca..5a2cf72 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/FilerBuilderSingleConditionPlusFolderTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/FilerBuilderSingleConditionPlusFolderTest.java
@@ -1,6 +1,6 @@
package com.atolcd.alfresco.filer.core.test.service;
-import static com.atolcd.alfresco.filer.core.test.util.NodeRefUtils.randomNodeRef;
+import static com.atolcd.alfresco.filer.core.test.framework.util.NodeRefUtils.randomNodeRef;
import static java.util.UUID.randomUUID;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/FilerBuilderSingleConditionTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/FilerBuilderSingleConditionTest.java
index 7725122..5813166 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/FilerBuilderSingleConditionTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/FilerBuilderSingleConditionTest.java
@@ -1,6 +1,6 @@
package com.atolcd.alfresco.filer.core.test.service;
-import static com.atolcd.alfresco.filer.core.test.util.NodeRefUtils.randomNodeRef;
+import static com.atolcd.alfresco.filer.core.test.framework.util.NodeRefUtils.randomNodeRef;
import static java.util.UUID.randomUUID;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/FilerBuilderTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/FilerBuilderTest.java
index c73c7ab..cfcfea6 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/FilerBuilderTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/FilerBuilderTest.java
@@ -1,6 +1,6 @@
package com.atolcd.alfresco.filer.core.test.service;
-import static com.atolcd.alfresco.filer.core.test.util.NodeRefUtils.randomNodeRef;
+import static com.atolcd.alfresco.filer.core.test.framework.util.NodeRefUtils.randomNodeRef;
import static java.util.UUID.randomUUID;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/impl/FilerFolderBuilderTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/impl/FilerFolderBuilderTest.java
index 737c824..ec549e2 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/impl/FilerFolderBuilderTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/impl/FilerFolderBuilderTest.java
@@ -1,6 +1,6 @@
package com.atolcd.alfresco.filer.core.test.service.impl;
-import static com.atolcd.alfresco.filer.core.test.util.NodeRefUtils.randomNodeRef;
+import static com.atolcd.alfresco.filer.core.test.framework.util.NodeRefUtils.randomNodeRef;
import static java.util.UUID.randomUUID;
import static org.assertj.core.api.Assertions.assertThat;
From 47e55bc015dd7e959296e8d7c15960fa25e75cd5 Mon Sep 17 00:00:00 2001
From: Corentin PIANA
Date: Tue, 3 Sep 2019 17:35:17 +0200
Subject: [PATCH 22/58] Test of 'AutowiredMockAwareMockitoExtension'
Import of Junit platform testkit
Change-Id: Icea4706df00944802aee60c8f95d80e94582cdc0
---
alfresco-filer-core/pom.xml | 4 +
...utowiredMockAwareMockitoExtensionTest.java | 205 ++++++++++++++++++
.../context/test-mocking-context.xml | 12 +
alfresco-filer-parent/pom.xml | 7 +
4 files changed, 228 insertions(+)
create mode 100644 alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/extension/AutowiredMockAwareMockitoExtensionTest.java
create mode 100644 alfresco-filer-core/src/test/resources/context/test-mocking-context.xml
diff --git a/alfresco-filer-core/pom.xml b/alfresco-filer-core/pom.xml
index 6ab649a..ee4bd1f 100644
--- a/alfresco-filer-core/pom.xml
+++ b/alfresco-filer-core/pom.xml
@@ -92,6 +92,10 @@
javax.servletjavax.servlet-api
+
+ org.junit.platform
+ junit-platform-testkit
+
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/extension/AutowiredMockAwareMockitoExtensionTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/extension/AutowiredMockAwareMockitoExtensionTest.java
new file mode 100644
index 0000000..3fa2552
--- /dev/null
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/extension/AutowiredMockAwareMockitoExtensionTest.java
@@ -0,0 +1,205 @@
+package com.atolcd.alfresco.filer.core.test.extension;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation;
+import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
+import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.Optional;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestMethodOrder;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.engine.JupiterTestEngine;
+import org.junit.platform.engine.DiscoverySelector;
+import org.junit.platform.launcher.LauncherDiscoveryRequest;
+import org.junit.platform.testkit.engine.EngineExecutionResults;
+import org.junit.platform.testkit.engine.EngineTestKit;
+import org.junit.platform.testkit.engine.Events;
+import org.mockito.Mockito;
+import org.mockito.exceptions.misusing.UnnecessaryStubbingException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import com.atolcd.alfresco.filer.core.test.framework.AutowiredMockAwareMockitoExtension;
+
+public class AutowiredMockAwareMockitoExtensionTest {
+
+ private final JupiterTestEngine engine = new JupiterTestEngine();
+
+ @Test
+ public void necessaryStubbing() {
+ Events testEvents = executeTestsForClass(NecessaryStubbingTestClass.class).tests();
+
+ assertThat(testEvents.started().count()).isEqualTo(2);
+ assertThat(testEvents.succeeded().count()).isEqualTo(2);
+ assertThat(testEvents.skipped().count()).isEqualTo(0);
+ assertThat(testEvents.aborted().count()).isEqualTo(0);
+ assertThat(testEvents.failed().count()).isEqualTo(0);
+ }
+
+ @ExpectedExceptionAutowiredMockAwareMockitoExtensionAnnotation
+ public static class NecessaryStubbingTestClass {
+
+ @Autowired
+ private Dummy dummy;
+
+ @Test
+ @Order(1)
+ public void necessaryStubbing() {
+ Mockito.doAnswer(invocation -> {
+ return null;
+ }).when(dummy).foo();
+
+ dummy.foo();
+ }
+
+ @Test
+ @Order(2)
+ public void verifyStubbingIsResetted() {
+ // no-op - Work is done by ExpectedExceptionAutowiredMockAwareMockitoExtension
+ }
+ }
+
+ @Test
+ public void unnecessaryStubbing() {
+ Events testEvents = executeTestsForClass(UnnecessaryStubbingTestClass.class).tests();
+
+ assertThat(testEvents.started().count()).isEqualTo(2);
+ assertThat(testEvents.succeeded().count()).isEqualTo(2);
+ assertThat(testEvents.skipped().count()).isEqualTo(0);
+ assertThat(testEvents.aborted().count()).isEqualTo(0);
+ assertThat(testEvents.failed().count()).isEqualTo(0);
+ }
+
+ @ExpectedExceptionAutowiredMockAwareMockitoExtensionAnnotation
+ public static class UnnecessaryStubbingTestClass {
+
+ @Autowired
+ private Dummy dummy;
+
+ @Test
+ @Order(1)
+ @ExpectedException(type = UnnecessaryStubbingException.class)
+ public void unnecessaryStubbing() {
+ Mockito.doAnswer(invocation -> {
+ return null;
+ }).when(dummy).foo();
+ }
+
+ @Test
+ @Order(2)
+ public void verifyUnnecessaryStubbingDoesNotLeakOnOtherTest() {
+ // no-op - Work is done by ExpectedExceptionAutowiredMockAwareMockitoExtension
+ }
+ }
+
+ @Test
+ public void stubbing() {
+ Events testEvents = executeTestsForClass(StubbingTestClass.class).tests();
+
+ assertThat(testEvents.started().count()).isEqualTo(2);
+ assertThat(testEvents.succeeded().count()).isEqualTo(2);
+ assertThat(testEvents.skipped().count()).isEqualTo(0);
+ assertThat(testEvents.aborted().count()).isEqualTo(0);
+ assertThat(testEvents.failed().count()).isEqualTo(0);
+ }
+
+ @ExpectedExceptionAutowiredMockAwareMockitoExtensionAnnotation
+ public static class StubbingTestClass {
+
+ @Autowired
+ private Dummy dummy;
+
+ @Test
+ @Order(1)
+ public void stubbing() {
+ Mockito.doAnswer(invocation -> {
+ return true;
+ }).when(dummy).bar();
+
+ if (!dummy.bar()) {
+ Assertions.fail();
+ }
+ }
+
+ @Test
+ @Order(2)
+ public void verifyStubbingIsResetted() {
+ if (dummy.bar()) {
+ Assertions.fail();
+ }
+ }
+ }
+
+ private EngineExecutionResults executeTestsForClass(final Class> testClass) {
+ DiscoverySelector selectors = selectClass(testClass);
+ LauncherDiscoveryRequest request = request().selectors(selectors).build();
+ return EngineTestKit.execute(this.engine, request);
+ }
+
+ /**
+ * Expected exception catcher inspired by : {@link io.github.glytching.junit.extension.exception.ExpectedExceptionExtension}
+ */
+ public static class ExpectedExceptionAutowiredMockAwareMockitoExtension extends AutowiredMockAwareMockitoExtension {
+
+ @Override
+ public void afterEach(final ExtensionContext context) {
+ Optional annotation = findAnnotation(context.getTestMethod(), ExpectedException.class);
+ boolean catched = false;
+ try {
+ super.afterEach(context);
+ } catch (Exception exception) { //NOPMD - Type is checked dynamically below
+ catched = true;
+ if (annotation.isPresent()) {
+ if (!annotation.get().type().isAssignableFrom(exception.getClass())) {
+ Assertions.fail("Exception of type " + annotation.get().type() + "expected", exception);
+ }
+ } else {
+ // No exception expected, behave normally
+ throw exception;
+ }
+ }
+ if (annotation.isPresent() && !catched) {
+ Assertions.fail("No exception was thrown. Expected exception was: " + annotation.get().type());
+ }
+ }
+ }
+
+ public static class Dummy {
+
+ public void foo() {
+ // no-op - Empty method to stub
+ }
+
+ public boolean bar() {
+ return false;
+ }
+ }
+
+ @Target(ElementType.TYPE)
+ @Retention(RetentionPolicy.RUNTIME)
+ @TestMethodOrder(OrderAnnotation.class)
+ @ExtendWith(ExpectedExceptionAutowiredMockAwareMockitoExtension.class)
+ @ExtendWith(SpringExtension.class)
+ @ContextConfiguration("classpath:context/test-mocking-context.xml")
+ public @interface ExpectedExceptionAutowiredMockAwareMockitoExtensionAnnotation {}
+
+ @Target({ ElementType.METHOD, ElementType.TYPE })
+ @Retention(RetentionPolicy.RUNTIME)
+ @Documented
+ public @interface ExpectedException {
+
+ Class extends Throwable> type();
+ }
+}
diff --git a/alfresco-filer-core/src/test/resources/context/test-mocking-context.xml b/alfresco-filer-core/src/test/resources/context/test-mocking-context.xml
new file mode 100644
index 0000000..33def77
--- /dev/null
+++ b/alfresco-filer-core/src/test/resources/context/test-mocking-context.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
diff --git a/alfresco-filer-parent/pom.xml b/alfresco-filer-parent/pom.xml
index 4c0c292..ead598d 100644
--- a/alfresco-filer-parent/pom.xml
+++ b/alfresco-filer-parent/pom.xml
@@ -22,6 +22,7 @@
2.22.25.5.1
+ 1.5.15.1.8.RELEASE3.0.03.12.2
@@ -80,6 +81,12 @@
${alfresco-filer-parent.servlet-api.version}test
+
+ org.junit.platform
+ junit-platform-testkit
+ ${alfresco-filer-parent.junit.platform.version}
+ test
+
From 8188eb4be7ca590f8009585a782cb48178138d4e Mon Sep 17 00:00:00 2001
From: Corentin PIANA
Date: Tue, 3 Sep 2019 17:39:21 +0200
Subject: [PATCH 23/58] Refactoring parallel test code execution
Change-Id: Iadbd541328a42f54b2e4844cf06980f1919c47a7
---
.../test/domain/AbstractParallelTest.java | 84 ++++++-----
.../domain/BinaryOperationParallelTest.java | 138 ++++++++----------
.../domain/DuplicateNameParallelTest.java | 45 +++---
.../domain/UnaryOperationParallelTest.java | 95 ++++++------
4 files changed, 170 insertions(+), 192 deletions(-)
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/AbstractParallelTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/AbstractParallelTest.java
index 22212a6..f5a6217 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/AbstractParallelTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/AbstractParallelTest.java
@@ -6,6 +6,7 @@
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.concurrent.BrokenBarrierException;
+import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
@@ -17,6 +18,7 @@
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
+import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ExecutionMode;
@@ -41,8 +43,6 @@
@Execution(ExecutionMode.SAME_THREAD)
public abstract class AbstractParallelTest extends SiteBasedTest {
- private static final Logger LOGGER = LoggerFactory.getLogger(AbstractParallelTest.class);
-
protected static final int NUM_THREAD_TO_LAUNCH = Runtime.getRuntime().availableProcessors() * 2;
protected static final int MAIN_TASK = 1;
@@ -50,24 +50,36 @@ public abstract class AbstractParallelTest extends SiteBasedTest {
protected static final int UPDATE_TASK = 1;
protected static final int DELETE_TASK = 1;
+ private final Logger logger = LoggerFactory.getLogger(getClass());
+
@Autowired
private NodeService nodeService;
- private final ExecutorService executor = Executors.newFixedThreadPool(NUM_THREAD_TO_LAUNCH);
+ private static ExecutorService executor;
@BeforeAll
- public static void assertParallelismIsAvailable() {
+ public static void preconditionAndStartExecutor() {
+ // Assert required level of parallelism is available
assertThat(Runtime.getRuntime().availableProcessors()).isGreaterThan(1);
+
+ executor = Executors.newFixedThreadPool(NUM_THREAD_TO_LAUNCH);
+ }
+
+ @AfterAll
+ public static void stopExecutor() {
+ executor.shutdown();
}
- protected void execute(final Runnable task, final CountDownLatch endingLatch) {
+ protected void execute(final CountDownLatch endingLatch, final Callable task) {
executor.submit(() -> {
AuthenticationUtil.setRunAsUserSystem();
try {
- task.run();
+ task.call();
+ } catch (Exception e) { //NOPMD Catch all exceptions that might occur in thread as they will not be thrown to main thread
+ logger.error("Parallel testing error", e);
} finally {
- endingLatch.countDown();
AuthenticationUtil.clearCurrentSecurityContext();
+ endingLatch.countDown();
}
});
}
@@ -89,49 +101,43 @@ protected void createAndDeleteNodesImpl() throws InterruptedException, BrokenBar
AtomicReference createdNode = new AtomicReference<>();
AtomicReference nodeToDelete = new AtomicReference<>();
- execute(() -> {
- LOGGER.debug("Create task: task started");
+ execute(endingLatch, () -> {
+ logger.debug("Create task: task started");
RepositoryNode node = buildNode(departmentName, date).build();
- try {
- // Wait for every task to be ready for launching parallel task execution
- startingBarrier.await(10, TimeUnit.SECONDS);
+ // Wait for every task to be ready for launching parallel task execution
+ startingBarrier.await(10, TimeUnit.SECONDS);
- LOGGER.debug("Create task: node creation start");
- createNode(node);
- LOGGER.debug("Create task: node creation end");
- createdNode.set(node);
- } catch (Exception e) { //NOPMD Catch all exceptions that might occur in thread as they will not be thrown to main thread
- LOGGER.error("Create task: could not create node", e);
- }
- }, endingLatch);
+ logger.debug("Create task: node creation start");
+ createNode(node);
+ logger.debug("Create task: node creation end");
+ createdNode.set(node);
+ return null;
+ });
- execute(() -> {
- LOGGER.debug("Delete task: task started");
+ execute(endingLatch, () -> {
+ logger.debug("Delete task: task started");
RepositoryNode node = buildNode(departmentName, date)
// Do not archive node, this could generate contention on creating user trashcan
.aspect(ContentModel.ASPECT_TEMPORARY)
.build();
- try {
- LOGGER.debug("Delete task: creating node that will be deleted");
- createNode(node);
- nodeToDelete.set(node);
+ logger.debug("Delete task: creating node that will be deleted");
+ createNode(node);
+ nodeToDelete.set(node);
- preparationAssertBarrier.await(10, TimeUnit.SECONDS);
- // Wait for assertion on created nodes taking place in main task
- preparationAssertBarrier.await(10, TimeUnit.SECONDS);
+ preparationAssertBarrier.await(10, TimeUnit.SECONDS);
+ // Wait for assertion on created nodes taking place in main task
+ preparationAssertBarrier.await(10, TimeUnit.SECONDS);
- // Wait for every task to be ready for launching parallel task execution
- startingBarrier.await(10, TimeUnit.SECONDS);
+ // Wait for every task to be ready for launching parallel task execution
+ startingBarrier.await(10, TimeUnit.SECONDS);
- LOGGER.debug("Delete task: node deletion start");
- deleteNode(node);
- LOGGER.debug("Delete task: node deletion end");
- } catch (Exception e) { //NOPMD Catch all exceptions that might occur in thread as they will not be thrown to main thread
- LOGGER.error("Delete task: could not delete node", e);
- }
- }, endingLatch);
+ logger.debug("Delete task: node deletion start");
+ deleteNode(node);
+ logger.debug("Delete task: node deletion end");
+ return null;
+ });
// Wait for node creation to finish and then assert node is indeed created
preparationAssertBarrier.await();
@@ -141,7 +147,7 @@ protected void createAndDeleteNodesImpl() throws InterruptedException, BrokenBar
// Wait for every task to finish job before asserting results
endingLatch.await();
- LOGGER.debug("All tasks are done, starting assertions");
+ logger.debug("All tasks are done, starting assertions");
// Assert all tasks were ready for parallel task execution
assertThat(startingBarrier.isBroken()).isFalse();
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/BinaryOperationParallelTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/BinaryOperationParallelTest.java
index b04c217..2387706 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/BinaryOperationParallelTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/BinaryOperationParallelTest.java
@@ -84,59 +84,53 @@ public void updateAndDeleteNodesInSourceFolder() throws InterruptedException, Br
AtomicReference nodeToUpdate = new AtomicReference<>();
AtomicReference nodeToDelete = new AtomicReference<>();
- execute(() -> {
+ execute(endingLatch, () -> {
LOGGER.debug("Update task: task started");
RepositoryNode node = buildNode(departmentName, sourceDate).build();
- try {
- LOGGER.debug("Update task: creating node that will be updated");
- createNode(node);
- nodeToUpdate.set(node);
+ LOGGER.debug("Update task: creating node that will be updated");
+ createNode(node);
+ nodeToUpdate.set(node);
- preparationAssertBarrier.await(10, TimeUnit.SECONDS);
- // Wait for assertion on created nodes taking place in main task
- preparationAssertBarrier.await(10, TimeUnit.SECONDS);
+ preparationAssertBarrier.await(10, TimeUnit.SECONDS);
+ // Wait for assertion on created nodes taking place in main task
+ preparationAssertBarrier.await(10, TimeUnit.SECONDS);
- Map dateProperty = Collections.singletonMap(FilerTestConstants.ImportedAspect.PROP_DATE,
- Date.from(targetDate.atZone(ZoneId.systemDefault()).toInstant()));
+ Map dateProperty = Collections.singletonMap(FilerTestConstants.ImportedAspect.PROP_DATE,
+ Date.from(targetDate.atZone(ZoneId.systemDefault()).toInstant()));
- // Wait for every task to be ready for launching parallel task execution
- startingBarrier.await(10, TimeUnit.SECONDS);
+ // Wait for every task to be ready for launching parallel task execution
+ startingBarrier.await(10, TimeUnit.SECONDS);
- LOGGER.debug("Update task: node creation start");
- updateNode(node, dateProperty);
- LOGGER.debug("Update task: node creation end");
- } catch (Exception e) { //NOPMD Catch all exceptions that might occur in thread as they will not be thrown to main thread
- LOGGER.error("Update task: could not update node", e);
- }
- }, endingLatch);
+ LOGGER.debug("Update task: node creation start");
+ updateNode(node, dateProperty);
+ LOGGER.debug("Update task: node creation end");
+ return null;
+ });
- execute(() -> {
+ execute(endingLatch, () -> {
LOGGER.debug("Delete task: task started");
RepositoryNode node = buildNode(departmentName, sourceDate)
// Do not archive node, this could generate contention on creating user trashcan
.aspect(ContentModel.ASPECT_TEMPORARY)
.build();
- try {
- LOGGER.debug("Delete task: creating node that will be deleted");
- createNode(node);
- nodeToDelete.set(node);
+ LOGGER.debug("Delete task: creating node that will be deleted");
+ createNode(node);
+ nodeToDelete.set(node);
- preparationAssertBarrier.await(10, TimeUnit.SECONDS);
- // Wait for assertion on created nodes taking place in main task
- preparationAssertBarrier.await(10, TimeUnit.SECONDS);
+ preparationAssertBarrier.await(10, TimeUnit.SECONDS);
+ // Wait for assertion on created nodes taking place in main task
+ preparationAssertBarrier.await(10, TimeUnit.SECONDS);
- // Wait for every task to be ready for launching parallel task execution
- startingBarrier.await(10, TimeUnit.SECONDS);
+ // Wait for every task to be ready for launching parallel task execution
+ startingBarrier.await(10, TimeUnit.SECONDS);
- LOGGER.debug("Delete task: node deletion start");
- deleteNode(node);
- LOGGER.debug("Delete task: node deletion end");
- } catch (Exception e) { //NOPMD Catch all exceptions that might occur in thread as they will not be thrown to main thread
- LOGGER.error("Delete task: could not delete node", e);
- }
- }, endingLatch);
+ LOGGER.debug("Delete task: node deletion start");
+ deleteNode(node);
+ LOGGER.debug("Delete task: node deletion end");
+ return null;
+ });
// Wait for node creation to finish and then assert all nodes are indeed created
preparationAssertBarrier.await();
@@ -181,59 +175,53 @@ public void updateAndDeleteNodesInTargetFolder() throws InterruptedException, Br
AtomicReference nodeToUpdate = new AtomicReference<>();
AtomicReference nodeToDelete = new AtomicReference<>();
- execute(() -> {
+ execute(endingLatch, () -> {
LOGGER.debug("Update task: task started");
RepositoryNode node = buildNode(departmentName, sourceDate).build();
- try {
- LOGGER.debug("Update task: creating node that will be updated");
- createNode(node);
- nodeToUpdate.set(node);
+ LOGGER.debug("Update task: creating node that will be updated");
+ createNode(node);
+ nodeToUpdate.set(node);
- preparationAssertBarrier.await(10, TimeUnit.SECONDS);
- // Wait for assertion on created nodes taking place in main task
- preparationAssertBarrier.await(10, TimeUnit.SECONDS);
+ preparationAssertBarrier.await(10, TimeUnit.SECONDS);
+ // Wait for assertion on created nodes taking place in main task
+ preparationAssertBarrier.await(10, TimeUnit.SECONDS);
- Map dateProperty = Collections.singletonMap(FilerTestConstants.ImportedAspect.PROP_DATE,
- Date.from(targetDate.atZone(ZoneId.systemDefault()).toInstant()));
+ Map dateProperty = Collections.singletonMap(FilerTestConstants.ImportedAspect.PROP_DATE,
+ Date.from(targetDate.atZone(ZoneId.systemDefault()).toInstant()));
- // Wait for every task to be ready for launching parallel task execution
- startingBarrier.await(10, TimeUnit.SECONDS);
+ // Wait for every task to be ready for launching parallel task execution
+ startingBarrier.await(10, TimeUnit.SECONDS);
- LOGGER.debug("Update task: node creation start");
- updateNode(node, dateProperty);
- LOGGER.debug("Update task: node creation end");
- } catch (Exception e) { //NOPMD Catch all exceptions that might occur in thread as they will not be thrown to main thread
- LOGGER.error("Update task: could not update node", e);
- }
- }, endingLatch);
+ LOGGER.debug("Update task: node creation start");
+ updateNode(node, dateProperty);
+ LOGGER.debug("Update task: node creation end");
+ return null;
+ });
- execute(() -> {
+ execute(endingLatch, () -> {
LOGGER.debug("Delete task: task started");
RepositoryNode node = buildNode(departmentName, targetDate)
// Do not archive node, this could generate contention on creating user trashcan
.aspect(ContentModel.ASPECT_TEMPORARY)
.build();
- try {
- LOGGER.debug("Delete task: creating node that will be deleted");
- createNode(node);
- nodeToDelete.set(node);
-
- preparationAssertBarrier.await(10, TimeUnit.SECONDS);
- // Wait for assertion on created nodes taking place in main task
- preparationAssertBarrier.await(10, TimeUnit.SECONDS);
-
- // Wait for every task to be ready for launching parallel task execution
- startingBarrier.await(10, TimeUnit.SECONDS);
-
- LOGGER.debug("Delete task: node deletion start");
- deleteNode(node);
- LOGGER.debug("Delete task: node deletion end");
- } catch (Exception e) { //NOPMD Catch all exceptions that might occur in thread as they will not be thrown to main thread
- LOGGER.error("Delete task: could not delete node", e);
- }
- }, endingLatch);
+ LOGGER.debug("Delete task: creating node that will be deleted");
+ createNode(node);
+ nodeToDelete.set(node);
+
+ preparationAssertBarrier.await(10, TimeUnit.SECONDS);
+ // Wait for assertion on created nodes taking place in main task
+ preparationAssertBarrier.await(10, TimeUnit.SECONDS);
+
+ // Wait for every task to be ready for launching parallel task execution
+ startingBarrier.await(10, TimeUnit.SECONDS);
+
+ LOGGER.debug("Delete task: node deletion start");
+ deleteNode(node);
+ LOGGER.debug("Delete task: node deletion end");
+ return null;
+ });
// Wait for node creation to finish and then assert all nodes are indeed created
preparationAssertBarrier.await();
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DuplicateNameParallelTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DuplicateNameParallelTest.java
index 0ec0eac..e896b99 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DuplicateNameParallelTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DuplicateNameParallelTest.java
@@ -42,33 +42,30 @@ public void createAndDeleteNodes() throws InterruptedException, BrokenBarrierExc
for (int i = 0; i < NUM_THREAD_TO_LAUNCH; i++) {
final int taskNumber = i;
- execute(() -> {
+ execute(endingLatch, () -> {
LOGGER.debug("Task {}: task started", taskNumber);
RepositoryNode node = buildNode(departmentName, date).build();
- try {
- // Wait for every task to be ready for launching parallel task execution
- startingBarrier.await(10, TimeUnit.SECONDS);
-
- LOGGER.debug("Task {}: node creation start", taskNumber);
- // Name generation and node creation must be in the same transaction as the whole transaction execution will be retried
- // when there is an attempt to create nodes with duplicate name.
- doInTransaction(() -> {
- // Generate name based on a variable shared between all test threads
- node.getProperties().put(ContentModel.PROP_NAME, "x" + Integer.toString(nodeNameSuffix.get()));
- createNodeImpl(node);
- });
- LOGGER.debug("Task {}: node creation end", taskNumber);
-
- results.add(node);
-
- // Wait before incrementing suffix to let more chance to other thread to generate duplicate node name
- TimeUnit.MILLISECONDS.sleep(250);
- nodeNameSuffix.getAndIncrement();
- } catch (Exception e) { //NOPMD Catch all exceptions that might occur in thread as they will not be thrown to main thread
- LOGGER.error("Task " + taskNumber + ": could not create node", e);
- }
- }, endingLatch);
+ // Wait for every task to be ready for launching parallel task execution
+ startingBarrier.await(10, TimeUnit.SECONDS);
+
+ LOGGER.debug("Task {}: node creation start", taskNumber);
+ // Name generation and node creation must be in the same transaction as the whole transaction execution will be retried
+ // when there is an attempt to create nodes with duplicate name.
+ doInTransaction(() -> {
+ // Generate name based on a variable shared between all test threads
+ node.getProperties().put(ContentModel.PROP_NAME, "x" + Integer.toString(nodeNameSuffix.get()));
+ createNodeImpl(node);
+ });
+ LOGGER.debug("Task {}: node creation end", taskNumber);
+
+ results.add(node);
+
+ // Wait before incrementing suffix to let more chance to other thread to generate duplicate node name
+ TimeUnit.MILLISECONDS.sleep(250);
+ nodeNameSuffix.getAndIncrement();
+ return null;
+ });
}
// Wait for every task to finish job before asserting results
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/UnaryOperationParallelTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/UnaryOperationParallelTest.java
index 023ab5d..014a03f 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/UnaryOperationParallelTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/UnaryOperationParallelTest.java
@@ -23,8 +23,6 @@
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.namespace.QName;
import org.junit.jupiter.api.Test;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import com.atolcd.alfresco.filer.core.model.RepositoryNode;
@@ -36,8 +34,6 @@
*/
public class UnaryOperationParallelTest extends AbstractParallelTest {
- private static final Logger LOGGER = LoggerFactory.getLogger(UnaryOperationParallelTest.class);
-
@Autowired
private NodeService nodeService;
@@ -51,19 +47,16 @@ public void createMultipleNodes() throws InterruptedException {
List results = Collections.synchronizedList(new ArrayList<>());
for (int i = 0; i < NUM_THREAD_TO_LAUNCH; i++) {
- execute(() -> {
+ execute(endingLatch, () -> {
RepositoryNode node = buildNode(departmentName, date).build();
- try {
- // Wait for every thread to be ready to launch parallel createNode
- startingBarrier.await(10, TimeUnit.SECONDS);
+ // Wait for every thread to be ready to launch parallel createNode
+ startingBarrier.await(10, TimeUnit.SECONDS);
- createNode(node);
- results.add(node);
- } catch (Exception e) { //NOPMD Catch all exceptions that might occur in thread as they will not be thrown to main thread
- LOGGER.error("Could not create node", e);
- }
- }, endingLatch);
+ createNode(node);
+ results.add(node);
+ return null;
+ });
}
// Wait for every thread to finish job before asserting results
@@ -91,34 +84,31 @@ public void updateMultipleNodes() throws InterruptedException, BrokenBarrierExce
Set closestNonSegmentAncestor = Collections.synchronizedSet(new HashSet<>());
for (int i = 0; i < NUM_THREAD_TO_LAUNCH; i++) {
- execute(() -> {
+ execute(endingLatch, () -> {
RepositoryNode node = buildNode(departmentName, sourceDate).build();
- try {
- createNode(node);
- results.add(node);
+ createNode(node);
+ results.add(node);
- preparationAssertBarrier.await(10, TimeUnit.SECONDS);
- // Wait for assertion on created nodes taking place in main thread
- preparationAssertBarrier.await(10, TimeUnit.SECONDS);
+ preparationAssertBarrier.await(10, TimeUnit.SECONDS);
+ // Wait for assertion on created nodes taking place in main thread
+ preparationAssertBarrier.await(10, TimeUnit.SECONDS);
- segmentAncestors.add(node.getParent());
- NodeRef grandParentRef = nodeService.getPrimaryParent(node.getParent()).getParentRef();
- segmentAncestors.add(grandParentRef);
- NodeRef greatGrandParentRef = nodeService.getPrimaryParent(grandParentRef).getParentRef();
- closestNonSegmentAncestor.add(greatGrandParentRef);
+ segmentAncestors.add(node.getParent());
+ NodeRef grandParentRef = nodeService.getPrimaryParent(node.getParent()).getParentRef();
+ segmentAncestors.add(grandParentRef);
+ NodeRef greatGrandParentRef = nodeService.getPrimaryParent(grandParentRef).getParentRef();
+ closestNonSegmentAncestor.add(greatGrandParentRef);
- Map dateProperty = Collections.singletonMap(FilerTestConstants.ImportedAspect.PROP_DATE,
- Date.from(targetDate.atZone(ZoneId.systemDefault()).toInstant()));
+ Map dateProperty = Collections.singletonMap(FilerTestConstants.ImportedAspect.PROP_DATE,
+ Date.from(targetDate.atZone(ZoneId.systemDefault()).toInstant()));
- // Wait for every thread to be ready to launch parallel updateNode
- startingBarrier.await(10, TimeUnit.SECONDS);
+ // Wait for every thread to be ready to launch parallel updateNode
+ startingBarrier.await(10, TimeUnit.SECONDS);
- updateNode(node, dateProperty);
- } catch (Exception e) { //NOPMD Catch all exceptions that might occur in thread as they will not be thrown to main thread
- LOGGER.error("Could not update node", e);
- }
- }, endingLatch);
+ updateNode(node, dateProperty);
+ return null;
+ });
}
// Wait for node creation to finish and then assert all nodes are well created
@@ -156,33 +146,30 @@ public void deleteMultipleNodes() throws InterruptedException, BrokenBarrierExce
Set closestNonSegmentAncestor = Collections.synchronizedSet(new HashSet<>());
for (int i = 0; i < NUM_THREAD_TO_LAUNCH; i++) {
- execute(() -> {
+ execute(endingLatch, () -> {
RepositoryNode node = buildNode(departmentName, date)
.aspect(ContentModel.ASPECT_TEMPORARY) // Do not archive node, this could generate contention on creating user trashcan
.build();
- try {
- createNode(node);
- results.add(node);
+ createNode(node);
+ results.add(node);
- preparationAssertBarrier.await(10, TimeUnit.SECONDS);
- // Wait for assertion on created nodes taking place in main thread
- preparationAssertBarrier.await(10, TimeUnit.SECONDS);
+ preparationAssertBarrier.await(10, TimeUnit.SECONDS);
+ // Wait for assertion on created nodes taking place in main thread
+ preparationAssertBarrier.await(10, TimeUnit.SECONDS);
- segmentAncestors.add(node.getParent());
- NodeRef grandParentRef = nodeService.getPrimaryParent(node.getParent()).getParentRef();
- segmentAncestors.add(grandParentRef);
- NodeRef greatGrandParentRef = nodeService.getPrimaryParent(grandParentRef).getParentRef();
- closestNonSegmentAncestor.add(greatGrandParentRef);
+ segmentAncestors.add(node.getParent());
+ NodeRef grandParentRef = nodeService.getPrimaryParent(node.getParent()).getParentRef();
+ segmentAncestors.add(grandParentRef);
+ NodeRef greatGrandParentRef = nodeService.getPrimaryParent(grandParentRef).getParentRef();
+ closestNonSegmentAncestor.add(greatGrandParentRef);
- // Wait for every thread to be ready to launch parallel deleteNode
- startingBarrier.await(10, TimeUnit.SECONDS);
+ // Wait for every thread to be ready to launch parallel deleteNode
+ startingBarrier.await(10, TimeUnit.SECONDS);
- deleteNode(node);
- } catch (Exception e) { //NOPMD Catch all exceptions that might occur in thread as they will not be thrown to main thread
- LOGGER.error("Could not delete node", e);
- }
- }, endingLatch);
+ deleteNode(node);
+ return null;
+ });
}
// Wait for node creation to finish and then assert all nodes are well created
From 8560fe393d884fd482d23494eeec47f01d2a90ed Mon Sep 17 00:00:00 2001
From: Corentin PIANA
Date: Wed, 4 Sep 2019 16:42:58 +0200
Subject: [PATCH 24/58] Fix: data race when getting folder
When trying to get a folder, by searching for corresponding name of
children of the parent node, the parent node could have been deleted
in another transaction running simultaneously. We catch the
InvalidNodeRefException and retry the transaction.
Change-Id: Ia074e2bf61cc93e690f89897447d35573bc8fb71
---
.../filer/core/service/impl/FilerFolderServiceImpl.java | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerFolderServiceImpl.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerFolderServiceImpl.java
index 73356dc..87ecbfd 100644
--- a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerFolderServiceImpl.java
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerFolderServiceImpl.java
@@ -7,6 +7,7 @@
import org.alfresco.model.ContentModel;
import org.alfresco.repo.domain.node.NodeDAO;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
+import org.alfresco.service.cmr.repository.InvalidNodeRefException;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.namespace.NamespaceService;
@@ -95,7 +96,12 @@ public void lockFolder(final NodeRef nodeRef) {
}
private void doGetFolder(final RepositoryNode node, final Consumer onGet) {
- NodeRef nodeRef = nodeService.getChildByName(node.getParent(), ContentModel.ASSOC_CONTAINS, node.getName().get());
+ NodeRef nodeRef = null;
+ try {
+ nodeRef = nodeService.getChildByName(node.getParent(), ContentModel.ASSOC_CONTAINS, node.getName().get());
+ } catch (InvalidNodeRefException e) {
+ throw new ConcurrencyFailureException("Could not get node. Node does not exist: " + node.getParent(), e);
+ }
if (nodeRef != null) {
node.setNodeRef(nodeRef);
afterGetFolder(node, onGet);
From 3e1980308ab397758c7ca738a90383c858d23b11 Mon Sep 17 00:00:00 2001
From: Corentin PIANA
Date: Thu, 5 Sep 2019 12:33:54 +0200
Subject: [PATCH 25/58] Testing property inheritance
Two tests are failing and tested code need correction.
Change-Id: I609eac639ac8b59edaba84e75c37070488539678
---
.../test/domain/PropertyInheritanceTest.java | 207 ++++++++++++++++++
...epartmentManagementContentFilerAction.java | 30 +++
.../content/model/FilerTestConstants.java | 25 +++
.../service/FilerTestActionService.java | 2 +
.../impl/FilerTestActionServiceImpl.java | 9 +
.../resources/context/test-action-context.xml | 5 +
.../test/resources/model/filerTestModel.xml | 33 +++
7 files changed, 311 insertions(+)
create mode 100644 alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/PropertyInheritanceTest.java
create mode 100644 alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/action/DepartmentManagementContentFilerAction.java
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/PropertyInheritanceTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/PropertyInheritanceTest.java
new file mode 100644
index 0000000..fc95235
--- /dev/null
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/PropertyInheritanceTest.java
@@ -0,0 +1,207 @@
+package com.atolcd.alfresco.filer.core.test.domain;
+
+import static java.util.UUID.randomUUID;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.Map;
+
+import org.alfresco.model.ContentModel;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.repository.NodeService;
+import org.alfresco.service.namespace.QName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import com.atolcd.alfresco.filer.core.model.RepositoryNode;
+import com.atolcd.alfresco.filer.core.test.domain.content.model.FilerTestConstants;
+import com.atolcd.alfresco.filer.core.test.framework.SiteBasedTest;
+
+public class PropertyInheritanceTest extends SiteBasedTest {
+
+ @Autowired
+ private NodeService nodeService;
+
+ @Test
+ public void createNodeInDepartmentFolder() {
+ RepositoryNode folderNode = buildNode()
+ .type(FilerTestConstants.Department.FolderType.NAME)
+ .property(FilerTestConstants.Department.Aspect.PROP_NAME, randomUUID())
+ .property(FilerTestConstants.Department.Aspect.PROP_ID, randomUUID())
+ .build();
+
+ createNode(folderNode);
+
+ // Create node in department folder and check if properties have been inheritated from department folder
+ RepositoryNode testNode = buildNode()
+ .type(FilerTestConstants.Department.DocumentType.NAME)
+ .parent(folderNode.getNodeRef())
+ .build();
+
+ createNode(testNode);
+
+ assertThat(testNode.getProperty(FilerTestConstants.Department.Aspect.PROP_NAME, String.class))
+ .isEqualTo(folderNode.getProperty(FilerTestConstants.Department.Aspect.PROP_NAME, String.class));
+ assertThat(testNode.getProperty(FilerTestConstants.Department.Aspect.PROP_ID, String.class))
+ .isEqualTo(folderNode.getProperty(FilerTestConstants.Department.Aspect.PROP_ID, String.class));
+ }
+
+ // Test fails. TODO Tested code need correction.
+// @Test
+// public void setWrongValuePropertyOnDocumentNode() {
+// RepositoryNode folderNode = buildNode()
+// .type(FilerTestConstants.Department.FolderType.NAME)
+// .property(FilerTestConstants.Department.Aspect.PROP_NAME, randomUUID())
+// .property(FilerTestConstants.Department.Aspect.PROP_ID, randomUUID())
+// .build();
+//
+// createNode(folderNode);
+//
+// RepositoryNode testNode = buildNode()
+// .type(FilerTestConstants.Department.DocumentType.NAME)
+// .parent(folderNode.getNodeRef())
+// .build();
+//
+// createNode(testNode);
+//
+// // Change property of document node and check that inheritance override that changement
+// Map property = Collections.singletonMap(FilerTestConstants.Department.Aspect.PROP_ID,
+// randomUUID().toString());
+// updateNode(testNode, property);
+//
+// assertThat(testNode.getProperty(FilerTestConstants.Department.Aspect.PROP_ID, String.class))
+// .isNotEqualTo(property.get(FilerTestConstants.Department.Aspect.PROP_ID));
+// assertThat(testNode.getProperty(FilerTestConstants.Department.Aspect.PROP_ID, String.class))
+// .isEqualTo(folderNode.getProperty(FilerTestConstants.Department.Aspect.PROP_ID, String.class));
+// }
+
+ @Test
+ public void changePropertyOfDepartmentFolder() {
+ RepositoryNode testNode = buildNode()
+ .type(FilerTestConstants.Department.DocumentType.NAME)
+ .property(FilerTestConstants.Department.Aspect.PROP_NAME, randomUUID())
+ .property(FilerTestConstants.Department.Aspect.PROP_ID, randomUUID())
+ .build();
+
+ createNode(testNode);
+
+ // Change property of department folder
+ RepositoryNode folderNode = new RepositoryNode(getDepartmentFolder(testNode));
+
+ Map property = Collections.singletonMap(FilerTestConstants.Department.Aspect.PROP_ID,
+ randomUUID().toString());
+ updateNode(folderNode, property);
+
+ assertThat(folderNode.getProperty(FilerTestConstants.Department.Aspect.PROP_ID, String.class))
+ .isEqualTo(property.get(FilerTestConstants.Department.Aspect.PROP_ID));
+
+ // Get document node and check if property change have been inherited
+ fetchNode(testNode);
+
+ assertThat(folderNode.getProperty(FilerTestConstants.Department.Aspect.PROP_NAME, String.class))
+ .isEqualTo(testNode.getProperty(FilerTestConstants.Department.Aspect.PROP_NAME, String.class));
+ assertThat(testNode.getProperty(FilerTestConstants.Department.Aspect.PROP_ID, String.class))
+ .isEqualTo(folderNode.getProperty(FilerTestConstants.Department.Aspect.PROP_ID, String.class));
+ }
+
+ @Test
+ public void createNodeInDepartmentFolderWithWrongProperty() {
+ RepositoryNode folderNode = buildNode()
+ .type(FilerTestConstants.Department.FolderType.NAME)
+ .property(FilerTestConstants.Department.Aspect.PROP_NAME, randomUUID())
+ .property(FilerTestConstants.Department.Aspect.PROP_ID, randomUUID())
+ .build();
+
+ createNode(folderNode);
+
+ // Create node in department folder with wrong property and check if correct property is inherited from folder
+ RepositoryNode testNode = buildNode()
+ .type(FilerTestConstants.Department.DocumentType.NAME)
+ .parent(folderNode.getNodeRef())
+ .property(FilerTestConstants.Department.Aspect.PROP_ID, randomUUID())
+ .build();
+
+ createNode(testNode);
+
+ assertThat(testNode.getProperty(FilerTestConstants.Department.Aspect.PROP_NAME, String.class))
+ .isEqualTo(folderNode.getProperty(FilerTestConstants.Department.Aspect.PROP_NAME, String.class));
+ assertThat(testNode.getProperty(FilerTestConstants.Department.Aspect.PROP_ID, String.class))
+ .isEqualTo(folderNode.getProperty(FilerTestConstants.Department.Aspect.PROP_ID, String.class));
+ }
+
+ // Test fails. TODO Tested code need correction.
+// @Test
+// public void createNodeInDocumentLibraryWithPropertiesForFiler() {
+// String departmentName = randomUUID().toString();
+//
+// RepositoryNode fixtureNode = buildNode()
+// .type(FilerTestConstants.Department.DocumentType.NAME)
+// .property(FilerTestConstants.Department.Aspect.PROP_NAME, departmentName)
+// .property(FilerTestConstants.Department.Aspect.PROP_ID, randomUUID())
+// .build();
+//
+// createNode(fixtureNode);
+//
+// // Create node in document library with all required properties to be filed
+// // and check if it has been filed correctly with property inheritance applied
+// RepositoryNode testNode = buildNode()
+// .type(FilerTestConstants.Department.DocumentType.NAME)
+// .property(FilerTestConstants.Department.Aspect.PROP_NAME, departmentName)
+// .build();
+//
+// createNode(testNode);
+//
+// assertThat(getPath(testNode)).isEqualTo(getPath(fixtureNode));
+// assertThat(testNode.getProperty(FilerTestConstants.Department.Aspect.PROP_NAME, String.class))
+// .isEqualTo(fixtureNode.getProperty(FilerTestConstants.Department.Aspect.PROP_NAME, String.class));
+// assertThat(testNode.getProperty(FilerTestConstants.Department.Aspect.PROP_ID, String.class))
+// .isEqualTo(fixtureNode.getProperty(FilerTestConstants.Department.Aspect.PROP_ID, String.class));
+// }
+
+ @Test
+ public void createNodeInFolderHierarchyWithMultipleLevelInheritance() {
+
+ RepositoryNode departmentFolderNode = buildNode()
+ .type(FilerTestConstants.Department.FolderType.NAME)
+ .property(FilerTestConstants.Department.Aspect.PROP_NAME, randomUUID())
+ .property(FilerTestConstants.Department.Aspect.PROP_ID, randomUUID())
+ .build();
+
+ createNode(departmentFolderNode);
+
+ // Create management folder in department folder and check if department properties are inherited
+ RepositoryNode managementFolderNode = buildNode()
+ .type(FilerTestConstants.Department.Management.DocumentType.NAME)
+ .parent(departmentFolderNode.getNodeRef())
+ .property(FilerTestConstants.Department.Management.Aspect.PROP_ID, randomUUID())
+ .build();
+
+ createNode(managementFolderNode);
+
+ assertThat(managementFolderNode.getProperty(FilerTestConstants.Department.Aspect.PROP_NAME, String.class))
+ .isEqualTo(departmentFolderNode.getProperty(FilerTestConstants.Department.Aspect.PROP_NAME, String.class));
+ assertThat(managementFolderNode.getProperty(FilerTestConstants.Department.Aspect.PROP_ID, String.class))
+ .isEqualTo(departmentFolderNode.getProperty(FilerTestConstants.Department.Aspect.PROP_ID, String.class));
+
+ // Create node in management folder and check if department and management properties are inherited
+ RepositoryNode testNode = buildNode()
+ .type(FilerTestConstants.Department.Management.DocumentType.NAME)
+ .parent(managementFolderNode.getParent())
+ .build();
+
+ createNode(testNode);
+
+ assertThat(testNode.getProperty(FilerTestConstants.Department.Aspect.PROP_NAME, String.class))
+ .isEqualTo(departmentFolderNode.getProperty(FilerTestConstants.Department.Aspect.PROP_NAME, String.class));
+ assertThat(testNode.getProperty(FilerTestConstants.Department.Aspect.PROP_ID, String.class))
+ .isEqualTo(departmentFolderNode.getProperty(FilerTestConstants.Department.Aspect.PROP_ID, String.class));
+ assertThat(testNode.getProperty(FilerTestConstants.Department.Management.Aspect.PROP_ID, String.class))
+ .isEqualTo(managementFolderNode.getProperty(FilerTestConstants.Department.Management.Aspect.PROP_ID, String.class));
+ }
+
+ private NodeRef getDepartmentFolder(final RepositoryNode node) {
+ String departmentName = node.getProperty(FilerTestConstants.Department.Aspect.PROP_NAME, String.class);
+ return nodeService.getChildByName(getDocumentLibrary(), ContentModel.ASSOC_CONTAINS, departmentName);
+ }
+}
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/action/DepartmentManagementContentFilerAction.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/action/DepartmentManagementContentFilerAction.java
new file mode 100644
index 0000000..62ae322
--- /dev/null
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/action/DepartmentManagementContentFilerAction.java
@@ -0,0 +1,30 @@
+package com.atolcd.alfresco.filer.core.test.domain.action;
+
+import java.util.Arrays;
+
+import com.atolcd.alfresco.filer.core.model.FilerEvent;
+import com.atolcd.alfresco.filer.core.model.RepositoryNode;
+import com.atolcd.alfresco.filer.core.service.impl.FilerBuilder;
+import com.atolcd.alfresco.filer.core.test.domain.content.model.FilerTestConstants;
+
+public class DepartmentManagementContentFilerAction extends AbstractFilerTestAction {
+
+ @Override
+ public boolean supportsActionResolution(final FilerEvent event) {
+ return event.getNode().getType().equals(FilerTestConstants.Department.Management.DocumentType.NAME)
+ && event.getNode().getAspects()
+ .containsAll(Arrays.asList(
+ FilerTestConstants.Department.Aspect.NAME, FilerTestConstants.Department.Management.Aspect.NAME));
+ }
+
+ @Override
+ public boolean supportsActionExecution(final RepositoryNode node) {
+ return true;
+ }
+
+ @Override
+ protected void execute(final FilerBuilder builder) {
+ builder.with(actions()::departmentManagementFolder).getOrCreate()
+ .tree(actions()::dateSegmentation).updateAndMove();
+ }
+}
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/content/model/FilerTestConstants.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/content/model/FilerTestConstants.java
index 09e6eb5..874c376 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/content/model/FilerTestConstants.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/content/model/FilerTestConstants.java
@@ -24,10 +24,35 @@ private DocumentType() {}
public static final class Aspect { //NOPMD - name: not a utility class
public static final QName NAME = QName.createQName(NAMESPACE_URI, "department");
public static final QName PROP_NAME = QName.createQName(NAMESPACE_URI, "departmentName");
+ public static final QName PROP_ID = QName.createQName(NAMESPACE_URI, "departmentId");
private Aspect() {}
}
+ public static final class Management {
+
+ public static final class FolderType {
+ public static final QName NAME = QName.createQName(NAMESPACE_URI, "departmentManagementFolder");
+
+ private FolderType() {}
+ }
+
+ public static final class DocumentType {
+ public static final QName NAME = QName.createQName(NAMESPACE_URI, "departmentManagementDocument");
+
+ private DocumentType() {}
+ }
+
+ public static final class Aspect { //NOPMD - name: not a utility class
+ public static final QName NAME = QName.createQName(NAMESPACE_URI, "departmentManagement");
+ public static final QName PROP_ID = QName.createQName(NAMESPACE_URI, "departmentManagementId");
+
+ private Aspect() {}
+ }
+
+ private Management() {}
+ }
+
private Department() {}
}
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/service/FilerTestActionService.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/service/FilerTestActionService.java
index 8a5eecc..4ea3d0f 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/service/FilerTestActionService.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/service/FilerTestActionService.java
@@ -8,5 +8,7 @@ public interface FilerTestActionService {
FilerFolderTypeBuilder departmentFolder(FilerBuilder builder);
+ FilerFolderTypeBuilder departmentManagementFolder(FilerBuilder builder);
+
FilerFolderBuilder dateSegmentation(FilerFolderBuilder builder);
}
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/service/impl/FilerTestActionServiceImpl.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/service/impl/FilerTestActionServiceImpl.java
index 909f3c2..623bca1 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/service/impl/FilerTestActionServiceImpl.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/service/impl/FilerTestActionServiceImpl.java
@@ -18,9 +18,18 @@ public FilerFolderTypeBuilder departmentFolder(final FilerBuilder builder) {
.folder()
.named().with(SiteService.DOCUMENT_LIBRARY).get()
.folder(FilerTestConstants.Department.FolderType.NAME)
+ .mandatoryPropertyInheritance(FilerTestConstants.Department.Aspect.NAME)
.named().withProperty(FilerTestConstants.Department.Aspect.PROP_NAME);
}
+ @Override
+ public FilerFolderTypeBuilder departmentManagementFolder(final FilerBuilder builder) {
+ return builder.with(this::departmentFolder).getOrCreate()
+ .folder(FilerTestConstants.Department.Management.FolderType.NAME)
+ .mandatoryPropertyInheritance(FilerTestConstants.Department.Management.Aspect.NAME)
+ .named().withProperty(FilerTestConstants.Department.Management.Aspect.PROP_ID);
+ }
+
@Override
public FilerFolderBuilder dateSegmentation(final FilerFolderBuilder builder) {
return builder
diff --git a/alfresco-filer-core/src/test/resources/context/test-action-context.xml b/alfresco-filer-core/src/test/resources/context/test-action-context.xml
index d492e90..c0b59d1 100644
--- a/alfresco-filer-core/src/test/resources/context/test-action-context.xml
+++ b/alfresco-filer-core/src/test/resources/context/test-action-context.xml
@@ -20,6 +20,11 @@
+
+
+
+
+
+
+ Department management folder
+ filerTest:departmentFolder
+
+ filerTest:departmentManagement
+
+
+
Department document
@@ -41,6 +50,15 @@
+
+
+ Department management document
+ filerTest:departmentDocument
+
+ filerTest:departmentManagement
+
+
+
Special document
@@ -63,6 +81,21 @@
d:texttrue
+
+ Department ID
+ d:text
+
+
+
+
+
+ Department management
+ filer:propertyInheritance
+
+
+ Department management id
+ d:text
+
From db6d5611b98d09c19e336f7e6a9b35c3a85395a2 Mon Sep 17 00:00:00 2001
From: Corentin PIANA
Date: Fri, 6 Sep 2019 16:29:43 +0200
Subject: [PATCH 26/58] Refactor test base classes
Mostly authentication management and class names
Change-Id: I4f7765b43463fb4eb3fe42d0128bc63887383fb4
---
.../core/test/content/ContextStartupTest.java | 20 ++-----------
.../test/domain/AbstractParallelTest.java | 8 ++----
.../test/domain/DeniedFilerActionTest.java | 4 +--
.../DepartmentContentFilerActionTest.java | 10 +++----
.../DepartmentFolderFilerActionTest.java | 4 +--
.../test/domain/PropertyInheritanceTest.java | 4 +--
.../domain/UnaryOperationParallelTest.java | 6 ++--
.../framework/AuthenticationExtension.java | 19 +++++++++++++
...Test.java => DocumentLibraryProvider.java} | 28 +++++++++----------
...sedTest.java => RepositoryOperations.java} | 17 +++++------
...eTest.java => TestApplicationContext.java} | 3 +-
11 files changed, 61 insertions(+), 62 deletions(-)
create mode 100644 alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/AuthenticationExtension.java
rename alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/{SiteBasedTest.java => DocumentLibraryProvider.java} (76%)
rename alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/{TransactionalBasedTest.java => RepositoryOperations.java} (89%)
rename alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/{ApplicationContextAwareTest.java => TestApplicationContext.java} (89%)
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/content/ContextStartupTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/content/ContextStartupTest.java
index 9a4405d..92624b3 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/content/ContextStartupTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/content/ContextStartupTest.java
@@ -5,21 +5,18 @@
import java.util.UUID;
import org.alfresco.model.ContentModel;
-import org.alfresco.repo.security.authentication.AuthenticationComponent;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.StoreRef;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronizationManager;
-import com.atolcd.alfresco.filer.core.test.framework.ApplicationContextAwareTest;
+import com.atolcd.alfresco.filer.core.test.framework.TestApplicationContext;
-@ApplicationContextAwareTest
+@TestApplicationContext
@Transactional
public class ContextStartupTest {
@@ -27,19 +24,6 @@ public class ContextStartupTest {
@Qualifier("NodeService")
private NodeService nodeService;
- @Autowired
- private AuthenticationComponent authenticationComponent;
-
- @BeforeEach
- public void initAuthorization() {
- authenticationComponent.setSystemUserAsCurrentUser();
- }
-
- @AfterEach
- public void clearAuthorization() {
- authenticationComponent.clearCurrentSecurityContext();
- }
-
@Test
public void basicWriteOperations() {
assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isTrue();
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/AbstractParallelTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/AbstractParallelTest.java
index f5a6217..e8e9ccc 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/AbstractParallelTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/AbstractParallelTest.java
@@ -29,7 +29,7 @@
import com.atolcd.alfresco.filer.core.model.RepositoryNode;
import com.atolcd.alfresco.filer.core.model.impl.RepositoryNodeBuilder;
import com.atolcd.alfresco.filer.core.test.domain.content.model.FilerTestConstants;
-import com.atolcd.alfresco.filer.core.test.framework.SiteBasedTest;
+import com.atolcd.alfresco.filer.core.test.framework.DocumentLibraryProvider;
/**
* Provide base class for parallel tests of {@linkplain com.atolcd.alfresco.filer.core.model.FilerAction Filer actions}. Assert
@@ -41,7 +41,7 @@
*
*/
@Execution(ExecutionMode.SAME_THREAD)
-public abstract class AbstractParallelTest extends SiteBasedTest {
+public abstract class AbstractParallelTest extends DocumentLibraryProvider {
protected static final int NUM_THREAD_TO_LAUNCH = Runtime.getRuntime().availableProcessors() * 2;
@@ -72,13 +72,11 @@ public static void stopExecutor() {
protected void execute(final CountDownLatch endingLatch, final Callable task) {
executor.submit(() -> {
- AuthenticationUtil.setRunAsUserSystem();
try {
- task.call();
+ AuthenticationUtil.runAsSystem(() -> task.call());
} catch (Exception e) { //NOPMD Catch all exceptions that might occur in thread as they will not be thrown to main thread
logger.error("Parallel testing error", e);
} finally {
- AuthenticationUtil.clearCurrentSecurityContext();
endingLatch.countDown();
}
});
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DeniedFilerActionTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DeniedFilerActionTest.java
index 93c7fbb..f6bd5f9 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DeniedFilerActionTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DeniedFilerActionTest.java
@@ -14,9 +14,9 @@
import com.atolcd.alfresco.filer.core.model.FilerException;
import com.atolcd.alfresco.filer.core.model.RepositoryNode;
import com.atolcd.alfresco.filer.core.service.FilerModelService;
-import com.atolcd.alfresco.filer.core.test.framework.SiteBasedTest;
+import com.atolcd.alfresco.filer.core.test.framework.DocumentLibraryProvider;
-public class DeniedFilerActionTest extends SiteBasedTest {
+public class DeniedFilerActionTest extends DocumentLibraryProvider {
@Autowired
private FilerModelService filerModelService;
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DepartmentContentFilerActionTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DepartmentContentFilerActionTest.java
index fbc6dce..796afa4 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DepartmentContentFilerActionTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DepartmentContentFilerActionTest.java
@@ -21,10 +21,10 @@
import com.atolcd.alfresco.filer.core.model.RepositoryNode;
import com.atolcd.alfresco.filer.core.service.FilerModelService;
import com.atolcd.alfresco.filer.core.test.domain.content.model.FilerTestConstants;
-import com.atolcd.alfresco.filer.core.test.framework.ApplicationContextAwareTest;
-import com.atolcd.alfresco.filer.core.test.framework.SiteBasedTest;
+import com.atolcd.alfresco.filer.core.test.framework.TestApplicationContext;
+import com.atolcd.alfresco.filer.core.test.framework.DocumentLibraryProvider;
-public class DepartmentContentFilerActionTest extends SiteBasedTest {
+public class DepartmentContentFilerActionTest extends DocumentLibraryProvider {
@Autowired
private FilerModelService filerModelService;
@@ -33,9 +33,9 @@ public class DepartmentContentFilerActionTest extends SiteBasedTest {
private NodeService nodeService;
@Nested
- // @ApplicationContextAwareTest is necessary as Spring does not find the configuration of nested class from the enclosing class
+ // @TestApplicationContext is necessary as Spring does not find the configuration of nested class from the enclosing class
// See https://github.com/spring-projects/spring-framework/issues/19930
- @ApplicationContextAwareTest
+ @TestApplicationContext
public class DepartmentDocument {
@Test
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DepartmentFolderFilerActionTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DepartmentFolderFilerActionTest.java
index 0d5be47..e858486 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DepartmentFolderFilerActionTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DepartmentFolderFilerActionTest.java
@@ -14,9 +14,9 @@
import com.atolcd.alfresco.filer.core.model.RepositoryNode;
import com.atolcd.alfresco.filer.core.test.domain.content.model.FilerTestConstants;
-import com.atolcd.alfresco.filer.core.test.framework.SiteBasedTest;
+import com.atolcd.alfresco.filer.core.test.framework.DocumentLibraryProvider;
-public class DepartmentFolderFilerActionTest extends SiteBasedTest {
+public class DepartmentFolderFilerActionTest extends DocumentLibraryProvider {
@Test
public void withDepartmentName() {
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/PropertyInheritanceTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/PropertyInheritanceTest.java
index fc95235..4894b6e 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/PropertyInheritanceTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/PropertyInheritanceTest.java
@@ -16,9 +16,9 @@
import com.atolcd.alfresco.filer.core.model.RepositoryNode;
import com.atolcd.alfresco.filer.core.test.domain.content.model.FilerTestConstants;
-import com.atolcd.alfresco.filer.core.test.framework.SiteBasedTest;
+import com.atolcd.alfresco.filer.core.test.framework.DocumentLibraryProvider;
-public class PropertyInheritanceTest extends SiteBasedTest {
+public class PropertyInheritanceTest extends DocumentLibraryProvider {
@Autowired
private NodeService nodeService;
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/UnaryOperationParallelTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/UnaryOperationParallelTest.java
index 014a03f..d4b85ea 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/UnaryOperationParallelTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/UnaryOperationParallelTest.java
@@ -27,7 +27,7 @@
import com.atolcd.alfresco.filer.core.model.RepositoryNode;
import com.atolcd.alfresco.filer.core.test.domain.content.model.FilerTestConstants;
-import com.atolcd.alfresco.filer.core.test.framework.SiteBasedTest;
+import com.atolcd.alfresco.filer.core.test.framework.DocumentLibraryProvider;
/**
* Test multiple executions in parallel of one operation
@@ -65,7 +65,7 @@ public void createMultipleNodes() throws InterruptedException {
// Assert all threads were ready for parallel createNode
assertThat(startingBarrier.isBroken()).isFalse();
- assertThat(results.stream().map(SiteBasedTest::getPath))
+ assertThat(results.stream().map(DocumentLibraryProvider::getPath))
.hasSize(NUM_THREAD_TO_LAUNCH)
.containsOnly(buildNodePath(departmentName, date));
}
@@ -125,7 +125,7 @@ public void updateMultipleNodes() throws InterruptedException, BrokenBarrierExce
// Assert all threads were ready for parallel updateNode
assertThat(startingBarrier.isBroken()).isFalse();
- assertThat(results.stream().map(SiteBasedTest::getPath))
+ assertThat(results.stream().map(DocumentLibraryProvider::getPath))
.hasSize(NUM_THREAD_TO_LAUNCH)
.containsOnly(buildNodePath(departmentName, targetDate));
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/AuthenticationExtension.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/AuthenticationExtension.java
new file mode 100644
index 0000000..f7abadd
--- /dev/null
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/AuthenticationExtension.java
@@ -0,0 +1,19 @@
+package com.atolcd.alfresco.filer.core.test.framework;
+
+import org.alfresco.repo.security.authentication.AuthenticationUtil;
+import org.junit.jupiter.api.extension.AfterEachCallback;
+import org.junit.jupiter.api.extension.BeforeEachCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+
+public class AuthenticationExtension implements BeforeEachCallback, AfterEachCallback {
+
+ @Override
+ public void beforeEach(final ExtensionContext context) {
+ AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName());
+ }
+
+ @Override
+ public void afterEach(final ExtensionContext context) {
+ AuthenticationUtil.clearCurrentSecurityContext();
+ }
+}
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/SiteBasedTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/DocumentLibraryProvider.java
similarity index 76%
rename from alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/SiteBasedTest.java
rename to alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/DocumentLibraryProvider.java
index 6f40bb6..09d9520 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/SiteBasedTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/DocumentLibraryProvider.java
@@ -22,9 +22,9 @@
import com.atolcd.alfresco.filer.core.model.RepositoryNode;
import com.atolcd.alfresco.filer.core.model.impl.RepositoryNodeBuilder;
-public abstract class SiteBasedTest extends TransactionalBasedTest {
+public abstract class DocumentLibraryProvider extends RepositoryOperations {
- private static final String NODE_PATH = SiteBasedTest.class + ".NODE_PATH";
+ private static final String NODE_PATH = DocumentLibraryProvider.class + ".NODE_PATH";
@Autowired
private TransactionService transactionService;
@@ -43,16 +43,17 @@ public abstract class SiteBasedTest extends TransactionalBasedTest {
private void initSite() {
String siteName = getSiteName();
- doInTransaction(() -> {
- AuthenticationUtil.setAdminUserAsFullyAuthenticatedUser();
-
- if (!siteService.hasSite(siteName)) {
- siteService.createSite(siteName, siteName, siteName, siteName, SiteVisibility.PUBLIC);
- }
-
- documentLibrary = SiteServiceImpl.getSiteContainer(siteName, SiteService.DOCUMENT_LIBRARY, true, siteService,
- transactionService, taggingService);
- });
+ AuthenticationUtil.runAs(() -> {
+ doInTransaction(() -> {
+ if (!siteService.hasSite(siteName)) {
+ siteService.createSite(siteName, siteName, siteName, siteName, SiteVisibility.PUBLIC);
+ }
+
+ documentLibrary = SiteServiceImpl.getSiteContainer(siteName, SiteService.DOCUMENT_LIBRARY, true, siteService,
+ transactionService, taggingService);
+ });
+ return null;
+ }, AuthenticationUtil.getAdminUserName());
}
protected String getSiteName() {
@@ -75,8 +76,7 @@ private static void setPath(final RepositoryNode node, final String path) {
protected String buildSitePath() {
return Paths
- .get(nodeService.getPath(getDocumentLibrary()).toDisplayPath(nodeService, permissionService),
- SiteService.DOCUMENT_LIBRARY)
+ .get(nodeService.getPath(documentLibrary).toDisplayPath(nodeService, permissionService), SiteService.DOCUMENT_LIBRARY)
.toString();
}
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/TransactionalBasedTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/RepositoryOperations.java
similarity index 89%
rename from alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/TransactionalBasedTest.java
rename to alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/RepositoryOperations.java
index 8ebb1b7..eba203c 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/TransactionalBasedTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/RepositoryOperations.java
@@ -4,7 +4,6 @@
import java.util.Map;
import org.alfresco.model.ContentModel;
-import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
@@ -12,24 +11,18 @@
import org.alfresco.service.namespace.QName;
import org.alfresco.service.transaction.TransactionService;
import org.alfresco.util.transaction.TransactionListenerAdapter;
-import org.junit.jupiter.api.AfterEach;
import org.springframework.beans.factory.annotation.Autowired;
import com.atolcd.alfresco.filer.core.model.RepositoryNode;
-@ApplicationContextAwareTest
-public class TransactionalBasedTest {
+@TestApplicationContext
+public class RepositoryOperations {
@Autowired
private TransactionService transactionService;
@Autowired
private NodeService nodeService;
- @AfterEach
- public void clearAuthorization() {
- AuthenticationUtil.clearCurrentSecurityContext();
- }
-
protected final void createNode(final RepositoryNode node) {
doInTransaction(() -> {
createNodeImpl(node);
@@ -76,7 +69,7 @@ protected final void deleteNode(final RepositoryNode node) {
});
}
- protected final void bindTransactionListener(final RepositoryNode node) {
+ private void bindTransactionListener(final RepositoryNode node) {
AlfrescoTransactionSupport.bindListener(new TransactionListenerAdapter() {
@Override
public void afterCommit() {
@@ -95,4 +88,8 @@ protected void doInTransaction(final Runnable callback, final boolean readOn
return null;
}, readOnly);
}
+
+ protected RepositoryOperations() {
+ // Prevent class instantiation
+ }
}
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/ApplicationContextAwareTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/TestApplicationContext.java
similarity index 89%
rename from alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/ApplicationContextAwareTest.java
rename to alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/TestApplicationContext.java
index 06be280..b6c19d6 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/ApplicationContextAwareTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/TestApplicationContext.java
@@ -19,4 +19,5 @@
"classpath:context/test-model-context.xml",
"classpath:context/test-action-context.xml"
})
-public @interface ApplicationContextAwareTest {}
+@ExtendWith(AuthenticationExtension.class)
+public @interface TestApplicationContext {}
From 0c2ecca380f2b3380275f9ac4dea7dad777204c1 Mon Sep 17 00:00:00 2001
From: Corentin PIANA
Date: Tue, 10 Sep 2019 12:46:55 +0200
Subject: [PATCH 27/58] Update JUnit 5.5.2 and JUnit Platform 1.5.2
Change-Id: Id662679da3f699440e055bd278433687c187b13b
---
alfresco-filer-parent/pom.xml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/alfresco-filer-parent/pom.xml b/alfresco-filer-parent/pom.xml
index ead598d..b2cdf9f 100644
--- a/alfresco-filer-parent/pom.xml
+++ b/alfresco-filer-parent/pom.xml
@@ -21,8 +21,8 @@
6.16.02.22.2
- 5.5.1
- 1.5.1
+ 5.5.2
+ 1.5.25.1.8.RELEASE3.0.03.12.2
From fb0ce0373e0684f1e28aea3f2802522cd0622f88 Mon Sep 17 00:00:00 2001
From: Corentin PIANA
Date: Wed, 11 Sep 2019 14:05:52 +0200
Subject: [PATCH 28/58] Fix: interface to be proxied in context file
Wrong package given.
Change-Id: Ida353a8e094e50bf810fb1a2f725f36488df2978
---
.../src/main/config/context/service-context.xml | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/alfresco-filer-core/src/main/config/context/service-context.xml b/alfresco-filer-core/src/main/config/context/service-context.xml
index f09713e..96fe4ce 100644
--- a/alfresco-filer-core/src/main/config/context/service-context.xml
+++ b/alfresco-filer-core/src/main/config/context/service-context.xml
@@ -29,7 +29,7 @@
class="org.springframework.aop.framework.ProxyFactoryBean">
- com.atolcd.alfresco.filer.core.service.impl.FilerOperationService
+ com.atolcd.alfresco.filer.core.service.FilerOperationService
@@ -48,7 +48,7 @@
class="org.springframework.aop.framework.ProxyFactoryBean">
- com.atolcd.alfresco.filer.core.service.impl.FilerFolderService
+ com.atolcd.alfresco.filer.core.service.FilerFolderService
@@ -65,7 +65,7 @@
class="org.springframework.aop.framework.ProxyFactoryBean">
- com.atolcd.alfresco.filer.core.service.impl.FilerUpdateService
+ com.atolcd.alfresco.filer.core.service.FilerUpdateService
@@ -83,7 +83,7 @@
class="org.springframework.aop.framework.ProxyFactoryBean">
- com.atolcd.alfresco.filer.core.service.impl.PropertyInheritanceService
+ com.atolcd.alfresco.filer.core.service.PropertyInheritanceService
From 0206d9720b38ce2c5eb81754a31efbbc8640457f Mon Sep 17 00:00:00 2001
From: Corentin PIANA
Date: Wed, 11 Sep 2019 17:54:55 +0200
Subject: [PATCH 29/58] Refactor scope loader classes
Set default methods in interface and delete one class turned useless.
Change-Id: Ic44ea0971d1d43c8e926178443bffb31c4919822
---
.../filer/core/scope/FilerScopeLoader.java | 8 +++--
.../scope/impl/AbstractFilerScopeLoader.java | 34 -------------------
.../scope/impl/AspectsFilerScopeLoader.java | 2 +-
.../scope/impl/DefaultFilerScopeLoader.java | 2 +-
.../scope/impl/EmptyFilerScopeLoader.java | 20 +++++++----
.../impl/PropertiesFilerScopeLoader.java | 2 +-
.../core/scope/impl/SiteFilerScopeLoader.java | 2 +-
7 files changed, 23 insertions(+), 47 deletions(-)
delete mode 100644 alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/scope/impl/AbstractFilerScopeLoader.java
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/scope/FilerScopeLoader.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/scope/FilerScopeLoader.java
index c84c300..fa84a99 100644
--- a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/scope/FilerScopeLoader.java
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/scope/FilerScopeLoader.java
@@ -4,7 +4,11 @@
public interface FilerScopeLoader {
- void init(FilerEvent event);
+ default void init(FilerEvent event) {
+ // no op
+ }
- void update(FilerEvent event);
+ default void update(FilerEvent event) {
+ // no op
+ }
}
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/scope/impl/AbstractFilerScopeLoader.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/scope/impl/AbstractFilerScopeLoader.java
deleted file mode 100644
index 5783e04..0000000
--- a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/scope/impl/AbstractFilerScopeLoader.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package com.atolcd.alfresco.filer.core.scope.impl;
-
-import java.util.Objects;
-
-import org.springframework.beans.factory.InitializingBean;
-
-import com.atolcd.alfresco.filer.core.model.FilerEvent;
-import com.atolcd.alfresco.filer.core.scope.FilerScopeLoader;
-import com.atolcd.alfresco.filer.core.service.FilerRegistry;
-
-public abstract class AbstractFilerScopeLoader implements FilerScopeLoader, InitializingBean {
-
- private FilerRegistry filerRegistry;
-
- @Override
- public void afterPropertiesSet() {
- Objects.requireNonNull(filerRegistry);
- filerRegistry.registerScopeLoader(this);
- }
-
- @Override
- public void init(final FilerEvent event) { // NOPMD - default empty method, in case init is not required
- // no op
- }
-
- @Override
- public void update(final FilerEvent event) { // NOPMD - default empty method, in case update is not required
- // no op
- }
-
- public void setFilerRegistry(final FilerRegistry filerRegistry) {
- this.filerRegistry = filerRegistry;
- }
-}
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/scope/impl/AspectsFilerScopeLoader.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/scope/impl/AspectsFilerScopeLoader.java
index 9e7f0c3..385092c 100644
--- a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/scope/impl/AspectsFilerScopeLoader.java
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/scope/impl/AspectsFilerScopeLoader.java
@@ -5,7 +5,7 @@
import com.atolcd.alfresco.filer.core.model.FilerEvent;
import com.atolcd.alfresco.filer.core.model.RepositoryNode;
-public class AspectsFilerScopeLoader extends AbstractFilerScopeLoader {
+public class AspectsFilerScopeLoader extends EmptyFilerScopeLoader {
private NodeService nodeService;
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/scope/impl/DefaultFilerScopeLoader.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/scope/impl/DefaultFilerScopeLoader.java
index 91a9927..3d683ad 100644
--- a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/scope/impl/DefaultFilerScopeLoader.java
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/scope/impl/DefaultFilerScopeLoader.java
@@ -5,7 +5,7 @@
import com.atolcd.alfresco.filer.core.model.FilerEvent;
import com.atolcd.alfresco.filer.core.model.RepositoryNode;
-public class DefaultFilerScopeLoader extends AbstractFilerScopeLoader {
+public class DefaultFilerScopeLoader extends EmptyFilerScopeLoader {
private NodeService nodeService;
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/scope/impl/EmptyFilerScopeLoader.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/scope/impl/EmptyFilerScopeLoader.java
index 7639826..f49d5ea 100644
--- a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/scope/impl/EmptyFilerScopeLoader.java
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/scope/impl/EmptyFilerScopeLoader.java
@@ -1,17 +1,23 @@
package com.atolcd.alfresco.filer.core.scope.impl;
-import com.atolcd.alfresco.filer.core.model.FilerEvent;
+import java.util.Objects;
+
+import org.springframework.beans.factory.InitializingBean;
+
import com.atolcd.alfresco.filer.core.scope.FilerScopeLoader;
+import com.atolcd.alfresco.filer.core.service.FilerRegistry;
-public class EmptyFilerScopeLoader implements FilerScopeLoader {
+public class EmptyFilerScopeLoader implements FilerScopeLoader, InitializingBean {
+
+ private FilerRegistry filerRegistry;
@Override
- public void init(final FilerEvent event) {
- // no op
+ public void afterPropertiesSet() {
+ Objects.requireNonNull(filerRegistry);
+ filerRegistry.registerScopeLoader(this);
}
- @Override
- public void update(final FilerEvent event) {
- // no op
+ public void setFilerRegistry(final FilerRegistry filerRegistry) {
+ this.filerRegistry = filerRegistry;
}
}
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/scope/impl/PropertiesFilerScopeLoader.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/scope/impl/PropertiesFilerScopeLoader.java
index b58f983..0b63ca8 100644
--- a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/scope/impl/PropertiesFilerScopeLoader.java
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/scope/impl/PropertiesFilerScopeLoader.java
@@ -5,7 +5,7 @@
import com.atolcd.alfresco.filer.core.model.FilerEvent;
import com.atolcd.alfresco.filer.core.model.RepositoryNode;
-public class PropertiesFilerScopeLoader extends AbstractFilerScopeLoader {
+public class PropertiesFilerScopeLoader extends EmptyFilerScopeLoader {
private NodeService nodeService;
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/scope/impl/SiteFilerScopeLoader.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/scope/impl/SiteFilerScopeLoader.java
index aebfa08..7ceab22 100644
--- a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/scope/impl/SiteFilerScopeLoader.java
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/scope/impl/SiteFilerScopeLoader.java
@@ -7,7 +7,7 @@
import com.atolcd.alfresco.filer.core.model.RepositoryNode;
import com.atolcd.alfresco.filer.core.util.FilerNodeUtils;
-public class SiteFilerScopeLoader extends AbstractFilerScopeLoader {
+public class SiteFilerScopeLoader extends EmptyFilerScopeLoader {
private SiteService siteService;
From 5dc6a50c2ffff02c1bb5bb15b19748b2404e03a7 Mon Sep 17 00:00:00 2001
From: Corentin PIANA
Date: Thu, 12 Sep 2019 09:44:57 +0200
Subject: [PATCH 30/58] Completing filer builder
Change-Id: If0596d2855fc3d9ac903a29903248b1d5b205b2c
---
.../alfresco/filer/core/service/impl/FilerBuilder.java | 4 ++++
.../alfresco/filer/core/service/impl/FilerNameBuilder.java | 6 +++++-
2 files changed, 9 insertions(+), 1 deletion(-)
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerBuilder.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerBuilder.java
index 16343ff..3de3cac 100644
--- a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerBuilder.java
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerBuilder.java
@@ -31,6 +31,10 @@ public FilerFolderBuilder root(final Supplier nodeRefSupplier) {
return root(nodeRefSupplier.get());
}
+ public FilerFolderBuilder tree(final Function builder) {
+ return builder.apply(this);
+ }
+
public FilerFolderTypeBuilder with(final Function builder) {
return builder.apply(this);
}
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerNameBuilder.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerNameBuilder.java
index eda0a8b..70094d9 100644
--- a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerNameBuilder.java
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerNameBuilder.java
@@ -65,9 +65,13 @@ public T with(final Supplier nodeNameFormatter) {
}
public T with(final Function nodeNameFormatter) {
+ return withContext(filerFolderContext -> nodeNameFormatter.apply(filerFolderContext.getNode()));
+ }
+
+ public T withContext(final Function nodeNameFormatter) {
String name = null;
if (context.isEnabled()) {
- name = nodeNameFormatter.apply(context.getNode());
+ name = nodeNameFormatter.apply(context);
}
return with(name);
}
From 311aff251e8c96375fff55c2635bb4b54d72ae6b Mon Sep 17 00:00:00 2001
From: Corentin PIANA
Date: Mon, 16 Sep 2019 17:34:34 +0200
Subject: [PATCH 31/58] Test refactor: Document library in JUnit extension
The extension create a site and the document library with name passed by
annotation.
Class DocumentLibraryProvider is split into the extension, a class
representing the Document library, the parent class
(RepositoryOperation) and an utility class.
Change-Id: I93c51ea02f54038c67f6b346b28343d0158baaca
---
.../test/domain/AbstractParallelTest.java | 31 +++---
.../domain/BinaryOperationParallelTest.java | 13 +--
.../test/domain/DeniedFilerActionTest.java | 9 +-
.../DepartmentContentFilerActionTest.java | 50 ++++-----
.../DepartmentFolderFilerActionTest.java | 18 ++--
.../test/domain/PropertyInheritanceTest.java | 31 +++---
.../domain/UnaryOperationParallelTest.java | 15 +--
.../core/test/domain/util/NodePathUtils.java | 18 ++++
.../core/test/framework/DocumentLibrary.java | 52 +++++++++
.../framework/DocumentLibraryExtension.java | 101 ++++++++++++++++++
.../framework/DocumentLibraryProvider.java | 98 -----------------
.../test/framework/RepositoryOperations.java | 27 +++--
.../test/framework/TestDocumentLibrary.java | 19 ++++
13 files changed, 297 insertions(+), 185 deletions(-)
create mode 100644 alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/util/NodePathUtils.java
create mode 100644 alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/DocumentLibrary.java
create mode 100644 alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/DocumentLibraryExtension.java
delete mode 100644 alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/DocumentLibraryProvider.java
create mode 100644 alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/TestDocumentLibrary.java
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/AbstractParallelTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/AbstractParallelTest.java
index e8e9ccc..a701b7e 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/AbstractParallelTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/AbstractParallelTest.java
@@ -1,5 +1,7 @@
package com.atolcd.alfresco.filer.core.test.domain;
+import static com.atolcd.alfresco.filer.core.test.domain.util.NodePathUtils.nodePath;
+import static com.atolcd.alfresco.filer.core.test.framework.DocumentLibraryExtension.getDocumentLibrary;
import static java.util.UUID.randomUUID;
import static org.assertj.core.api.Assertions.assertThat;
@@ -29,7 +31,9 @@
import com.atolcd.alfresco.filer.core.model.RepositoryNode;
import com.atolcd.alfresco.filer.core.model.impl.RepositoryNodeBuilder;
import com.atolcd.alfresco.filer.core.test.domain.content.model.FilerTestConstants;
-import com.atolcd.alfresco.filer.core.test.framework.DocumentLibraryProvider;
+import com.atolcd.alfresco.filer.core.test.framework.DocumentLibrary;
+import com.atolcd.alfresco.filer.core.test.framework.DocumentLibraryExtension;
+import com.atolcd.alfresco.filer.core.test.framework.RepositoryOperations;
/**
* Provide base class for parallel tests of {@linkplain com.atolcd.alfresco.filer.core.model.FilerAction Filer actions}. Assert
@@ -41,7 +45,7 @@
*
*/
@Execution(ExecutionMode.SAME_THREAD)
-public abstract class AbstractParallelTest extends DocumentLibraryProvider {
+public abstract class AbstractParallelTest extends RepositoryOperations {
protected static final int NUM_THREAD_TO_LAUNCH = Runtime.getRuntime().availableProcessors() * 2;
@@ -71,19 +75,22 @@ public static void stopExecutor() {
}
protected void execute(final CountDownLatch endingLatch, final Callable task) {
+ DocumentLibrary documentLibrary = getDocumentLibrary();
executor.submit(() -> {
- try {
- AuthenticationUtil.runAsSystem(() -> task.call());
- } catch (Exception e) { //NOPMD Catch all exceptions that might occur in thread as they will not be thrown to main thread
- logger.error("Parallel testing error", e);
- } finally {
- endingLatch.countDown();
- }
+ DocumentLibraryExtension.withDocumentLibrary(documentLibrary, () -> {
+ try {
+ AuthenticationUtil.runAsSystem(() -> task.call());
+ } catch (Exception e) { //NOPMD Catch all exceptions that might occur in thread as they will not be thrown to main thread
+ logger.error("Parallel testing error", e);
+ } finally {
+ endingLatch.countDown();
+ }
+ });
});
}
protected RepositoryNodeBuilder buildNode(final String departmentName, final LocalDateTime date) {
- return buildNode()
+ return getDocumentLibrary().childNode()
.type(FilerTestConstants.Department.DocumentType.NAME)
.property(FilerTestConstants.Department.Aspect.PROP_NAME, departmentName)
.property(FilerTestConstants.ImportedAspect.PROP_DATE, date.atZone(ZoneId.systemDefault()));
@@ -139,7 +146,7 @@ protected void createAndDeleteNodesImpl() throws InterruptedException, BrokenBar
// Wait for node creation to finish and then assert node is indeed created
preparationAssertBarrier.await();
- assertThat(getPath(nodeToDelete.get())).isEqualTo(buildNodePath(departmentName, date));
+ assertThat(getPath(nodeToDelete.get())).isEqualTo(nodePath(departmentName, date));
preparationAssertBarrier.await();
// Wait for every task to finish job before asserting results
@@ -150,7 +157,7 @@ protected void createAndDeleteNodesImpl() throws InterruptedException, BrokenBar
// Assert all tasks were ready for parallel task execution
assertThat(startingBarrier.isBroken()).isFalse();
- assertThat(getPath(createdNode.get())).isEqualTo(buildNodePath(departmentName, date));
+ assertThat(getPath(createdNode.get())).isEqualTo(nodePath(departmentName, date));
assertThat(nodeService.exists(nodeToDelete.get().getNodeRef())).isFalse();
NodeRef nodeToDeleteParent = nodeToDelete.get().getParent();
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/BinaryOperationParallelTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/BinaryOperationParallelTest.java
index 2387706..33e1cf8 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/BinaryOperationParallelTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/BinaryOperationParallelTest.java
@@ -1,5 +1,6 @@
package com.atolcd.alfresco.filer.core.test.domain;
+import static com.atolcd.alfresco.filer.core.test.domain.util.NodePathUtils.nodePath;
import static java.util.UUID.randomUUID;
import static org.assertj.core.api.Assertions.assertThat;
@@ -134,8 +135,8 @@ public void updateAndDeleteNodesInSourceFolder() throws InterruptedException, Br
// Wait for node creation to finish and then assert all nodes are indeed created
preparationAssertBarrier.await();
- assertThat(getPath(nodeToUpdate.get())).isEqualTo(buildNodePath(departmentName, sourceDate));
- assertThat(getPath(nodeToDelete.get())).isEqualTo(buildNodePath(departmentName, sourceDate));
+ assertThat(getPath(nodeToUpdate.get())).isEqualTo(nodePath(departmentName, sourceDate));
+ assertThat(getPath(nodeToDelete.get())).isEqualTo(nodePath(departmentName, sourceDate));
preparationAssertBarrier.await();
// Wait for every task to finish job before asserting results
@@ -146,7 +147,7 @@ public void updateAndDeleteNodesInSourceFolder() throws InterruptedException, Br
// Assert all tasks were ready for parallel task execution
assertThat(startingBarrier.isBroken()).isFalse();
- assertThat(getPath(nodeToUpdate.get())).isEqualTo(buildNodePath(departmentName, targetDate));
+ assertThat(getPath(nodeToUpdate.get())).isEqualTo(nodePath(departmentName, targetDate));
assertThat(nodeService.exists(nodeToDelete.get().getNodeRef())).isFalse();
assertThat(nodeService.exists(nodeToDelete.get().getParent())).isFalse();
@@ -225,8 +226,8 @@ public void updateAndDeleteNodesInTargetFolder() throws InterruptedException, Br
// Wait for node creation to finish and then assert all nodes are indeed created
preparationAssertBarrier.await();
- assertThat(getPath(nodeToUpdate.get())).isEqualTo(buildNodePath(departmentName, sourceDate));
- assertThat(getPath(nodeToDelete.get())).isEqualTo(buildNodePath(departmentName, targetDate));
+ assertThat(getPath(nodeToUpdate.get())).isEqualTo(nodePath(departmentName, sourceDate));
+ assertThat(getPath(nodeToDelete.get())).isEqualTo(nodePath(departmentName, targetDate));
preparationAssertBarrier.await();
// Wait for every task to finish job before asserting results
@@ -237,7 +238,7 @@ public void updateAndDeleteNodesInTargetFolder() throws InterruptedException, Br
// Assert all tasks were ready for parallel task execution
assertThat(startingBarrier.isBroken()).isFalse();
- assertThat(getPath(nodeToUpdate.get())).isEqualTo(buildNodePath(departmentName, targetDate));
+ assertThat(getPath(nodeToUpdate.get())).isEqualTo(nodePath(departmentName, targetDate));
assertThat(nodeService.exists(nodeToDelete.get().getNodeRef())).isFalse();
NodeRef nodeToDeleteParent = nodeToDelete.get().getParent();
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DeniedFilerActionTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DeniedFilerActionTest.java
index f6bd5f9..e41f3f1 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DeniedFilerActionTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DeniedFilerActionTest.java
@@ -1,5 +1,6 @@
package com.atolcd.alfresco.filer.core.test.domain;
+import static com.atolcd.alfresco.filer.core.test.framework.DocumentLibraryExtension.getDocumentLibrary;
import static java.util.UUID.randomUUID;
import static org.assertj.core.api.Assertions.assertThat;
@@ -14,9 +15,9 @@
import com.atolcd.alfresco.filer.core.model.FilerException;
import com.atolcd.alfresco.filer.core.model.RepositoryNode;
import com.atolcd.alfresco.filer.core.service.FilerModelService;
-import com.atolcd.alfresco.filer.core.test.framework.DocumentLibraryProvider;
+import com.atolcd.alfresco.filer.core.test.framework.RepositoryOperations;
-public class DeniedFilerActionTest extends DocumentLibraryProvider {
+public class DeniedFilerActionTest extends RepositoryOperations {
@Autowired
private FilerModelService filerModelService;
@@ -28,7 +29,7 @@ public class DeniedFilerActionTest extends DocumentLibraryProvider {
public void withTypeContent() {
String name = randomUUID().toString();
- RepositoryNode node = buildNode()
+ RepositoryNode node = getDocumentLibrary().childNode()
.type(ContentModel.TYPE_CONTENT)
.aspect(filerModelService.getFileableAspect())
.named(name)
@@ -52,7 +53,7 @@ public void withTypeContent() {
public void withTypeFolder() {
String name = randomUUID().toString();
- RepositoryNode node = buildNode()
+ RepositoryNode node = getDocumentLibrary().childNode()
.type(ContentModel.TYPE_FOLDER)
.aspect(filerModelService.getFileableAspect())
.named(name)
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DepartmentContentFilerActionTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DepartmentContentFilerActionTest.java
index 796afa4..4a7834b 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DepartmentContentFilerActionTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DepartmentContentFilerActionTest.java
@@ -1,5 +1,7 @@
package com.atolcd.alfresco.filer.core.test.domain;
+import static com.atolcd.alfresco.filer.core.test.domain.util.NodePathUtils.nodePath;
+import static com.atolcd.alfresco.filer.core.test.framework.DocumentLibraryExtension.getDocumentLibrary;
import static java.util.UUID.randomUUID;
import static org.assertj.core.api.Assertions.assertThat;
@@ -21,10 +23,10 @@
import com.atolcd.alfresco.filer.core.model.RepositoryNode;
import com.atolcd.alfresco.filer.core.service.FilerModelService;
import com.atolcd.alfresco.filer.core.test.domain.content.model.FilerTestConstants;
-import com.atolcd.alfresco.filer.core.test.framework.TestApplicationContext;
-import com.atolcd.alfresco.filer.core.test.framework.DocumentLibraryProvider;
+import com.atolcd.alfresco.filer.core.test.framework.RepositoryOperations;
+import com.atolcd.alfresco.filer.core.test.framework.TestDocumentLibrary;
-public class DepartmentContentFilerActionTest extends DocumentLibraryProvider {
+public class DepartmentContentFilerActionTest extends RepositoryOperations {
@Autowired
private FilerModelService filerModelService;
@@ -33,14 +35,14 @@ public class DepartmentContentFilerActionTest extends DocumentLibraryProvider {
private NodeService nodeService;
@Nested
- // @TestApplicationContext is necessary as Spring does not find the configuration of nested class from the enclosing class
+ // @TestDocumentLibrary is necessary as Spring does not find the configuration of nested class from the enclosing class
// See https://github.com/spring-projects/spring-framework/issues/19930
- @TestApplicationContext
+ @TestDocumentLibrary
public class DepartmentDocument {
@Test
public void filerAspectHierarchy() {
- RepositoryNode node = buildNode()
+ RepositoryNode node = getDocumentLibrary().childNode()
.type(FilerTestConstants.Department.DocumentType.NAME)
.property(FilerTestConstants.Department.Aspect.PROP_NAME, randomUUID())
.build();
@@ -63,7 +65,7 @@ public void filerAspectHierarchy() {
public void typeHierarchy() {
QName type = FilerTestConstants.Department.DocumentType.NAME;
- RepositoryNode node = buildNode()
+ RepositoryNode node = getDocumentLibrary().childNode()
.type(FilerTestConstants.Department.DocumentType.NAME)
.property(FilerTestConstants.Department.Aspect.PROP_NAME, randomUUID())
.build();
@@ -85,7 +87,7 @@ public void withImportDate() {
String departmentName = randomUUID().toString();
LocalDateTime date = LocalDateTime.of(2004, 8, 12, 0, 0, 0);
- RepositoryNode node = buildNode()
+ RepositoryNode node = getDocumentLibrary().childNode()
.type(FilerTestConstants.Department.DocumentType.NAME)
.property(FilerTestConstants.Department.Aspect.PROP_NAME, departmentName)
.property(FilerTestConstants.ImportedAspect.PROP_DATE, date.atZone(ZoneId.systemDefault()))
@@ -93,21 +95,21 @@ public void withImportDate() {
createNode(node);
- assertThat(getPath(node)).isEqualTo(buildNodePath(departmentName, date));
+ assertThat(getPath(node)).isEqualTo(nodePath(departmentName, date));
}
@Test
public void withoutImportDate() {
String departmentName = randomUUID().toString();
- RepositoryNode node = buildNode()
+ RepositoryNode node = getDocumentLibrary().childNode()
.type(FilerTestConstants.Department.DocumentType.NAME)
.property(FilerTestConstants.Department.Aspect.PROP_NAME, departmentName)
.build();
createNode(node);
- assertThat(getPath(node)).isEqualTo(buildNodePath(departmentName, LocalDateTime.now()));
+ assertThat(getPath(node)).isEqualTo(nodePath(departmentName, LocalDateTime.now()));
}
@Test
@@ -117,7 +119,7 @@ public void withImportDateInWrongSegment() {
LocalDateTime targetDate = LocalDateTime.of(2002, 4, 6, 0, 0, 0);
// Create a node with the wrong date to create corresponding folder
- RepositoryNode wrongSegmentNode = buildNode()
+ RepositoryNode wrongSegmentNode = getDocumentLibrary().childNode()
.type(FilerTestConstants.Department.DocumentType.NAME)
.property(FilerTestConstants.Department.Aspect.PROP_NAME, departmentNom)
.property(FilerTestConstants.ImportedAspect.PROP_DATE, wrongDate.atZone(ZoneId.systemDefault()))
@@ -126,7 +128,7 @@ public void withImportDateInWrongSegment() {
createNode(wrongSegmentNode);
// Create node with the target date in the wrong folder
- RepositoryNode node = buildNode()
+ RepositoryNode node = getDocumentLibrary().childNode()
.type(FilerTestConstants.Department.DocumentType.NAME)
.parent(wrongSegmentNode.getParent())
.property(FilerTestConstants.Department.Aspect.PROP_NAME, departmentNom)
@@ -135,7 +137,7 @@ public void withImportDateInWrongSegment() {
createNode(node);
- assertThat(getPath(node)).isEqualTo(buildNodePath(departmentNom, targetDate));
+ assertThat(getPath(node)).isEqualTo(nodePath(departmentNom, targetDate));
}
@Test
@@ -145,7 +147,7 @@ public void updateImportDate() {
LocalDateTime targetDate = LocalDateTime.of(2002, 4, 6, 0, 0, 0);
// Create node that will be updated
- RepositoryNode node = buildNode()
+ RepositoryNode node = getDocumentLibrary().childNode()
.type(FilerTestConstants.Department.DocumentType.NAME)
.property(FilerTestConstants.Department.Aspect.PROP_NAME, departmentNom)
.property(FilerTestConstants.ImportedAspect.PROP_DATE, sourceDate.atZone(ZoneId.systemDefault()))
@@ -153,7 +155,7 @@ public void updateImportDate() {
createNode(node);
- assertThat(getPath(node)).isEqualTo(buildNodePath(departmentNom, sourceDate));
+ assertThat(getPath(node)).isEqualTo(nodePath(departmentNom, sourceDate));
// Get ancestors before updating node as they will change
NodeRef oldParent = node.getParent();
@@ -167,7 +169,7 @@ public void updateImportDate() {
assertThat(nodeService.exists(oldParent)).isFalse();
assertThat(nodeService.exists(oldGrandParent)).isFalse();
- assertThat(getPath(node)).isEqualTo(buildNodePath(departmentNom, targetDate));
+ assertThat(getPath(node)).isEqualTo(nodePath(departmentNom, targetDate));
}
@Test
@@ -176,7 +178,7 @@ public void deleteDocument() {
LocalDateTime date = LocalDateTime.of(2004, 8, 12, 0, 0, 0);
// Create node that will be deleted
- RepositoryNode node = buildNode()
+ RepositoryNode node = getDocumentLibrary().childNode()
.type(FilerTestConstants.Department.DocumentType.NAME)
.property(FilerTestConstants.Department.Aspect.PROP_NAME, departmentNom)
.property(FilerTestConstants.ImportedAspect.PROP_DATE, date.atZone(ZoneId.systemDefault()))
@@ -184,7 +186,7 @@ public void deleteDocument() {
createNode(node);
- assertThat(getPath(node)).isEqualTo(buildNodePath(departmentNom, date));
+ assertThat(getPath(node)).isEqualTo(nodePath(departmentNom, date));
// Get ancestors before deleting node
NodeRef grandParent = nodeService.getPrimaryParent(node.getParent()).getParentRef();
@@ -203,13 +205,13 @@ public void multipleDocumentWithSameImportDate() {
String departmentName = randomUUID().toString();
LocalDateTime date = LocalDateTime.of(2004, 8, 12, 0, 0, 0);
- RepositoryNode firstNode = buildNode()
+ RepositoryNode firstNode = getDocumentLibrary().childNode()
.type(FilerTestConstants.Department.DocumentType.NAME)
.property(FilerTestConstants.Department.Aspect.PROP_NAME, departmentName)
.property(FilerTestConstants.ImportedAspect.PROP_DATE, date.atZone(ZoneId.systemDefault()))
.build();
- RepositoryNode secondNode = buildNode()
+ RepositoryNode secondNode = getDocumentLibrary().childNode()
.type(FilerTestConstants.Department.DocumentType.NAME)
.property(FilerTestConstants.Department.Aspect.PROP_NAME, departmentName)
.property(FilerTestConstants.ImportedAspect.PROP_DATE, date.atZone(ZoneId.systemDefault()))
@@ -218,7 +220,7 @@ public void multipleDocumentWithSameImportDate() {
createNode(firstNode);
createNode(secondNode);
- String nodePath = buildNodePath(departmentName, date);
+ String nodePath = nodePath(departmentName, date);
assertThat(getPath(firstNode)).isEqualTo(nodePath);
assertThat(getPath(secondNode)).isEqualTo(nodePath);
@@ -230,7 +232,7 @@ public void specialDocumentTypeWithoutImportDate() {
QName type = FilerTestConstants.SpecialDocumentType.NAME;
String departmentName = randomUUID().toString();
- RepositoryNode node = buildNode()
+ RepositoryNode node = getDocumentLibrary().childNode()
.type(type)
.property(FilerTestConstants.Department.Aspect.PROP_NAME, departmentName)
.build();
@@ -238,6 +240,6 @@ public void specialDocumentTypeWithoutImportDate() {
createNode(node);
assertThat(node.getType()).isEqualTo(type);
- assertThat(getPath(node)).isEqualTo(buildNodePath(departmentName, LocalDateTime.now()));
+ assertThat(getPath(node)).isEqualTo(nodePath(departmentName, LocalDateTime.now()));
}
}
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DepartmentFolderFilerActionTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DepartmentFolderFilerActionTest.java
index e858486..1aa118d 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DepartmentFolderFilerActionTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DepartmentFolderFilerActionTest.java
@@ -1,5 +1,7 @@
package com.atolcd.alfresco.filer.core.test.domain;
+import static com.atolcd.alfresco.filer.core.test.domain.util.NodePathUtils.nodePath;
+import static com.atolcd.alfresco.filer.core.test.framework.DocumentLibraryExtension.getDocumentLibrary;
import static java.util.UUID.randomUUID;
import static org.assertj.core.api.Assertions.assertThat;
@@ -14,15 +16,15 @@
import com.atolcd.alfresco.filer.core.model.RepositoryNode;
import com.atolcd.alfresco.filer.core.test.domain.content.model.FilerTestConstants;
-import com.atolcd.alfresco.filer.core.test.framework.DocumentLibraryProvider;
+import com.atolcd.alfresco.filer.core.test.framework.RepositoryOperations;
-public class DepartmentFolderFilerActionTest extends DocumentLibraryProvider {
+public class DepartmentFolderFilerActionTest extends RepositoryOperations {
@Test
public void withDepartmentName() {
String departmentName = randomUUID().toString();
- RepositoryNode node = buildNode()
+ RepositoryNode node = getDocumentLibrary().childNode()
.type(FilerTestConstants.Department.FolderType.NAME)
.property(FilerTestConstants.Department.Aspect.PROP_NAME, departmentName)
.build();
@@ -30,13 +32,13 @@ public void withDepartmentName() {
createNode(node);
assertThat(node.getProperty(FilerTestConstants.Department.Aspect.PROP_NAME, String.class)).isEqualTo(departmentName);
- assertThat(getPath(node)).isEqualTo(buildSitePath());
+ assertThat(getPath(node)).isEqualTo(getDocumentLibrary().getPath());
}
@Test
public void updateDepartmentName() {
// Create folder node
- RepositoryNode departmentFolderNode = buildNode()
+ RepositoryNode departmentFolderNode = getDocumentLibrary().childNode()
.type(FilerTestConstants.Department.FolderType.NAME)
.property(FilerTestConstants.Department.Aspect.PROP_NAME, randomUUID())
.build();
@@ -45,7 +47,7 @@ public void updateDepartmentName() {
// Create document node in folder
LocalDateTime date = LocalDateTime.of(2004, 8, 12, 0, 0, 0);
- RepositoryNode documentNode = buildNode()
+ RepositoryNode documentNode = getDocumentLibrary().childNode()
.type(FilerTestConstants.Department.DocumentType.NAME)
.property(FilerTestConstants.Department.Aspect.PROP_NAME,
departmentFolderNode.getProperty(FilerTestConstants.Department.Aspect.PROP_NAME, String.class))
@@ -55,7 +57,7 @@ public void updateDepartmentName() {
createNode(documentNode);
assertThat(getPath(documentNode)).isEqualTo(
- buildNodePath(departmentFolderNode.getProperty(FilerTestConstants.Department.Aspect.PROP_NAME, String.class), date));
+ nodePath(departmentFolderNode.getProperty(FilerTestConstants.Department.Aspect.PROP_NAME, String.class), date));
// Update folder's name
Map departmentNameProperty = Collections.singletonMap(FilerTestConstants.Department.Aspect.PROP_NAME,
@@ -67,6 +69,6 @@ public void updateDepartmentName() {
fetchNode(documentNode);
assertThat(getPath(documentNode)).isEqualTo(
- buildNodePath(departmentFolderNode.getProperty(FilerTestConstants.Department.Aspect.PROP_NAME, String.class), date));
+ nodePath(departmentFolderNode.getProperty(FilerTestConstants.Department.Aspect.PROP_NAME, String.class), date));
}
}
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/PropertyInheritanceTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/PropertyInheritanceTest.java
index 4894b6e..816c5ab 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/PropertyInheritanceTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/PropertyInheritanceTest.java
@@ -1,5 +1,6 @@
package com.atolcd.alfresco.filer.core.test.domain;
+import static com.atolcd.alfresco.filer.core.test.framework.DocumentLibraryExtension.getDocumentLibrary;
import static java.util.UUID.randomUUID;
import static org.assertj.core.api.Assertions.assertThat;
@@ -16,16 +17,16 @@
import com.atolcd.alfresco.filer.core.model.RepositoryNode;
import com.atolcd.alfresco.filer.core.test.domain.content.model.FilerTestConstants;
-import com.atolcd.alfresco.filer.core.test.framework.DocumentLibraryProvider;
+import com.atolcd.alfresco.filer.core.test.framework.RepositoryOperations;
-public class PropertyInheritanceTest extends DocumentLibraryProvider {
+public class PropertyInheritanceTest extends RepositoryOperations {
@Autowired
private NodeService nodeService;
@Test
public void createNodeInDepartmentFolder() {
- RepositoryNode folderNode = buildNode()
+ RepositoryNode folderNode = getDocumentLibrary().childNode()
.type(FilerTestConstants.Department.FolderType.NAME)
.property(FilerTestConstants.Department.Aspect.PROP_NAME, randomUUID())
.property(FilerTestConstants.Department.Aspect.PROP_ID, randomUUID())
@@ -34,7 +35,7 @@ public void createNodeInDepartmentFolder() {
createNode(folderNode);
// Create node in department folder and check if properties have been inheritated from department folder
- RepositoryNode testNode = buildNode()
+ RepositoryNode testNode = getDocumentLibrary().childNode()
.type(FilerTestConstants.Department.DocumentType.NAME)
.parent(folderNode.getNodeRef())
.build();
@@ -50,7 +51,7 @@ public void createNodeInDepartmentFolder() {
// Test fails. TODO Tested code need correction.
// @Test
// public void setWrongValuePropertyOnDocumentNode() {
-// RepositoryNode folderNode = buildNode()
+// RepositoryNode folderNode = getDocumentLibrary().childNode()
// .type(FilerTestConstants.Department.FolderType.NAME)
// .property(FilerTestConstants.Department.Aspect.PROP_NAME, randomUUID())
// .property(FilerTestConstants.Department.Aspect.PROP_ID, randomUUID())
@@ -58,7 +59,7 @@ public void createNodeInDepartmentFolder() {
//
// createNode(folderNode);
//
-// RepositoryNode testNode = buildNode()
+// RepositoryNode testNode = getDocumentLibrary().childNode()
// .type(FilerTestConstants.Department.DocumentType.NAME)
// .parent(folderNode.getNodeRef())
// .build();
@@ -78,7 +79,7 @@ public void createNodeInDepartmentFolder() {
@Test
public void changePropertyOfDepartmentFolder() {
- RepositoryNode testNode = buildNode()
+ RepositoryNode testNode = getDocumentLibrary().childNode()
.type(FilerTestConstants.Department.DocumentType.NAME)
.property(FilerTestConstants.Department.Aspect.PROP_NAME, randomUUID())
.property(FilerTestConstants.Department.Aspect.PROP_ID, randomUUID())
@@ -107,7 +108,7 @@ public void changePropertyOfDepartmentFolder() {
@Test
public void createNodeInDepartmentFolderWithWrongProperty() {
- RepositoryNode folderNode = buildNode()
+ RepositoryNode folderNode = getDocumentLibrary().childNode()
.type(FilerTestConstants.Department.FolderType.NAME)
.property(FilerTestConstants.Department.Aspect.PROP_NAME, randomUUID())
.property(FilerTestConstants.Department.Aspect.PROP_ID, randomUUID())
@@ -116,7 +117,7 @@ public void createNodeInDepartmentFolderWithWrongProperty() {
createNode(folderNode);
// Create node in department folder with wrong property and check if correct property is inherited from folder
- RepositoryNode testNode = buildNode()
+ RepositoryNode testNode = getDocumentLibrary().childNode()
.type(FilerTestConstants.Department.DocumentType.NAME)
.parent(folderNode.getNodeRef())
.property(FilerTestConstants.Department.Aspect.PROP_ID, randomUUID())
@@ -135,7 +136,7 @@ public void createNodeInDepartmentFolderWithWrongProperty() {
// public void createNodeInDocumentLibraryWithPropertiesForFiler() {
// String departmentName = randomUUID().toString();
//
-// RepositoryNode fixtureNode = buildNode()
+// RepositoryNode fixtureNode = getDocumentLibrary().childNode()
// .type(FilerTestConstants.Department.DocumentType.NAME)
// .property(FilerTestConstants.Department.Aspect.PROP_NAME, departmentName)
// .property(FilerTestConstants.Department.Aspect.PROP_ID, randomUUID())
@@ -145,7 +146,7 @@ public void createNodeInDepartmentFolderWithWrongProperty() {
//
// // Create node in document library with all required properties to be filed
// // and check if it has been filed correctly with property inheritance applied
-// RepositoryNode testNode = buildNode()
+// RepositoryNode testNode = getDocumentLibrary().childNode()
// .type(FilerTestConstants.Department.DocumentType.NAME)
// .property(FilerTestConstants.Department.Aspect.PROP_NAME, departmentName)
// .build();
@@ -162,7 +163,7 @@ public void createNodeInDepartmentFolderWithWrongProperty() {
@Test
public void createNodeInFolderHierarchyWithMultipleLevelInheritance() {
- RepositoryNode departmentFolderNode = buildNode()
+ RepositoryNode departmentFolderNode = getDocumentLibrary().childNode()
.type(FilerTestConstants.Department.FolderType.NAME)
.property(FilerTestConstants.Department.Aspect.PROP_NAME, randomUUID())
.property(FilerTestConstants.Department.Aspect.PROP_ID, randomUUID())
@@ -171,7 +172,7 @@ public void createNodeInFolderHierarchyWithMultipleLevelInheritance() {
createNode(departmentFolderNode);
// Create management folder in department folder and check if department properties are inherited
- RepositoryNode managementFolderNode = buildNode()
+ RepositoryNode managementFolderNode = getDocumentLibrary().childNode()
.type(FilerTestConstants.Department.Management.DocumentType.NAME)
.parent(departmentFolderNode.getNodeRef())
.property(FilerTestConstants.Department.Management.Aspect.PROP_ID, randomUUID())
@@ -185,7 +186,7 @@ public void createNodeInFolderHierarchyWithMultipleLevelInheritance() {
.isEqualTo(departmentFolderNode.getProperty(FilerTestConstants.Department.Aspect.PROP_ID, String.class));
// Create node in management folder and check if department and management properties are inherited
- RepositoryNode testNode = buildNode()
+ RepositoryNode testNode = getDocumentLibrary().childNode()
.type(FilerTestConstants.Department.Management.DocumentType.NAME)
.parent(managementFolderNode.getParent())
.build();
@@ -202,6 +203,6 @@ public void createNodeInFolderHierarchyWithMultipleLevelInheritance() {
private NodeRef getDepartmentFolder(final RepositoryNode node) {
String departmentName = node.getProperty(FilerTestConstants.Department.Aspect.PROP_NAME, String.class);
- return nodeService.getChildByName(getDocumentLibrary(), ContentModel.ASSOC_CONTAINS, departmentName);
+ return nodeService.getChildByName(getDocumentLibrary().getNodeRef(), ContentModel.ASSOC_CONTAINS, departmentName);
}
}
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/UnaryOperationParallelTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/UnaryOperationParallelTest.java
index d4b85ea..33a3a88 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/UnaryOperationParallelTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/UnaryOperationParallelTest.java
@@ -1,5 +1,6 @@
package com.atolcd.alfresco.filer.core.test.domain;
+import static com.atolcd.alfresco.filer.core.test.domain.util.NodePathUtils.nodePath;
import static java.util.UUID.randomUUID;
import static org.assertj.core.api.Assertions.assertThat;
@@ -27,7 +28,7 @@
import com.atolcd.alfresco.filer.core.model.RepositoryNode;
import com.atolcd.alfresco.filer.core.test.domain.content.model.FilerTestConstants;
-import com.atolcd.alfresco.filer.core.test.framework.DocumentLibraryProvider;
+import com.atolcd.alfresco.filer.core.test.framework.RepositoryOperations;
/**
* Test multiple executions in parallel of one operation
@@ -65,9 +66,9 @@ public void createMultipleNodes() throws InterruptedException {
// Assert all threads were ready for parallel createNode
assertThat(startingBarrier.isBroken()).isFalse();
- assertThat(results.stream().map(DocumentLibraryProvider::getPath))
+ assertThat(results.stream().map(RepositoryOperations::getPath))
.hasSize(NUM_THREAD_TO_LAUNCH)
- .containsOnly(buildNodePath(departmentName, date));
+ .containsOnly(nodePath(departmentName, date));
}
@Test
@@ -115,7 +116,7 @@ public void updateMultipleNodes() throws InterruptedException, BrokenBarrierExce
preparationAssertBarrier.await();
assertThat(results).hasSize(NUM_THREAD_TO_LAUNCH);
for (RepositoryNode node : results) {
- assertThat(getPath(node)).isEqualTo(buildNodePath(departmentName, sourceDate));
+ assertThat(getPath(node)).isEqualTo(nodePath(departmentName, sourceDate));
}
preparationAssertBarrier.await();
@@ -125,9 +126,9 @@ public void updateMultipleNodes() throws InterruptedException, BrokenBarrierExce
// Assert all threads were ready for parallel updateNode
assertThat(startingBarrier.isBroken()).isFalse();
- assertThat(results.stream().map(DocumentLibraryProvider::getPath))
+ assertThat(results.stream().map(RepositoryOperations::getPath))
.hasSize(NUM_THREAD_TO_LAUNCH)
- .containsOnly(buildNodePath(departmentName, targetDate));
+ .containsOnly(nodePath(departmentName, targetDate));
assertThat(segmentAncestors.stream().map(nodeService::exists)).isNotEmpty().containsOnly(false);
assertThat(closestNonSegmentAncestor.stream().map(nodeService::exists)).isNotEmpty().containsOnly(true);
@@ -176,7 +177,7 @@ public void deleteMultipleNodes() throws InterruptedException, BrokenBarrierExce
preparationAssertBarrier.await();
assertThat(results).hasSize(NUM_THREAD_TO_LAUNCH);
for (RepositoryNode node : results) {
- assertThat(getPath(node)).isEqualTo(buildNodePath(departmentName, date));
+ assertThat(getPath(node)).isEqualTo(nodePath(departmentName, date));
}
preparationAssertBarrier.await();
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/util/NodePathUtils.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/util/NodePathUtils.java
new file mode 100644
index 0000000..c46a7f8
--- /dev/null
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/util/NodePathUtils.java
@@ -0,0 +1,18 @@
+package com.atolcd.alfresco.filer.core.test.domain.util;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+
+import com.atolcd.alfresco.filer.core.test.framework.DocumentLibraryExtension;
+
+public final class NodePathUtils {
+
+ public static String nodePath(final String departmentName, final LocalDateTime date) {
+ return DocumentLibraryExtension.getDocumentLibrary().childPath(
+ departmentName,
+ Integer.toString(date.getYear()),
+ date.format(DateTimeFormatter.ofPattern("MM")));
+ }
+
+ private NodePathUtils() {}
+}
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/DocumentLibrary.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/DocumentLibrary.java
new file mode 100644
index 0000000..70aeeb4
--- /dev/null
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/DocumentLibrary.java
@@ -0,0 +1,52 @@
+package com.atolcd.alfresco.filer.core.test.framework;
+
+import static java.util.UUID.randomUUID;
+
+import java.nio.file.Paths;
+import java.util.Optional;
+import java.util.function.Function;
+
+import org.alfresco.service.cmr.repository.NodeRef;
+
+import com.atolcd.alfresco.filer.core.model.RepositoryNode;
+import com.atolcd.alfresco.filer.core.model.impl.RepositoryNodeBuilder;
+
+public class DocumentLibrary {
+
+ private final String siteName;
+ private NodeRef nodeRef;
+ private String path;
+ private final Function pathProvider;
+
+ public DocumentLibrary(final String siteName, final Function pathProvider) {
+ this.siteName = siteName;
+ this.pathProvider = pathProvider;
+ }
+
+ public RepositoryNodeBuilder childNode() {
+ return RepositoryNode.builder()
+ .parent(nodeRef)
+ .named(randomUUID());
+ }
+
+ public String getSiteName() {
+ return siteName;
+ }
+
+ public NodeRef getNodeRef() {
+ return nodeRef;
+ }
+
+ public void setNodeRef(final NodeRef nodeRef) {
+ this.nodeRef = nodeRef;
+ }
+
+ public String getPath() {
+ path = Optional.ofNullable(path).orElseGet(() -> pathProvider.apply(nodeRef));
+ return path;
+ }
+
+ public String childPath(final String... children) {
+ return Paths.get(getPath(), children).toString();
+ }
+}
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/DocumentLibraryExtension.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/DocumentLibraryExtension.java
new file mode 100644
index 0000000..c2d501f
--- /dev/null
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/DocumentLibraryExtension.java
@@ -0,0 +1,101 @@
+package com.atolcd.alfresco.filer.core.test.framework;
+
+import static java.util.UUID.randomUUID;
+
+import java.nio.file.Paths;
+
+import org.alfresco.repo.security.authentication.AuthenticationUtil;
+import org.alfresco.repo.site.SiteServiceImpl;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.repository.NodeService;
+import org.alfresco.service.cmr.security.PermissionService;
+import org.alfresco.service.cmr.site.SiteService;
+import org.alfresco.service.cmr.site.SiteVisibility;
+import org.alfresco.service.cmr.tagging.TaggingService;
+import org.alfresco.service.transaction.TransactionService;
+import org.junit.jupiter.api.extension.AfterAllCallback;
+import org.junit.jupiter.api.extension.BeforeAllCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+public class DocumentLibraryExtension implements BeforeAllCallback, AfterAllCallback {
+
+ @Autowired
+ private TransactionService transactionService;
+ @Autowired
+ private SiteService siteService;
+ @Autowired
+ private TaggingService taggingService;
+ @Autowired
+ private NodeService nodeService;
+ @Autowired
+ private PermissionService permissionService;
+
+ private static ThreadLocal documentLibraryHolder = new ThreadLocal<>();
+
+ @Override
+ public void beforeAll(final ExtensionContext context) {
+ Class> clazz = context.getRequiredTestClass();
+ String siteName;
+ if (!clazz.isAnnotationPresent(TestDocumentLibrary.class)
+ || clazz.getAnnotation(TestDocumentLibrary.class).value().isEmpty()) {
+ siteName = randomUUID().toString();
+ } else {
+ siteName = clazz.getAnnotation(TestDocumentLibrary.class).value();
+ }
+
+ ApplicationContext applicationContext = SpringExtension.getApplicationContext(context);
+ applicationContext.getAutowireCapableBeanFactory().autowireBean(this);
+
+ DocumentLibrary documentLibrary = new DocumentLibrary(siteName, nodeRef -> {
+ return Paths
+ .get(nodeService.getPath(nodeRef).toDisplayPath(nodeService, permissionService), SiteService.DOCUMENT_LIBRARY)
+ .toString();
+ });
+ initDocumentLibrary(documentLibrary);
+ documentLibraryHolder.set(documentLibrary);
+ }
+
+ private void initDocumentLibrary(final DocumentLibrary documentLibrary) {
+ AuthenticationUtil.runAs(() -> {
+ transactionService.getRetryingTransactionHelper().doInTransaction(() -> {
+ String siteName = documentLibrary.getSiteName();
+
+ if (!siteService.hasSite(siteName)) {
+ siteService.createSite(siteName, siteName, siteName, siteName, SiteVisibility.PUBLIC);
+ }
+
+ NodeRef library = SiteServiceImpl.getSiteContainer(siteName, SiteService.DOCUMENT_LIBRARY, true, siteService,
+ transactionService, taggingService);
+ documentLibrary.setNodeRef(library);
+ return null;
+ }, false);
+ return null;
+ }, AuthenticationUtil.getAdminUserName());
+ }
+
+ @Override
+ public void afterAll(final ExtensionContext context) {
+ documentLibraryHolder.remove();
+ }
+
+ public static DocumentLibrary getDocumentLibrary() {
+ return documentLibraryHolder.get();
+ }
+
+ public static void withDocumentLibrary(final DocumentLibrary documentLibrary, final Runnable task) {
+ DocumentLibrary original = getDocumentLibrary();
+ documentLibraryHolder.set(documentLibrary);
+ try {
+ task.run();
+ } finally {
+ if (original == null) {
+ documentLibraryHolder.remove();
+ } else {
+ documentLibraryHolder.set(original);
+ }
+ }
+ }
+}
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/DocumentLibraryProvider.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/DocumentLibraryProvider.java
deleted file mode 100644
index 09d9520..0000000
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/DocumentLibraryProvider.java
+++ /dev/null
@@ -1,98 +0,0 @@
-package com.atolcd.alfresco.filer.core.test.framework;
-
-import static java.util.UUID.randomUUID;
-
-import java.nio.file.Paths;
-import java.time.LocalDateTime;
-import java.time.format.DateTimeFormatter;
-
-import javax.annotation.PostConstruct;
-
-import org.alfresco.repo.security.authentication.AuthenticationUtil;
-import org.alfresco.repo.site.SiteServiceImpl;
-import org.alfresco.service.cmr.repository.NodeRef;
-import org.alfresco.service.cmr.repository.NodeService;
-import org.alfresco.service.cmr.security.PermissionService;
-import org.alfresco.service.cmr.site.SiteService;
-import org.alfresco.service.cmr.site.SiteVisibility;
-import org.alfresco.service.cmr.tagging.TaggingService;
-import org.alfresco.service.transaction.TransactionService;
-import org.springframework.beans.factory.annotation.Autowired;
-
-import com.atolcd.alfresco.filer.core.model.RepositoryNode;
-import com.atolcd.alfresco.filer.core.model.impl.RepositoryNodeBuilder;
-
-public abstract class DocumentLibraryProvider extends RepositoryOperations {
-
- private static final String NODE_PATH = DocumentLibraryProvider.class + ".NODE_PATH";
-
- @Autowired
- private TransactionService transactionService;
- @Autowired
- private NodeService nodeService;
- @Autowired
- private PermissionService permissionService;
- @Autowired
- private SiteService siteService;
- @Autowired
- private TaggingService taggingService;
-
- private NodeRef documentLibrary;
-
- @PostConstruct
- private void initSite() {
- String siteName = getSiteName();
-
- AuthenticationUtil.runAs(() -> {
- doInTransaction(() -> {
- if (!siteService.hasSite(siteName)) {
- siteService.createSite(siteName, siteName, siteName, siteName, SiteVisibility.PUBLIC);
- }
-
- documentLibrary = SiteServiceImpl.getSiteContainer(siteName, SiteService.DOCUMENT_LIBRARY, true, siteService,
- transactionService, taggingService);
- });
- return null;
- }, AuthenticationUtil.getAdminUserName());
- }
-
- protected String getSiteName() {
- return randomUUID().toString();
- }
-
- @Override
- protected void fetchNodeImpl(final RepositoryNode node) {
- super.fetchNodeImpl(node);
- setPath(node, nodeService.getPath(node.getNodeRef()).toDisplayPath(nodeService, permissionService));
- }
-
- protected static String getPath(final RepositoryNode node) {
- return node.getExtension(NODE_PATH, String.class);
- }
-
- private static void setPath(final RepositoryNode node, final String path) {
- node.getExtensions().put(NODE_PATH, path);
- }
-
- protected String buildSitePath() {
- return Paths
- .get(nodeService.getPath(documentLibrary).toDisplayPath(nodeService, permissionService), SiteService.DOCUMENT_LIBRARY)
- .toString();
- }
-
- protected String buildNodePath(final String departmentName, final LocalDateTime date) {
- return Paths
- .get(buildSitePath(), departmentName, Integer.toString(date.getYear()), date.format(DateTimeFormatter.ofPattern("MM")))
- .toString();
- }
-
- protected RepositoryNodeBuilder buildNode() {
- return RepositoryNode.builder()
- .parent(documentLibrary)
- .named(randomUUID());
- }
-
- protected NodeRef getDocumentLibrary() {
- return documentLibrary;
- }
-}
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/RepositoryOperations.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/RepositoryOperations.java
index eba203c..9faa53b 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/RepositoryOperations.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/RepositoryOperations.java
@@ -7,6 +7,7 @@
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
+import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.transaction.TransactionService;
@@ -14,14 +15,17 @@
import org.springframework.beans.factory.annotation.Autowired;
import com.atolcd.alfresco.filer.core.model.RepositoryNode;
+import com.atolcd.alfresco.filer.core.util.FilerNodeUtils;
-@TestApplicationContext
+@TestDocumentLibrary
public class RepositoryOperations {
@Autowired
private TransactionService transactionService;
@Autowired
private NodeService nodeService;
+ @Autowired
+ private PermissionService permissionService;
protected final void createNode(final RepositoryNode node) {
doInTransaction(() -> {
@@ -42,19 +46,16 @@ protected void createNodeImpl(final RepositoryNode node) {
protected final void fetchNode(final RepositoryNode node) {
doInTransaction(() -> {
- fetchNodeImpl(node);
+ node.setParent(nodeService.getPrimaryParent(node.getNodeRef()).getParentRef());
+ node.setType(nodeService.getType(node.getNodeRef()));
+ node.getAspects().clear();
+ node.getAspects().addAll(nodeService.getAspects(node.getNodeRef()));
+ node.getProperties().clear();
+ node.getProperties().putAll(nodeService.getProperties(node.getNodeRef()));
+ FilerNodeUtils.setDisplayPath(node, nodeService.getPath(node.getNodeRef()).toDisplayPath(nodeService, permissionService));
}, true);
}
- protected void fetchNodeImpl(final RepositoryNode node) {
- node.setParent(nodeService.getPrimaryParent(node.getNodeRef()).getParentRef());
- node.setType(nodeService.getType(node.getNodeRef()));
- node.getAspects().clear();
- node.getAspects().addAll(nodeService.getAspects(node.getNodeRef()));
- node.getProperties().clear();
- node.getProperties().putAll(nodeService.getProperties(node.getNodeRef()));
- }
-
protected final void updateNode(final RepositoryNode node, final Map properties) {
doInTransaction(() -> {
nodeService.addProperties(node.getNodeRef(), properties);
@@ -69,6 +70,10 @@ protected final void deleteNode(final RepositoryNode node) {
});
}
+ protected static String getPath(final RepositoryNode node) {
+ return FilerNodeUtils.getDisplayPath(node);
+ }
+
private void bindTransactionListener(final RepositoryNode node) {
AlfrescoTransactionSupport.bindListener(new TransactionListenerAdapter() {
@Override
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/TestDocumentLibrary.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/TestDocumentLibrary.java
new file mode 100644
index 0000000..c633a0d
--- /dev/null
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/TestDocumentLibrary.java
@@ -0,0 +1,19 @@
+package com.atolcd.alfresco.filer.core.test.framework;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.junit.jupiter.api.extension.ExtendWith;
+
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+@TestApplicationContext
+@ExtendWith(DocumentLibraryExtension.class)
+public @interface TestDocumentLibrary {
+
+ String value() default "";
+}
From 04f092875652575446f29ff6ddae91119cce75bc Mon Sep 17 00:00:00 2001
From: Corentin PIANA
Date: Tue, 17 Sep 2019 15:13:59 +0200
Subject: [PATCH 32/58] Test: Using Path instead of String
Change-Id: Id5d7d676ebff4baa8f7744efd5a7e0848476bdbd
---
.../core/service/impl/FilerServiceImpl.java | 13 ++++++------
.../filer/core/util/FilerNodeUtils.java | 12 ++++++-----
.../test/domain/AbstractParallelTest.java | 7 ++++---
.../domain/BinaryOperationParallelTest.java | 1 +
.../DepartmentContentFilerActionTest.java | 20 ++++++++++---------
.../DepartmentFolderFilerActionTest.java | 11 +++++-----
.../domain/UnaryOperationParallelTest.java | 7 ++++---
.../core/test/domain/util/NodePathUtils.java | 3 ++-
.../core/test/framework/DocumentLibrary.java | 18 ++++++++++-------
.../framework/DocumentLibraryExtension.java | 3 +--
.../test/framework/RepositoryOperations.java | 6 +-----
11 files changed, 55 insertions(+), 46 deletions(-)
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerServiceImpl.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerServiceImpl.java
index c2ee3cf..a67407d 100644
--- a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerServiceImpl.java
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerServiceImpl.java
@@ -1,5 +1,7 @@
package com.atolcd.alfresco.filer.core.service.impl;
+import java.nio.file.Path;
+import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collection;
import java.util.Optional;
@@ -90,19 +92,18 @@ private void executeActionImpl(final FilerEvent event) {
// Put display path for logging purposes
if (LOGGER.isDebugEnabled()) {
// Compute display path now, because it may not exist afterwards if a filer has been removed
- FilerNodeUtils.setDisplayPath(node,
- nodeService.getPath(node.getNodeRef()).toDisplayPath(nodeService, permissionService));
+ FilerNodeUtils.setPath(node, nodeService.getPath(node.getNodeRef()).toDisplayPath(nodeService, permissionService));
}
// Execute filer action
event.setExecuted();
filerOperationService.execute(event.getAction().get(), node);
if (LOGGER.isDebugEnabled()) {
- String beforePath = FilerNodeUtils.getDisplayPath(node);
- String afterPath = nodeService.getPath(node.getNodeRef()).toDisplayPath(nodeService, permissionService);
- afterPath = afterPath.equals(beforePath) ? "Same location" : "It is now at " + afterPath;
+ Path beforePath = FilerNodeUtils.getPath(node);
+ Path afterPath = Paths.get(nodeService.getPath(node.getNodeRef()).toDisplayPath(nodeService, permissionService));
+ String afterLocation = afterPath.equals(beforePath) ? "Same location" : "It is now at " + afterPath.toString();
String afterName = (String) nodeService.getProperty(node.getNodeRef(), ContentModel.PROP_NAME);
afterName = node.getName().get().equals(afterName) ? "" : " and renamed " + afterName;
- LOGGER.debug("Executed filer on {} at {}: {}{}", event, beforePath, afterPath, afterName);
+ LOGGER.debug("Executed filer on {} at {}: {}{}", event, beforePath, afterLocation, afterName);
}
}
}
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/util/FilerNodeUtils.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/util/FilerNodeUtils.java
index 856af1f..a330973 100644
--- a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/util/FilerNodeUtils.java
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/util/FilerNodeUtils.java
@@ -1,5 +1,7 @@
package com.atolcd.alfresco.filer.core.util;
+import java.nio.file.Path;
+import java.nio.file.Paths;
import java.util.Objects;
import java.util.Optional;
@@ -10,7 +12,7 @@
public final class FilerNodeUtils {
- private static final String DISPLAY_PATH_KEY = "displayPath";
+ private static final String PATH_KEY = "path";
private static final String SITE_INFO_KEY = "siteInfo";
private static final String ORIGINAL_KEY = "original";
private static final String ORIGINAL_NODE_KEY = "originalNode";
@@ -49,12 +51,12 @@ public static void setOriginalNode(final RepositoryNode node, final RepositoryNo
node.getExtensions().put(ORIGINAL_NODE_KEY, originalNode);
}
- public static String getDisplayPath(final RepositoryNode node) {
- return Optional.ofNullable(node.getExtension(DISPLAY_PATH_KEY, String.class)).orElse("");
+ public static Path getPath(final RepositoryNode node) {
+ return node.getExtension(PATH_KEY, Path.class);
}
- public static void setDisplayPath(final RepositoryNode node, final String path) {
- node.getExtensions().put(DISPLAY_PATH_KEY, path);
+ public static void setPath(final RepositoryNode node, final String path) {
+ node.getExtensions().put(PATH_KEY, Paths.get(path));
}
private FilerNodeUtils() {}
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/AbstractParallelTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/AbstractParallelTest.java
index a701b7e..80d8f11 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/AbstractParallelTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/AbstractParallelTest.java
@@ -1,7 +1,7 @@
package com.atolcd.alfresco.filer.core.test.domain;
-import static com.atolcd.alfresco.filer.core.test.domain.util.NodePathUtils.nodePath;
import static com.atolcd.alfresco.filer.core.test.framework.DocumentLibraryExtension.getDocumentLibrary;
+import static com.atolcd.alfresco.filer.core.util.FilerNodeUtils.getPath;
import static java.util.UUID.randomUUID;
import static org.assertj.core.api.Assertions.assertThat;
@@ -31,6 +31,7 @@
import com.atolcd.alfresco.filer.core.model.RepositoryNode;
import com.atolcd.alfresco.filer.core.model.impl.RepositoryNodeBuilder;
import com.atolcd.alfresco.filer.core.test.domain.content.model.FilerTestConstants;
+import com.atolcd.alfresco.filer.core.test.domain.util.NodePathUtils;
import com.atolcd.alfresco.filer.core.test.framework.DocumentLibrary;
import com.atolcd.alfresco.filer.core.test.framework.DocumentLibraryExtension;
import com.atolcd.alfresco.filer.core.test.framework.RepositoryOperations;
@@ -146,7 +147,7 @@ protected void createAndDeleteNodesImpl() throws InterruptedException, BrokenBar
// Wait for node creation to finish and then assert node is indeed created
preparationAssertBarrier.await();
- assertThat(getPath(nodeToDelete.get())).isEqualTo(nodePath(departmentName, date));
+ assertThat(getPath(nodeToDelete.get())).isEqualTo(NodePathUtils.nodePath(departmentName, date));
preparationAssertBarrier.await();
// Wait for every task to finish job before asserting results
@@ -157,7 +158,7 @@ protected void createAndDeleteNodesImpl() throws InterruptedException, BrokenBar
// Assert all tasks were ready for parallel task execution
assertThat(startingBarrier.isBroken()).isFalse();
- assertThat(getPath(createdNode.get())).isEqualTo(nodePath(departmentName, date));
+ assertThat(getPath(createdNode.get())).isEqualTo(NodePathUtils.nodePath(departmentName, date));
assertThat(nodeService.exists(nodeToDelete.get().getNodeRef())).isFalse();
NodeRef nodeToDeleteParent = nodeToDelete.get().getParent();
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/BinaryOperationParallelTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/BinaryOperationParallelTest.java
index 33e1cf8..3744636 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/BinaryOperationParallelTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/BinaryOperationParallelTest.java
@@ -1,6 +1,7 @@
package com.atolcd.alfresco.filer.core.test.domain;
import static com.atolcd.alfresco.filer.core.test.domain.util.NodePathUtils.nodePath;
+import static com.atolcd.alfresco.filer.core.util.FilerNodeUtils.getPath;
import static java.util.UUID.randomUUID;
import static org.assertj.core.api.Assertions.assertThat;
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DepartmentContentFilerActionTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DepartmentContentFilerActionTest.java
index 4a7834b..51a095c 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DepartmentContentFilerActionTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DepartmentContentFilerActionTest.java
@@ -1,11 +1,12 @@
package com.atolcd.alfresco.filer.core.test.domain;
-import static com.atolcd.alfresco.filer.core.test.domain.util.NodePathUtils.nodePath;
import static com.atolcd.alfresco.filer.core.test.framework.DocumentLibraryExtension.getDocumentLibrary;
+import static com.atolcd.alfresco.filer.core.util.FilerNodeUtils.getPath;
import static java.util.UUID.randomUUID;
import static org.assertj.core.api.Assertions.assertThat;
import java.io.Serializable;
+import java.nio.file.Path;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Collections;
@@ -23,6 +24,7 @@
import com.atolcd.alfresco.filer.core.model.RepositoryNode;
import com.atolcd.alfresco.filer.core.service.FilerModelService;
import com.atolcd.alfresco.filer.core.test.domain.content.model.FilerTestConstants;
+import com.atolcd.alfresco.filer.core.test.domain.util.NodePathUtils;
import com.atolcd.alfresco.filer.core.test.framework.RepositoryOperations;
import com.atolcd.alfresco.filer.core.test.framework.TestDocumentLibrary;
@@ -95,7 +97,7 @@ public void withImportDate() {
createNode(node);
- assertThat(getPath(node)).isEqualTo(nodePath(departmentName, date));
+ assertThat(getPath(node)).isEqualTo(NodePathUtils.nodePath(departmentName, date));
}
@Test
@@ -109,7 +111,7 @@ public void withoutImportDate() {
createNode(node);
- assertThat(getPath(node)).isEqualTo(nodePath(departmentName, LocalDateTime.now()));
+ assertThat(getPath(node)).isEqualTo(NodePathUtils.nodePath(departmentName, LocalDateTime.now()));
}
@Test
@@ -137,7 +139,7 @@ public void withImportDateInWrongSegment() {
createNode(node);
- assertThat(getPath(node)).isEqualTo(nodePath(departmentNom, targetDate));
+ assertThat(getPath(node)).isEqualTo(NodePathUtils.nodePath(departmentNom, targetDate));
}
@Test
@@ -155,7 +157,7 @@ public void updateImportDate() {
createNode(node);
- assertThat(getPath(node)).isEqualTo(nodePath(departmentNom, sourceDate));
+ assertThat(getPath(node)).isEqualTo(NodePathUtils.nodePath(departmentNom, sourceDate));
// Get ancestors before updating node as they will change
NodeRef oldParent = node.getParent();
@@ -169,7 +171,7 @@ public void updateImportDate() {
assertThat(nodeService.exists(oldParent)).isFalse();
assertThat(nodeService.exists(oldGrandParent)).isFalse();
- assertThat(getPath(node)).isEqualTo(nodePath(departmentNom, targetDate));
+ assertThat(getPath(node)).isEqualTo(NodePathUtils.nodePath(departmentNom, targetDate));
}
@Test
@@ -186,7 +188,7 @@ public void deleteDocument() {
createNode(node);
- assertThat(getPath(node)).isEqualTo(nodePath(departmentNom, date));
+ assertThat(getPath(node)).isEqualTo(NodePathUtils.nodePath(departmentNom, date));
// Get ancestors before deleting node
NodeRef grandParent = nodeService.getPrimaryParent(node.getParent()).getParentRef();
@@ -220,7 +222,7 @@ public void multipleDocumentWithSameImportDate() {
createNode(firstNode);
createNode(secondNode);
- String nodePath = nodePath(departmentName, date);
+ Path nodePath = NodePathUtils.nodePath(departmentName, date);
assertThat(getPath(firstNode)).isEqualTo(nodePath);
assertThat(getPath(secondNode)).isEqualTo(nodePath);
@@ -240,6 +242,6 @@ public void specialDocumentTypeWithoutImportDate() {
createNode(node);
assertThat(node.getType()).isEqualTo(type);
- assertThat(getPath(node)).isEqualTo(nodePath(departmentName, LocalDateTime.now()));
+ assertThat(getPath(node)).isEqualTo(NodePathUtils.nodePath(departmentName, LocalDateTime.now()));
}
}
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DepartmentFolderFilerActionTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DepartmentFolderFilerActionTest.java
index 1aa118d..eaca0aa 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DepartmentFolderFilerActionTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DepartmentFolderFilerActionTest.java
@@ -1,7 +1,7 @@
package com.atolcd.alfresco.filer.core.test.domain;
-import static com.atolcd.alfresco.filer.core.test.domain.util.NodePathUtils.nodePath;
import static com.atolcd.alfresco.filer.core.test.framework.DocumentLibraryExtension.getDocumentLibrary;
+import static com.atolcd.alfresco.filer.core.util.FilerNodeUtils.getPath;
import static java.util.UUID.randomUUID;
import static org.assertj.core.api.Assertions.assertThat;
@@ -16,6 +16,7 @@
import com.atolcd.alfresco.filer.core.model.RepositoryNode;
import com.atolcd.alfresco.filer.core.test.domain.content.model.FilerTestConstants;
+import com.atolcd.alfresco.filer.core.test.domain.util.NodePathUtils;
import com.atolcd.alfresco.filer.core.test.framework.RepositoryOperations;
public class DepartmentFolderFilerActionTest extends RepositoryOperations {
@@ -56,8 +57,8 @@ public void updateDepartmentName() {
createNode(documentNode);
- assertThat(getPath(documentNode)).isEqualTo(
- nodePath(departmentFolderNode.getProperty(FilerTestConstants.Department.Aspect.PROP_NAME, String.class), date));
+ assertThat(getPath(documentNode)).isEqualTo(NodePathUtils
+ .nodePath(departmentFolderNode.getProperty(FilerTestConstants.Department.Aspect.PROP_NAME, String.class), date));
// Update folder's name
Map departmentNameProperty = Collections.singletonMap(FilerTestConstants.Department.Aspect.PROP_NAME,
@@ -68,7 +69,7 @@ public void updateDepartmentName() {
// Get document node and check if it has been updated automatically
fetchNode(documentNode);
- assertThat(getPath(documentNode)).isEqualTo(
- nodePath(departmentFolderNode.getProperty(FilerTestConstants.Department.Aspect.PROP_NAME, String.class), date));
+ assertThat(getPath(documentNode)).isEqualTo(NodePathUtils
+ .nodePath(departmentFolderNode.getProperty(FilerTestConstants.Department.Aspect.PROP_NAME, String.class), date));
}
}
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/UnaryOperationParallelTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/UnaryOperationParallelTest.java
index 33a3a88..892896f 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/UnaryOperationParallelTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/UnaryOperationParallelTest.java
@@ -1,6 +1,7 @@
package com.atolcd.alfresco.filer.core.test.domain;
import static com.atolcd.alfresco.filer.core.test.domain.util.NodePathUtils.nodePath;
+import static com.atolcd.alfresco.filer.core.util.FilerNodeUtils.getPath;
import static java.util.UUID.randomUUID;
import static org.assertj.core.api.Assertions.assertThat;
@@ -28,7 +29,7 @@
import com.atolcd.alfresco.filer.core.model.RepositoryNode;
import com.atolcd.alfresco.filer.core.test.domain.content.model.FilerTestConstants;
-import com.atolcd.alfresco.filer.core.test.framework.RepositoryOperations;
+import com.atolcd.alfresco.filer.core.util.FilerNodeUtils;
/**
* Test multiple executions in parallel of one operation
@@ -66,7 +67,7 @@ public void createMultipleNodes() throws InterruptedException {
// Assert all threads were ready for parallel createNode
assertThat(startingBarrier.isBroken()).isFalse();
- assertThat(results.stream().map(RepositoryOperations::getPath))
+ assertThat(results.stream().map(FilerNodeUtils::getPath))
.hasSize(NUM_THREAD_TO_LAUNCH)
.containsOnly(nodePath(departmentName, date));
}
@@ -126,7 +127,7 @@ public void updateMultipleNodes() throws InterruptedException, BrokenBarrierExce
// Assert all threads were ready for parallel updateNode
assertThat(startingBarrier.isBroken()).isFalse();
- assertThat(results.stream().map(RepositoryOperations::getPath))
+ assertThat(results.stream().map(FilerNodeUtils::getPath))
.hasSize(NUM_THREAD_TO_LAUNCH)
.containsOnly(nodePath(departmentName, targetDate));
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/util/NodePathUtils.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/util/NodePathUtils.java
index c46a7f8..d57523a 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/util/NodePathUtils.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/util/NodePathUtils.java
@@ -1,5 +1,6 @@
package com.atolcd.alfresco.filer.core.test.domain.util;
+import java.nio.file.Path;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@@ -7,7 +8,7 @@
public final class NodePathUtils {
- public static String nodePath(final String departmentName, final LocalDateTime date) {
+ public static Path nodePath(final String departmentName, final LocalDateTime date) {
return DocumentLibraryExtension.getDocumentLibrary().childPath(
departmentName,
Integer.toString(date.getYear()),
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/DocumentLibrary.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/DocumentLibrary.java
index 70aeeb4..66f7551 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/DocumentLibrary.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/DocumentLibrary.java
@@ -2,7 +2,7 @@
import static java.util.UUID.randomUUID;
-import java.nio.file.Paths;
+import java.nio.file.Path;
import java.util.Optional;
import java.util.function.Function;
@@ -15,10 +15,10 @@ public class DocumentLibrary {
private final String siteName;
private NodeRef nodeRef;
- private String path;
- private final Function pathProvider;
+ private Path path;
+ private final Function pathProvider;
- public DocumentLibrary(final String siteName, final Function pathProvider) {
+ public DocumentLibrary(final String siteName, final Function pathProvider) {
this.siteName = siteName;
this.pathProvider = pathProvider;
}
@@ -41,12 +41,16 @@ public void setNodeRef(final NodeRef nodeRef) {
this.nodeRef = nodeRef;
}
- public String getPath() {
+ public Path getPath() {
path = Optional.ofNullable(path).orElseGet(() -> pathProvider.apply(nodeRef));
return path;
}
- public String childPath(final String... children) {
- return Paths.get(getPath(), children).toString();
+ public Path childPath(final String... children) {
+ Path childPath = getPath();
+ for (String child : children) {
+ childPath = childPath.resolve(child);
+ }
+ return childPath;
}
}
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/DocumentLibraryExtension.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/DocumentLibraryExtension.java
index c2d501f..04a47f9 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/DocumentLibraryExtension.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/DocumentLibraryExtension.java
@@ -51,8 +51,7 @@ public void beforeAll(final ExtensionContext context) {
DocumentLibrary documentLibrary = new DocumentLibrary(siteName, nodeRef -> {
return Paths
- .get(nodeService.getPath(nodeRef).toDisplayPath(nodeService, permissionService), SiteService.DOCUMENT_LIBRARY)
- .toString();
+ .get(nodeService.getPath(nodeRef).toDisplayPath(nodeService, permissionService), SiteService.DOCUMENT_LIBRARY);
});
initDocumentLibrary(documentLibrary);
documentLibraryHolder.set(documentLibrary);
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/RepositoryOperations.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/RepositoryOperations.java
index 9faa53b..d52dc74 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/RepositoryOperations.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/RepositoryOperations.java
@@ -52,7 +52,7 @@ protected final void fetchNode(final RepositoryNode node) {
node.getAspects().addAll(nodeService.getAspects(node.getNodeRef()));
node.getProperties().clear();
node.getProperties().putAll(nodeService.getProperties(node.getNodeRef()));
- FilerNodeUtils.setDisplayPath(node, nodeService.getPath(node.getNodeRef()).toDisplayPath(nodeService, permissionService));
+ FilerNodeUtils.setPath(node, nodeService.getPath(node.getNodeRef()).toDisplayPath(nodeService, permissionService));
}, true);
}
@@ -70,10 +70,6 @@ protected final void deleteNode(final RepositoryNode node) {
});
}
- protected static String getPath(final RepositoryNode node) {
- return FilerNodeUtils.getDisplayPath(node);
- }
-
private void bindTransactionListener(final RepositoryNode node) {
AlfrescoTransactionSupport.bindListener(new TransactionListenerAdapter() {
@Override
From 042208a600c54a889dce2c6287d3c7df67a2b304 Mon Sep 17 00:00:00 2001
From: Corentin PIANA
Date: Wed, 18 Sep 2019 10:40:36 +0200
Subject: [PATCH 33/58] Use JUnit AnnotationUtils & refactor using Stream
Also adding error logging in AutowiredMockAwareMockitoExtensionTest
class.
Change-Id: Ib5725654f2fa8562e80f2ff1becc4d5221e322f5
---
...utowiredMockAwareMockitoExtensionTest.java | 20 +++++++++++++++
.../AutowiredMockAwareMockitoExtension.java | 25 ++++++++-----------
.../framework/DocumentLibraryExtension.java | 14 ++++++-----
3 files changed, 39 insertions(+), 20 deletions(-)
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/extension/AutowiredMockAwareMockitoExtensionTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/extension/AutowiredMockAwareMockitoExtensionTest.java
index 3fa2552..82d6bcd 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/extension/AutowiredMockAwareMockitoExtensionTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/extension/AutowiredMockAwareMockitoExtensionTest.java
@@ -21,12 +21,15 @@
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.engine.JupiterTestEngine;
import org.junit.platform.engine.DiscoverySelector;
+import org.junit.platform.engine.TestExecutionResult;
import org.junit.platform.launcher.LauncherDiscoveryRequest;
import org.junit.platform.testkit.engine.EngineExecutionResults;
import org.junit.platform.testkit.engine.EngineTestKit;
import org.junit.platform.testkit.engine.Events;
import org.mockito.Mockito;
import org.mockito.exceptions.misusing.UnnecessaryStubbingException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
@@ -35,12 +38,16 @@
public class AutowiredMockAwareMockitoExtensionTest {
+ private static final Logger LOGGER = LoggerFactory.getLogger(AutowiredMockAwareMockitoExtensionTest.class);
+
private final JupiterTestEngine engine = new JupiterTestEngine();
@Test
public void necessaryStubbing() {
Events testEvents = executeTestsForClass(NecessaryStubbingTestClass.class).tests();
+ logTestFailures(testEvents);
+
assertThat(testEvents.started().count()).isEqualTo(2);
assertThat(testEvents.succeeded().count()).isEqualTo(2);
assertThat(testEvents.skipped().count()).isEqualTo(0);
@@ -75,6 +82,8 @@ public void verifyStubbingIsResetted() {
public void unnecessaryStubbing() {
Events testEvents = executeTestsForClass(UnnecessaryStubbingTestClass.class).tests();
+ logTestFailures(testEvents);
+
assertThat(testEvents.started().count()).isEqualTo(2);
assertThat(testEvents.succeeded().count()).isEqualTo(2);
assertThat(testEvents.skipped().count()).isEqualTo(0);
@@ -108,6 +117,8 @@ public void verifyUnnecessaryStubbingDoesNotLeakOnOtherTest() {
public void stubbing() {
Events testEvents = executeTestsForClass(StubbingTestClass.class).tests();
+ logTestFailures(testEvents);
+
assertThat(testEvents.started().count()).isEqualTo(2);
assertThat(testEvents.succeeded().count()).isEqualTo(2);
assertThat(testEvents.skipped().count()).isEqualTo(0);
@@ -148,6 +159,15 @@ private EngineExecutionResults executeTestsForClass(final Class> testClass) {
return EngineTestKit.execute(this.engine, request);
}
+ private static void logTestFailures(final Events testEvents) {
+ testEvents.finished().failed().stream()
+ .map(event -> event.getPayload(TestExecutionResult.class))
+ .map(Optional::get) // Event of type FINISHED cannot have empty payload
+ .map(TestExecutionResult::getThrowable)
+ .map(Optional::get) // Throwable is always present on failed test
+ .forEach(thrown -> LOGGER.error("Extension test error:", thrown));
+ }
+
/**
* Expected exception catcher inspired by : {@link io.github.glytching.junit.extension.exception.ExpectedExceptionExtension}
*/
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/AutowiredMockAwareMockitoExtension.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/AutowiredMockAwareMockitoExtension.java
index a60e356..86847d7 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/AutowiredMockAwareMockitoExtension.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/AutowiredMockAwareMockitoExtension.java
@@ -1,10 +1,11 @@
package com.atolcd.alfresco.filer.core.test.framework;
import java.lang.reflect.Field;
-import java.util.ArrayList;
import java.util.List;
+import java.util.stream.Collectors;
import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.platform.commons.util.AnnotationUtils;
import org.mockito.Mockito;
import org.mockito.internal.util.MockUtil;
import org.mockito.junit.jupiter.MockitoExtension;
@@ -38,22 +39,18 @@ public void beforeEach(final ExtensionContext context) {
}
private static List getAutowiredMocks(final Object testInstance) {
- List result = new ArrayList<>();
+ List fields = AnnotationUtils.findAnnotatedFields(testInstance.getClass(),
+ Autowired.class, field -> MockUtil.isMock(getFieldValue(testInstance, field)));
+ return fields.stream().map(field -> getFieldValue(testInstance, field)).collect(Collectors.toList());
+ }
- Field[] fields = testInstance.getClass().getDeclaredFields();
- for (Field field : fields) {
+ private static Object getFieldValue(final Object testInstance, final Field field) {
+ try {
field.setAccessible(true);
- Object instance;
- try {
- instance = field.get(testInstance);
- if (field.isAnnotationPresent(Autowired.class) && MockUtil.isMock(instance)) {
- result.add(instance);
- }
- } catch (IllegalArgumentException | IllegalAccessException e) {
- throw new IllegalStateException(e);
- }
+ return field.get(testInstance);
+ } catch (IllegalArgumentException | IllegalAccessException e) {
+ throw new IllegalStateException(e);
}
- return result;
}
@Override
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/DocumentLibraryExtension.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/DocumentLibraryExtension.java
index 04a47f9..f11b436 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/DocumentLibraryExtension.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/DocumentLibraryExtension.java
@@ -3,6 +3,7 @@
import static java.util.UUID.randomUUID;
import java.nio.file.Paths;
+import java.util.Optional;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.site.SiteServiceImpl;
@@ -16,6 +17,7 @@
import org.junit.jupiter.api.extension.AfterAllCallback;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.platform.commons.util.AnnotationUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.junit.jupiter.SpringExtension;
@@ -38,14 +40,14 @@ public class DocumentLibraryExtension implements BeforeAllCallback, AfterAllCall
@Override
public void beforeAll(final ExtensionContext context) {
Class> clazz = context.getRequiredTestClass();
- String siteName;
- if (!clazz.isAnnotationPresent(TestDocumentLibrary.class)
- || clazz.getAnnotation(TestDocumentLibrary.class).value().isEmpty()) {
- siteName = randomUUID().toString();
- } else {
- siteName = clazz.getAnnotation(TestDocumentLibrary.class).value();
+ Optional annotation = AnnotationUtils.findAnnotation(clazz, TestDocumentLibrary.class);
+ if (!annotation.isPresent()) {
+ throw new IllegalStateException("Could not find TestDocumentLibrary annotation on class: " + clazz);
}
+ String siteName = annotation.map(TestDocumentLibrary::value).filter(value -> !value.isEmpty())
+ .orElseGet(() -> randomUUID().toString());
+
ApplicationContext applicationContext = SpringExtension.getApplicationContext(context);
applicationContext.getAutowireCapableBeanFactory().autowireBean(this);
From b848e81f8efbc1dbdbdabf204c28c9ff73be1d00 Mon Sep 17 00:00:00 2001
From: Corentin PIANA
Date: Wed, 18 Sep 2019 15:55:26 +0200
Subject: [PATCH 34/58] Correcting regex in PMD rules
Change-Id: Ia8431d5d9f07cde27c1531a31920048bc89a4af2
---
.../src/main/resources/filer/pmd-ruleset.xml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/alfresco-filer-quality/src/main/resources/filer/pmd-ruleset.xml b/alfresco-filer-quality/src/main/resources/filer/pmd-ruleset.xml
index c99cacb..4bcda5f 100644
--- a/alfresco-filer-quality/src/main/resources/filer/pmd-ruleset.xml
+++ b/alfresco-filer-quality/src/main/resources/filer/pmd-ruleset.xml
@@ -81,7 +81,7 @@
+ value="TypeDeclaration/ClassOrInterfaceDeclaration[matches(@Image, 'Test$')]"/>
@@ -89,7 +89,7 @@
$maximumStaticImports]
+ .[count(ImportDeclaration[@Static = 'true' and not(matches(@PackageName, '^org\.mockito\.'))]) > $maximumStaticImports]
]]>
From d530c9ee30e7fe8082c8e9bb7cca8366fee869a7 Mon Sep 17 00:00:00 2001
From: Corentin PIANA
Date: Mon, 23 Sep 2019 10:41:16 +0200
Subject: [PATCH 35/58] Test : fix data race lors de la creation des sites
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Ajout de synchronisation sur le code de création de site.
Plusieurs classes de test exécutées en parallèle essayaient de créer le
même site au même moment créant une erreur de duplication de nom de
noeud.
Change-Id: I9011917517dd11844ab181fe4d75d668bd8d8e14
---
.../framework/DocumentLibraryExtension.java | 34 +++++++++++--------
1 file changed, 20 insertions(+), 14 deletions(-)
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/DocumentLibraryExtension.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/DocumentLibraryExtension.java
index f11b436..61a378f 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/DocumentLibraryExtension.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/DocumentLibraryExtension.java
@@ -24,6 +24,8 @@
public class DocumentLibraryExtension implements BeforeAllCallback, AfterAllCallback {
+ private static final Object SITE_CREATE_LOCK = new Object();
+
@Autowired
private TransactionService transactionService;
@Autowired
@@ -60,21 +62,25 @@ public void beforeAll(final ExtensionContext context) {
}
private void initDocumentLibrary(final DocumentLibrary documentLibrary) {
- AuthenticationUtil.runAs(() -> {
- transactionService.getRetryingTransactionHelper().doInTransaction(() -> {
- String siteName = documentLibrary.getSiteName();
-
- if (!siteService.hasSite(siteName)) {
- siteService.createSite(siteName, siteName, siteName, siteName, SiteVisibility.PUBLIC);
- }
-
- NodeRef library = SiteServiceImpl.getSiteContainer(siteName, SiteService.DOCUMENT_LIBRARY, true, siteService,
- transactionService, taggingService);
- documentLibrary.setNodeRef(library);
+ // Synchronization is required to avoid race condition: multiple test classes executed in parallel trying to create the same
+ // site at the same time, leading to duplicate child name errors.
+ synchronized (SITE_CREATE_LOCK) {
+ AuthenticationUtil.runAs(() -> {
+ transactionService.getRetryingTransactionHelper().doInTransaction(() -> {
+ String siteName = documentLibrary.getSiteName();
+
+ if (!siteService.hasSite(siteName)) {
+ siteService.createSite(siteName, siteName, siteName, siteName, SiteVisibility.PUBLIC);
+ }
+
+ NodeRef library = SiteServiceImpl.getSiteContainer(siteName, SiteService.DOCUMENT_LIBRARY, true, siteService,
+ transactionService, taggingService);
+ documentLibrary.setNodeRef(library);
+ return null;
+ }, false);
return null;
- }, false);
- return null;
- }, AuthenticationUtil.getAdminUserName());
+ }, AuthenticationUtil.getAdminUserName());
+ }
}
@Override
From 4e4ad3aa30161be27e3fbc2c43801bbb621bbf0f Mon Sep 17 00:00:00 2001
From: Corentin PIANA
Date: Tue, 24 Sep 2019 11:50:58 +0200
Subject: [PATCH 36/58] RepositoryNodeBuilder: modify consumer method
For greater flexibility in node building
Change-Id: I6c15f1bad0afd09e0b3d0ddb9a5e9d7c3e9eefec
---
.../filer/core/model/impl/RepositoryNodeBuilder.java | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/impl/RepositoryNodeBuilder.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/impl/RepositoryNodeBuilder.java
index 4db9e36..a78a1d3 100644
--- a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/impl/RepositoryNodeBuilder.java
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/impl/RepositoryNodeBuilder.java
@@ -6,7 +6,7 @@
import java.util.Map;
import java.util.Set;
import java.util.UUID;
-import java.util.function.BiConsumer;
+import java.util.function.Consumer;
import org.alfresco.model.ContentModel;
import org.alfresco.service.cmr.repository.NodeRef;
@@ -79,8 +79,8 @@ public RepositoryNodeBuilder extensions(final Map extensions) {
return this;
}
- public RepositoryNodeBuilder with(final BiConsumer consumer, final T value) {
- consumer.accept(node, value);
+ public RepositoryNodeBuilder with(final Consumer consumer) {
+ consumer.accept(this);
return this;
}
From 9c48c57e14139b270180ce400f4bdd15cdc4aa1e Mon Sep 17 00:00:00 2001
From: Corentin PIANA
Date: Tue, 1 Oct 2019 16:09:00 +0200
Subject: [PATCH 37/58] Supress unused type parameters
Change-Id: I7ff306806dc7a5de1aa47e5855e4f8ef7cf64fc2
---
.../filer/core/test/framework/RepositoryOperations.java | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/RepositoryOperations.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/RepositoryOperations.java
index d52dc74..47a6b5b 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/RepositoryOperations.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/RepositoryOperations.java
@@ -79,11 +79,11 @@ public void afterCommit() {
});
}
- protected void doInTransaction(final Runnable callback) {
+ protected void doInTransaction(final Runnable callback) {
doInTransaction(callback, false);
}
- protected void doInTransaction(final Runnable callback, final boolean readOnly) {
+ protected void doInTransaction(final Runnable callback, final boolean readOnly) {
transactionService.getRetryingTransactionHelper().doInTransaction(() -> {
callback.run();
return null;
From 15d97af55274157a4cbfc498cc6d7e2aa949ac1d Mon Sep 17 00:00:00 2001
From: Corentin PIANA
Date: Wed, 2 Oct 2019 14:29:36 +0200
Subject: [PATCH 38/58] Consumer of node method in RepositoryNodeBuilder
Change-Id: I863fe70bdc0811c761c9d3938dcc1100b9f636b9
---
.../filer/core/model/impl/RepositoryNodeBuilder.java | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/impl/RepositoryNodeBuilder.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/impl/RepositoryNodeBuilder.java
index a78a1d3..661892d 100644
--- a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/impl/RepositoryNodeBuilder.java
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/impl/RepositoryNodeBuilder.java
@@ -6,6 +6,7 @@
import java.util.Map;
import java.util.Set;
import java.util.UUID;
+import java.util.function.BiConsumer;
import java.util.function.Consumer;
import org.alfresco.model.ContentModel;
@@ -84,6 +85,11 @@ public RepositoryNodeBuilder with(final Consumer consumer
return this;
}
+ public RepositoryNodeBuilder with(final BiConsumer consumer, final T value) {
+ consumer.accept(node, value);
+ return this;
+ }
+
public RepositoryNode build() {
return node;
}
From 4bafd9394cd56d55972e15f4fbd701e1fbc7c435 Mon Sep 17 00:00:00 2001
From: Corentin PIANA
Date: Thu, 3 Oct 2019 11:27:09 +0200
Subject: [PATCH 39/58] Update Mockito 3.1.0
MockitoExtension now support correctly JUnit parallel test execution.
Mockito.verifyZeroInteractions is deprecated and replaced by
Mockito.verifyNoInteractions
Change-Id: Ieddb0eccc7efb5e2f77487a84a02ce6fcaf53bfc
---
.../test/service/impl/FilerFolderBuilderTest.java | 8 ++++----
.../service/impl/FilerFolderTypeBuilderTest.java | 14 +++++++-------
alfresco-filer-parent/pom.xml | 2 +-
3 files changed, 12 insertions(+), 12 deletions(-)
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/impl/FilerFolderBuilderTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/impl/FilerFolderBuilderTest.java
index ec549e2..4489ea2 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/impl/FilerFolderBuilderTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/impl/FilerFolderBuilderTest.java
@@ -7,6 +7,8 @@
import org.alfresco.service.cmr.repository.NodeRef;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.api.parallel.Execution;
+import org.junit.jupiter.api.parallel.ExecutionMode;
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.Mockito;
@@ -17,9 +19,7 @@
import com.atolcd.alfresco.filer.core.service.FilerService;
import com.atolcd.alfresco.filer.core.service.impl.FilerFolderBuilder;
-// Could be executed in parallel but Mockito JUnit Jupiter extension does not correctly support parallel test execution yet
-// See https://github.com/mockito/mockito/issues/1630
-//@Execution(ExecutionMode.CONCURRENT)
+@Execution(ExecutionMode.CONCURRENT)
@ExtendWith(MockitoExtension.class)
public class FilerFolderBuilderTest {
@@ -85,7 +85,7 @@ public void updateAndMoveWithContextDisabled() {
filerFolderBuilder.updateAndMove();
- Mockito.verifyZeroInteractions(filerService);
+ Mockito.verifyNoInteractions(filerService);
}
@Test
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/impl/FilerFolderTypeBuilderTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/impl/FilerFolderTypeBuilderTest.java
index ae61956..095ce22 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/impl/FilerFolderTypeBuilderTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/service/impl/FilerFolderTypeBuilderTest.java
@@ -4,12 +4,14 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoInteractions;
import org.alfresco.model.ContentModel;
import org.alfresco.service.namespace.QName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.api.parallel.Execution;
+import org.junit.jupiter.api.parallel.ExecutionMode;
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.Mockito;
@@ -20,9 +22,7 @@
import com.atolcd.alfresco.filer.core.service.FilerService;
import com.atolcd.alfresco.filer.core.service.impl.FilerFolderTypeBuilder;
-// Could be executed in parallel but Mockito JUnit Jupiter extension does not correctly support parallel test execution yet
-// See https://github.com/mockito/mockito/issues/1630
-//@Execution(ExecutionMode.CONCURRENT)
+@Execution(ExecutionMode.CONCURRENT)
@ExtendWith(MockitoExtension.class)
public class FilerFolderTypeBuilderTest {
@@ -51,7 +51,7 @@ public void getOrCreateWithContextDisabled() { //NOPMD - name: not a getter
filerFolderTypeBuilder.getOrCreate();
- verifyZeroInteractions(filerService);
+ verifyNoInteractions(filerService);
}
@Test
@@ -76,7 +76,7 @@ public void getWithContextDisabled() { //NOPMD - name: not a getter
filerFolderTypeBuilder.get();
- verifyZeroInteractions(filerService);
+ verifyNoInteractions(filerService);
}
@Test
@@ -102,7 +102,7 @@ public void updateAndMoveWithContextDisabled() {
filerFolderTypeBuilder.updateAndMove();
- verifyZeroInteractions(filerService);
+ verifyNoInteractions(filerService);
}
@Test
diff --git a/alfresco-filer-parent/pom.xml b/alfresco-filer-parent/pom.xml
index b2cdf9f..8f477e7 100644
--- a/alfresco-filer-parent/pom.xml
+++ b/alfresco-filer-parent/pom.xml
@@ -24,7 +24,7 @@
5.5.21.5.25.1.8.RELEASE
- 3.0.0
+ 3.1.03.12.20.13.13.0.1
From 0de9d39bf94c0e433ba093183db2cfbdcee13cd9 Mon Sep 17 00:00:00 2001
From: Nicolas Barithel
Date: Tue, 8 Oct 2019 18:13:56 +0200
Subject: [PATCH 40/58] Code Quality with SpotBugs
Change-Id: I84e162a0257f1ce6f754f646d3ccb60c9da540c2
---
Jenkinsfile | 7 ++---
alfresco-filer-parent/pom.xml | 27 +++++++++++++++++++
.../main/resources/filer/spotbugs-exclude.xml | 24 +++++++++++++++++
3 files changed, 55 insertions(+), 3 deletions(-)
create mode 100644 alfresco-filer-quality/src/main/resources/filer/spotbugs-exclude.xml
diff --git a/Jenkinsfile b/Jenkinsfile
index 2bf22d2..99036d9 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -45,13 +45,13 @@ pipeline {
MAVEN_OPTIONS = "${env.MAVEN_GLOBAL_OPTIONS} ${params.MAVEN_OPTIONS}"
}
steps {
- sh 'mvn $MAVEN_OPTIONS verify'
+ sh 'mvn $MAVEN_OPTIONS test'
}
}
stage('Package') {
environment {
// Uses maven.test.skip rather than skipTests to also ignore the goals compile:testCompile, resources:testResources
- MAVEN_OPTIONS = "${env.MAVEN_GLOBAL_OPTIONS} -Dmaven.test.skip=true ${params.MAVEN_OPTIONS}"
+ MAVEN_OPTIONS = "${env.MAVEN_GLOBAL_OPTIONS} -Dmaven.test.skip=true -Dspotbugs.skip=true ${params.MAVEN_OPTIONS}"
}
steps {
sh 'mvn $MAVEN_OPTIONS package'
@@ -83,7 +83,8 @@ pipeline {
mavenConsole(),
java(),
checkStyle(pattern: '**/target/checkstyle-result.xml', reportEncoding: 'UTF-8'),
- pmdParser(pattern: '**/target/pmd.xml')
+ pmdParser(pattern: '**/target/pmd.xml'),
+ spotBugs(pattern: '**/target/spotbugsXml.xml')
]
)
}
diff --git a/alfresco-filer-parent/pom.xml b/alfresco-filer-parent/pom.xml
index 8f477e7..8b37d62 100644
--- a/alfresco-filer-parent/pom.xml
+++ b/alfresco-filer-parent/pom.xml
@@ -19,6 +19,7 @@
3.12.06.16.0
+ 3.1.122.22.25.5.2
@@ -188,6 +189,32 @@
+
+ com.github.spotbugs
+ spotbugs-maven-plugin
+ ${alfresco-filer-parent.maven-spotbugs-plugin.version}
+
+
+ check
+ test
+
+ check
+
+
+
+
+ true
+ 20
+ filer/spotbugs-exclude.xml
+
+
+
+ com.atolcd.alfresco.filer
+ alfresco-filer-quality
+ 0.1.0-SNAPSHOT
+
+
+
diff --git a/alfresco-filer-quality/src/main/resources/filer/spotbugs-exclude.xml b/alfresco-filer-quality/src/main/resources/filer/spotbugs-exclude.xml
new file mode 100644
index 0000000..8faf84c
--- /dev/null
+++ b/alfresco-filer-quality/src/main/resources/filer/spotbugs-exclude.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
From 2240bc9790d79ef7218f8fed6f0bc7df96c390dc Mon Sep 17 00:00:00 2001
From: Nicolas Barithel
Date: Wed, 9 Oct 2019 16:54:10 +0200
Subject: [PATCH 41/58] Skip already executed plugins on Jenkins stages
Change-Id: I058cf127c8373c17901dd932975fe4711832f2cf
---
Jenkinsfile | 9 ++++-----
pom.xml | 24 ++++++++++++++++++++++++
2 files changed, 28 insertions(+), 5 deletions(-)
diff --git a/Jenkinsfile b/Jenkinsfile
index 99036d9..f304691 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -27,7 +27,7 @@ pipeline {
stages {
stage('Build') {
environment {
- MAVEN_OPTIONS = "${env.MAVEN_GLOBAL_OPTIONS} -U -DskipTests ${params.MAVEN_OPTIONS}"
+ MAVEN_OPTIONS = "${env.MAVEN_GLOBAL_OPTIONS} -U ${params.MAVEN_OPTIONS}"
}
steps {
// Print disk space
@@ -42,7 +42,7 @@ pipeline {
not { branch 'master' }
}
environment {
- MAVEN_OPTIONS = "${env.MAVEN_GLOBAL_OPTIONS} ${params.MAVEN_OPTIONS}"
+ MAVEN_OPTIONS = "${env.MAVEN_GLOBAL_OPTIONS} -PskipBuildPlugins ${params.MAVEN_OPTIONS}"
}
steps {
sh 'mvn $MAVEN_OPTIONS test'
@@ -50,8 +50,7 @@ pipeline {
}
stage('Package') {
environment {
- // Uses maven.test.skip rather than skipTests to also ignore the goals compile:testCompile, resources:testResources
- MAVEN_OPTIONS = "${env.MAVEN_GLOBAL_OPTIONS} -Dmaven.test.skip=true -Dspotbugs.skip=true ${params.MAVEN_OPTIONS}"
+ MAVEN_OPTIONS = "${env.MAVEN_GLOBAL_OPTIONS} -PskipBuildPlugins,skipTestPlugins ${params.MAVEN_OPTIONS}"
}
steps {
sh 'mvn $MAVEN_OPTIONS package'
@@ -67,7 +66,7 @@ pipeline {
}
}
environment {
- MAVEN_OPTIONS = "${env.MAVEN_GLOBAL_OPTIONS} -Dmaven.test.skip=true ${params.MAVEN_OPTIONS}"
+ MAVEN_OPTIONS = "${env.MAVEN_GLOBAL_OPTIONS} -PskipBuildPlugins,skipTestPlugins ${params.MAVEN_OPTIONS}"
}
steps {
sh 'mvn $MAVEN_OPTIONS deploy'
diff --git a/pom.xml b/pom.xml
index bef8830..f6e5e33 100644
--- a/pom.xml
+++ b/pom.xml
@@ -35,6 +35,7 @@
UTF-8${alfresco-filer.java.version}${alfresco-filer.java.version}
+ false1.4.13.1.0
@@ -133,6 +134,9 @@
com.google.code.maven-replacer-pluginreplacer${alfresco-filer.replacer-plugin.version}
+
+ ${replacer.skip}
+ external.atlassian.jgitflow
@@ -301,5 +305,25 @@
false
+
+ skipBuildPlugins
+
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+
+
+
+ skipTestPlugins
+
+
+ true
+ true
+
+
From 2b1099a3ab5e8f32a063db275cdc37e49f7edf1b Mon Sep 17 00:00:00 2001
From: Corentin PIANA
Date: Wed, 16 Oct 2019 11:53:26 +0200
Subject: [PATCH 42/58] Filer: add conditional get method
Change-Id: I91005921465749660e0ab9f6f83513c9331c0601
---
.../filer/core/service/impl/FilerFolderTypeBuilder.java | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerFolderTypeBuilder.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerFolderTypeBuilder.java
index 5c436a6..f24f6b0 100644
--- a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerFolderTypeBuilder.java
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerFolderTypeBuilder.java
@@ -116,6 +116,10 @@ public FilerFolderBuilder get() {
return new FilerFolderBuilder(filerService, context, child);
}
+ public FilerFolderBuilder get(final boolean createIfAbsent) {
+ return createIfAbsent ? getOrCreate() : get();
+ }
+
public void updateAndMove() {
if (context.isEnabled()) {
RepositoryNode node = context.getNode();
From 7bfbdfa4667d04740831f0f983f69d1d5d161e18 Mon Sep 17 00:00:00 2001
From: Nicolas Barithel
Date: Mon, 14 Oct 2019 15:06:57 +0200
Subject: [PATCH 43/58] Fix plugin execution after compile
- dependency and replacer plugins are required to generate test
resources and run on generate-test-resources phase
Change-Id: I77067dbd903c7fa4bca025a7fd02a8a7eb545073
---
Jenkinsfile | 8 ++++----
pom.xml | 6 +++---
2 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/Jenkinsfile b/Jenkinsfile
index f304691..3c89dce 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -25,7 +25,7 @@ pipeline {
}
stages {
- stage('Build') {
+ stage('Compile') {
environment {
MAVEN_OPTIONS = "${env.MAVEN_GLOBAL_OPTIONS} -U ${params.MAVEN_OPTIONS}"
}
@@ -42,7 +42,7 @@ pipeline {
not { branch 'master' }
}
environment {
- MAVEN_OPTIONS = "${env.MAVEN_GLOBAL_OPTIONS} -PskipBuildPlugins ${params.MAVEN_OPTIONS}"
+ MAVEN_OPTIONS = "${env.MAVEN_GLOBAL_OPTIONS} -PskipCompilePlugins ${params.MAVEN_OPTIONS}"
}
steps {
sh 'mvn $MAVEN_OPTIONS test'
@@ -50,7 +50,7 @@ pipeline {
}
stage('Package') {
environment {
- MAVEN_OPTIONS = "${env.MAVEN_GLOBAL_OPTIONS} -PskipBuildPlugins,skipTestPlugins ${params.MAVEN_OPTIONS}"
+ MAVEN_OPTIONS = "${env.MAVEN_GLOBAL_OPTIONS} -PskipCompilePlugins,skipTestPlugins ${params.MAVEN_OPTIONS}"
}
steps {
sh 'mvn $MAVEN_OPTIONS package'
@@ -66,7 +66,7 @@ pipeline {
}
}
environment {
- MAVEN_OPTIONS = "${env.MAVEN_GLOBAL_OPTIONS} -PskipBuildPlugins,skipTestPlugins ${params.MAVEN_OPTIONS}"
+ MAVEN_OPTIONS = "${env.MAVEN_GLOBAL_OPTIONS} -PskipCompilePlugins,skipTestPlugins ${params.MAVEN_OPTIONS}"
}
steps {
sh 'mvn $MAVEN_OPTIONS deploy'
diff --git a/pom.xml b/pom.xml
index f6e5e33..160e45c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -306,13 +306,11 @@
- skipBuildPlugins
+ skipCompilePluginstruetruetrue
- true
- truetruetrue
@@ -320,6 +318,8 @@
skipTestPlugins
+ true
+ truetruetrue
From 8066d0cea4906a1aafd21ad73d4c8790fe732ad2 Mon Sep 17 00:00:00 2001
From: Nicolas Barithel
Date: Fri, 11 Oct 2019 18:32:11 +0200
Subject: [PATCH 44/58] Code Coverage with JaCoCo
Change-Id: I5015fd351f21cff98c71505851f830a2dfd3d3d2
---
Jenkinsfile | 4 ++++
alfresco-filer-parent/pom.xml | 21 +++++++++++++++++++++
pom.xml | 4 ++++
3 files changed, 29 insertions(+)
diff --git a/Jenkinsfile b/Jenkinsfile
index 3c89dce..60448ef 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -86,6 +86,10 @@ pipeline {
spotBugs(pattern: '**/target/spotbugsXml.xml')
]
)
+ publishCoverage(
+ adapters: [jacocoAdapter('**/target/site/jacoco/jacoco.xml')],
+ sourceFileResolver: sourceFiles('STORE_ALL_BUILD')
+ )
}
success {
archiveArtifacts artifacts: '**/target/*.jar', fingerprint: true
diff --git a/alfresco-filer-parent/pom.xml b/alfresco-filer-parent/pom.xml
index 8b37d62..e5d0212 100644
--- a/alfresco-filer-parent/pom.xml
+++ b/alfresco-filer-parent/pom.xml
@@ -21,6 +21,7 @@
6.16.03.1.122.22.2
+ 0.8.45.5.21.5.2
@@ -215,6 +216,26 @@
+
+ org.jacoco
+ jacoco-maven-plugin
+ ${alfresco-filer-parent.jacoco-maven-plugin.version}
+
+
+ prepare
+
+ prepare-agent
+
+
+
+ report
+ test
+
+ report
+
+
+
+
diff --git a/pom.xml b/pom.xml
index 160e45c..b8d8824 100644
--- a/pom.xml
+++ b/pom.xml
@@ -282,6 +282,7 @@
truetruetrue
+ true
@@ -303,6 +304,7 @@
truefalsefalse
+ true
@@ -313,6 +315,7 @@
truetruetrue
+ false
@@ -323,6 +326,7 @@
truetrue
+ true
From 0cee7cda917dea2c257ca084b009a496908c7f3f Mon Sep 17 00:00:00 2001
From: Nicolas Barithel
Date: Mon, 14 Oct 2019 12:54:26 +0200
Subject: [PATCH 45/58] Update Maven plugins and use Java Release 8
Change-Id: Id593430107d93326101f5a337b6024d5a69ec22e
---
alfresco-filer-parent/pom.xml | 4 ++--
pom.xml | 11 +++++------
2 files changed, 7 insertions(+), 8 deletions(-)
diff --git a/alfresco-filer-parent/pom.xml b/alfresco-filer-parent/pom.xml
index e5d0212..c9373c9 100644
--- a/alfresco-filer-parent/pom.xml
+++ b/alfresco-filer-parent/pom.xml
@@ -19,7 +19,7 @@
3.12.06.16.0
- 3.1.12
+ 3.1.12.22.22.20.8.4
@@ -193,7 +193,7 @@
com.github.spotbugsspotbugs-maven-plugin
- ${alfresco-filer-parent.maven-spotbugs-plugin.version}
+ ${alfresco-filer-parent.spotbugs-maven-plugin.version}check
diff --git a/pom.xml b/pom.xml
index b8d8824..8710bbf 100644
--- a/pom.xml
+++ b/pom.xml
@@ -29,21 +29,20 @@
3.6.0
- 1.8
+ 8UTF-8UTF-8
- ${alfresco-filer.java.version}
- ${alfresco-filer.java.version}
+ ${alfresco-filer.java.version}false1.4.13.1.03.1.0
- 3.8.0
+ 3.8.13.1.2
- 3.0.1
- 3.0.1
+ 3.1.0
+ 3.1.12.62.5.22.8.2
From 4598337dfaf9a13a50edc89ec1b23746f77a5cf9 Mon Sep 17 00:00:00 2001
From: Nicolas Barithel
Date: Mon, 14 Oct 2019 17:23:07 +0200
Subject: [PATCH 46/58] Avoid String concatenation when logging
Change-Id: Id978f6e74732c3c01df4b265886a462d529a1608
---
.../core/service/impl/FilerOperationServiceImpl.java | 10 +++++-----
.../filer/core/service/impl/FilerServiceImpl.java | 6 +++---
.../core/service/impl/FilerUpdateServiceImpl.java | 8 ++++----
.../AutowiredMockAwareMockitoExtensionTest.java | 2 +-
4 files changed, 13 insertions(+), 13 deletions(-)
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerOperationServiceImpl.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerOperationServiceImpl.java
index 093aeb3..c0edd4d 100644
--- a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerOperationServiceImpl.java
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerOperationServiceImpl.java
@@ -60,7 +60,7 @@ public NodeRef getFolder(final NodeRef parent, final String name, final Consumer
try {
filerFolderService.fetchFolder(node, onGet);
} catch (RuntimeException e) { // NOPMD - for logging purposes
- LOGGER.error("Could not get filer folder: " + node, e);
+ LOGGER.error("Could not get filer folder: {}", node, e);
throw e;
}
return node.getNodeRef();
@@ -73,7 +73,7 @@ public NodeRef getOrCreateFolder(final NodeRef parent, final QName type, final S
try {
filerFolderService.fetchOrCreateFolder(node, onGet, onCreate);
} catch (RuntimeException e) { // NOPMD - for logging purposes
- LOGGER.error("Could not get or create filer folder: " + node, e);
+ LOGGER.error("Could not get or create filer folder: {}", node, e);
throw e;
}
return node.getNodeRef();
@@ -88,7 +88,7 @@ public void updateFileable(final RepositoryNode node, final NodeRef destination,
try {
filerUpdateService.updateAndMoveFileable(initialNode, originalNode, node);
} catch (RuntimeException e) { // NOPMD - for logging purposes
- LOGGER.error("Could not update fileable: " + node, e);
+ LOGGER.error("Could not update fileable: {}", node, e);
throw e;
}
// Delete previous parent if it became an empty segment
@@ -100,7 +100,7 @@ public void updateFolder(final RepositoryNode node, final Consumer onGe
try {
filerFolderService.updateFolder(node, onGet, onCreate);
} catch (RuntimeException e) { // NOPMD - for logging purposes
- LOGGER.error("Could not update filer folder: " + node, e);
+ LOGGER.error("Could not update filer folder: {}", node, e);
throw e;
}
}
@@ -114,7 +114,7 @@ public void deleteSegment(final NodeRef nodeRef) {
return null;
});
} catch (RuntimeException e) { // NOPMD - for logging purposes
- LOGGER.error("Could not remove filer segment: " + nodeRef, e);
+ LOGGER.error("Could not remove filer segment: {}", nodeRef, e);
throw e;
}
}
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerServiceImpl.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerServiceImpl.java
index a67407d..039ba99 100644
--- a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerServiceImpl.java
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerServiceImpl.java
@@ -65,7 +65,7 @@ public void executeAction(final FilerEvent event) {
try {
executeActionImpl(event);
} catch (RuntimeException e) { // NOPMD - for logging purposes
- LOGGER.error("Could not execute action: " + event, e);
+ LOGGER.error("Could not execute action: {}", event, e);
throw e;
}
}
@@ -81,7 +81,7 @@ public boolean resolveFileable(final FilerEvent event) {
}
return result;
} catch (RuntimeException e) { // NOPMD - for logging purposes
- LOGGER.error("Could not resolve fileable: " + event, e);
+ LOGGER.error("Could not resolve fileable: {}", event, e);
throw e;
}
}
@@ -175,7 +175,7 @@ private static boolean isUpdateEvent(final FilerEvent event) {
.count();
if (updatedPropertiesCount + updatedAspectCount == 0) {
result = false;
- LOGGER.debug("Ignoring update event without any updated property nor aspect: " + event);
+ LOGGER.debug("Ignoring update event without any updated property nor aspect: {}", event);
}
}
return result;
diff --git a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerUpdateServiceImpl.java b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerUpdateServiceImpl.java
index 2b56275..bb3b1dc 100644
--- a/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerUpdateServiceImpl.java
+++ b/alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/impl/FilerUpdateServiceImpl.java
@@ -51,8 +51,8 @@ private void updateAndMoveFileableImpl(final RepositoryNode initialNode, final R
}
// Update node (ignore node name for now) if filer made changes
RepositoryNodeDifference originalDifference = updateFileable(originalNode, resultingNode);
- if (!originalDifference.isEmpty()) {
- LOGGER.debug("Node updated: " + originalDifference);
+ if (LOGGER.isDebugEnabled() && !originalDifference.isEmpty()) {
+ LOGGER.debug("Node updated: {}", originalDifference);
}
// Lock target segment to prevent its deletion by another transaction
filerFolderService.lockFolder(resultingNode.getParent());
@@ -62,8 +62,8 @@ private void updateAndMoveFileableImpl(final RepositoryNode initialNode, final R
RepositoryNodeDifference initialDifference = new RepositoryNodeDifference(initialNode, resultingNode);
PropertyInheritancePayload inheritance = propertyInheritanceService.getPayload(initialDifference);
propertyInheritanceService.setInheritance(resultingNode.getNodeRef(), inheritance);
- if (!inheritance.isEmpty()) {
- LOGGER.debug("Node inheritance updated: " + inheritance);
+ if (LOGGER.isDebugEnabled() && !inheritance.isEmpty()) {
+ LOGGER.debug("Node inheritance updated: {}", inheritance);
}
}
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/extension/AutowiredMockAwareMockitoExtensionTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/extension/AutowiredMockAwareMockitoExtensionTest.java
index 82d6bcd..5782683 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/extension/AutowiredMockAwareMockitoExtensionTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/extension/AutowiredMockAwareMockitoExtensionTest.java
@@ -165,7 +165,7 @@ private static void logTestFailures(final Events testEvents) {
.map(Optional::get) // Event of type FINISHED cannot have empty payload
.map(TestExecutionResult::getThrowable)
.map(Optional::get) // Throwable is always present on failed test
- .forEach(thrown -> LOGGER.error("Extension test error:", thrown));
+ .forEach(thrown -> LOGGER.error("Extension test error", thrown));
}
/**
From c7cc8a0438550093b15891128b5b364c59cf5ead Mon Sep 17 00:00:00 2001
From: Corentin PIANA
Date: Tue, 29 Oct 2019 13:59:50 +0100
Subject: [PATCH 47/58] Test: beans with permissions enforced by default
Redefine those beans as primary instead of those with no permission
check. This ensure to always have security applied for testing.
Change-Id: I08bd3bbd4fdc06da2cfd1152130938ecec87f3ba
---
.../core/test/content/ContextStartupTest.java | 2 -
.../framework/TestApplicationContext.java | 1 +
.../resources/context/security-context.xml | 160 ++++++++++++++++++
3 files changed, 161 insertions(+), 2 deletions(-)
create mode 100644 alfresco-filer-core/src/test/resources/context/security-context.xml
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/content/ContextStartupTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/content/ContextStartupTest.java
index 92624b3..ef0e1d7 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/content/ContextStartupTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/content/ContextStartupTest.java
@@ -10,7 +10,6 @@
import org.alfresco.service.cmr.repository.StoreRef;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronizationManager;
@@ -21,7 +20,6 @@
public class ContextStartupTest {
@Autowired
- @Qualifier("NodeService")
private NodeService nodeService;
@Test
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/TestApplicationContext.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/TestApplicationContext.java
index b6c19d6..19d1e61 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/TestApplicationContext.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/TestApplicationContext.java
@@ -15,6 +15,7 @@
@ExtendWith(SpringExtension.class)
@ContextConfiguration({
"classpath:alfresco/application-context.xml",
+ "classpath:context/security-context.xml",
"classpath:context/test-service-context.xml",
"classpath:context/test-model-context.xml",
"classpath:context/test-action-context.xml"
diff --git a/alfresco-filer-core/src/test/resources/context/security-context.xml b/alfresco-filer-core/src/test/resources/context/security-context.xml
new file mode 100644
index 0000000..4fc4ecb
--- /dev/null
+++ b/alfresco-filer-core/src/test/resources/context/security-context.xml
@@ -0,0 +1,160 @@
+
+
+
+
+
+
+ org.alfresco.service.cmr.audit.AuditService
+
+
+
+
+
+
+
+
+ org.alfresco.service.cmr.security.AuthenticationService
+
+
+
+
+
+
+
+
+ org.alfresco.service.cmr.security.AuthorityService
+
+
+
+
+
+
+
+
+ org.alfresco.service.cmr.coci.CheckOutCheckInService
+
+
+
+
+
+
+
+
+ org.alfresco.service.cmr.repository.ContentService
+
+
+
+
+
+
+
+
+ org.alfresco.service.cmr.repository.CopyService
+
+
+
+
+
+
+
+
+ org.alfresco.service.cmr.model.FileFolderService
+
+
+
+
+
+
+
+
+ org.alfresco.service.cmr.lock.LockService
+
+
+
+
+
+
+
+
+ org.alfresco.service.cmr.repository.NodeService
+
+
+
+
+
+
+
+
+ org.alfresco.service.cmr.security.OwnableService
+
+
+
+
+
+
+
+
+ org.alfresco.service.cmr.security.PermissionService
+
+
+
+
+
+
+
+
+ org.alfresco.service.cmr.security.PersonService
+
+
+
+
+
+
+
+
+ org.alfresco.service.cmr.search.SearchService
+
+
+
+
+
+
+
+
+ org.alfresco.service.cmr.site.SiteService
+
+
+
+
+
+
From c25e6d8dedfe8a172cff53b4a1257be1d2500bee Mon Sep 17 00:00:00 2001
From: Corentin PIANA
Date: Tue, 29 Oct 2019 15:03:52 +0100
Subject: [PATCH 48/58] Test framework: users and roles
Extensions to define with which user a test must be executed and to give
the user roles over the library.
Change-Id: Ia5df1b3d0a25072d89f2a93a7a30565f54d30c6d
---
.../test/domain/AbstractParallelTest.java | 12 +-
.../test/domain/DeniedFilerActionTest.java | 6 +-
.../DepartmentContentFilerActionTest.java | 32 ++---
.../DepartmentFolderFilerActionTest.java | 10 +-
.../test/domain/PropertyInheritanceTest.java | 20 +--
.../core/test/domain/util/NodePathUtils.java | 4 +-
.../framework/AuthenticationExtension.java | 121 +++++++++++++++++-
.../{DocumentLibrary.java => Library.java} | 4 +-
...ryExtension.java => LibraryExtension.java} | 87 +++++++++----
.../test/framework/LibraryRoleExtension.java | 61 +++++++++
.../test/framework/RepositoryOperations.java | 3 +-
.../framework/TestApplicationContext.java | 2 +-
.../test/framework/TestAuthentication.java | 21 +++
...tDocumentLibrary.java => TestLibrary.java} | 8 +-
.../core/test/framework/TestLibraryRole.java | 21 +++
15 files changed, 333 insertions(+), 79 deletions(-)
rename alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/{DocumentLibrary.java => Library.java} (90%)
rename alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/{DocumentLibraryExtension.java => LibraryExtension.java} (50%)
create mode 100644 alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/LibraryRoleExtension.java
create mode 100644 alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/TestAuthentication.java
rename alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/{TestDocumentLibrary.java => TestLibrary.java} (79%)
create mode 100644 alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/TestLibraryRole.java
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/AbstractParallelTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/AbstractParallelTest.java
index 80d8f11..8f73d95 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/AbstractParallelTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/AbstractParallelTest.java
@@ -1,6 +1,6 @@
package com.atolcd.alfresco.filer.core.test.domain;
-import static com.atolcd.alfresco.filer.core.test.framework.DocumentLibraryExtension.getDocumentLibrary;
+import static com.atolcd.alfresco.filer.core.test.framework.LibraryExtension.getLibrary;
import static com.atolcd.alfresco.filer.core.util.FilerNodeUtils.getPath;
import static java.util.UUID.randomUUID;
import static org.assertj.core.api.Assertions.assertThat;
@@ -32,8 +32,8 @@
import com.atolcd.alfresco.filer.core.model.impl.RepositoryNodeBuilder;
import com.atolcd.alfresco.filer.core.test.domain.content.model.FilerTestConstants;
import com.atolcd.alfresco.filer.core.test.domain.util.NodePathUtils;
-import com.atolcd.alfresco.filer.core.test.framework.DocumentLibrary;
-import com.atolcd.alfresco.filer.core.test.framework.DocumentLibraryExtension;
+import com.atolcd.alfresco.filer.core.test.framework.Library;
+import com.atolcd.alfresco.filer.core.test.framework.LibraryExtension;
import com.atolcd.alfresco.filer.core.test.framework.RepositoryOperations;
/**
@@ -76,9 +76,9 @@ public static void stopExecutor() {
}
protected void execute(final CountDownLatch endingLatch, final Callable task) {
- DocumentLibrary documentLibrary = getDocumentLibrary();
+ Library documentLibrary = getLibrary();
executor.submit(() -> {
- DocumentLibraryExtension.withDocumentLibrary(documentLibrary, () -> {
+ LibraryExtension.withLibrary(documentLibrary, () -> {
try {
AuthenticationUtil.runAsSystem(() -> task.call());
} catch (Exception e) { //NOPMD Catch all exceptions that might occur in thread as they will not be thrown to main thread
@@ -91,7 +91,7 @@ protected void execute(final CountDownLatch endingLatch, final Callable ta
}
protected RepositoryNodeBuilder buildNode(final String departmentName, final LocalDateTime date) {
- return getDocumentLibrary().childNode()
+ return getLibrary().childNode()
.type(FilerTestConstants.Department.DocumentType.NAME)
.property(FilerTestConstants.Department.Aspect.PROP_NAME, departmentName)
.property(FilerTestConstants.ImportedAspect.PROP_DATE, date.atZone(ZoneId.systemDefault()));
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DeniedFilerActionTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DeniedFilerActionTest.java
index e41f3f1..8600c8f 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DeniedFilerActionTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DeniedFilerActionTest.java
@@ -1,6 +1,6 @@
package com.atolcd.alfresco.filer.core.test.domain;
-import static com.atolcd.alfresco.filer.core.test.framework.DocumentLibraryExtension.getDocumentLibrary;
+import static com.atolcd.alfresco.filer.core.test.framework.LibraryExtension.getLibrary;
import static java.util.UUID.randomUUID;
import static org.assertj.core.api.Assertions.assertThat;
@@ -29,7 +29,7 @@ public class DeniedFilerActionTest extends RepositoryOperations {
public void withTypeContent() {
String name = randomUUID().toString();
- RepositoryNode node = getDocumentLibrary().childNode()
+ RepositoryNode node = getLibrary().childNode()
.type(ContentModel.TYPE_CONTENT)
.aspect(filerModelService.getFileableAspect())
.named(name)
@@ -53,7 +53,7 @@ public void withTypeContent() {
public void withTypeFolder() {
String name = randomUUID().toString();
- RepositoryNode node = getDocumentLibrary().childNode()
+ RepositoryNode node = getLibrary().childNode()
.type(ContentModel.TYPE_FOLDER)
.aspect(filerModelService.getFileableAspect())
.named(name)
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DepartmentContentFilerActionTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DepartmentContentFilerActionTest.java
index 51a095c..a0cf60a 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DepartmentContentFilerActionTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DepartmentContentFilerActionTest.java
@@ -1,6 +1,6 @@
package com.atolcd.alfresco.filer.core.test.domain;
-import static com.atolcd.alfresco.filer.core.test.framework.DocumentLibraryExtension.getDocumentLibrary;
+import static com.atolcd.alfresco.filer.core.test.framework.LibraryExtension.getLibrary;
import static com.atolcd.alfresco.filer.core.util.FilerNodeUtils.getPath;
import static java.util.UUID.randomUUID;
import static org.assertj.core.api.Assertions.assertThat;
@@ -26,7 +26,8 @@
import com.atolcd.alfresco.filer.core.test.domain.content.model.FilerTestConstants;
import com.atolcd.alfresco.filer.core.test.domain.util.NodePathUtils;
import com.atolcd.alfresco.filer.core.test.framework.RepositoryOperations;
-import com.atolcd.alfresco.filer.core.test.framework.TestDocumentLibrary;
+import com.atolcd.alfresco.filer.core.test.framework.TestApplicationContext;
+import com.atolcd.alfresco.filer.core.test.framework.TestLibrary;
public class DepartmentContentFilerActionTest extends RepositoryOperations {
@@ -37,14 +38,15 @@ public class DepartmentContentFilerActionTest extends RepositoryOperations {
private NodeService nodeService;
@Nested
- // @TestDocumentLibrary is necessary as Spring does not find the configuration of nested class from the enclosing class
+ // The annotations below are necessary as Spring does not find the configuration of nested class from the enclosing class
// See https://github.com/spring-projects/spring-framework/issues/19930
- @TestDocumentLibrary
+ @TestApplicationContext
+ @TestLibrary
public class DepartmentDocument {
@Test
public void filerAspectHierarchy() {
- RepositoryNode node = getDocumentLibrary().childNode()
+ RepositoryNode node = getLibrary().childNode()
.type(FilerTestConstants.Department.DocumentType.NAME)
.property(FilerTestConstants.Department.Aspect.PROP_NAME, randomUUID())
.build();
@@ -67,7 +69,7 @@ public void filerAspectHierarchy() {
public void typeHierarchy() {
QName type = FilerTestConstants.Department.DocumentType.NAME;
- RepositoryNode node = getDocumentLibrary().childNode()
+ RepositoryNode node = getLibrary().childNode()
.type(FilerTestConstants.Department.DocumentType.NAME)
.property(FilerTestConstants.Department.Aspect.PROP_NAME, randomUUID())
.build();
@@ -89,7 +91,7 @@ public void withImportDate() {
String departmentName = randomUUID().toString();
LocalDateTime date = LocalDateTime.of(2004, 8, 12, 0, 0, 0);
- RepositoryNode node = getDocumentLibrary().childNode()
+ RepositoryNode node = getLibrary().childNode()
.type(FilerTestConstants.Department.DocumentType.NAME)
.property(FilerTestConstants.Department.Aspect.PROP_NAME, departmentName)
.property(FilerTestConstants.ImportedAspect.PROP_DATE, date.atZone(ZoneId.systemDefault()))
@@ -104,7 +106,7 @@ public void withImportDate() {
public void withoutImportDate() {
String departmentName = randomUUID().toString();
- RepositoryNode node = getDocumentLibrary().childNode()
+ RepositoryNode node = getLibrary().childNode()
.type(FilerTestConstants.Department.DocumentType.NAME)
.property(FilerTestConstants.Department.Aspect.PROP_NAME, departmentName)
.build();
@@ -121,7 +123,7 @@ public void withImportDateInWrongSegment() {
LocalDateTime targetDate = LocalDateTime.of(2002, 4, 6, 0, 0, 0);
// Create a node with the wrong date to create corresponding folder
- RepositoryNode wrongSegmentNode = getDocumentLibrary().childNode()
+ RepositoryNode wrongSegmentNode = getLibrary().childNode()
.type(FilerTestConstants.Department.DocumentType.NAME)
.property(FilerTestConstants.Department.Aspect.PROP_NAME, departmentNom)
.property(FilerTestConstants.ImportedAspect.PROP_DATE, wrongDate.atZone(ZoneId.systemDefault()))
@@ -130,7 +132,7 @@ public void withImportDateInWrongSegment() {
createNode(wrongSegmentNode);
// Create node with the target date in the wrong folder
- RepositoryNode node = getDocumentLibrary().childNode()
+ RepositoryNode node = getLibrary().childNode()
.type(FilerTestConstants.Department.DocumentType.NAME)
.parent(wrongSegmentNode.getParent())
.property(FilerTestConstants.Department.Aspect.PROP_NAME, departmentNom)
@@ -149,7 +151,7 @@ public void updateImportDate() {
LocalDateTime targetDate = LocalDateTime.of(2002, 4, 6, 0, 0, 0);
// Create node that will be updated
- RepositoryNode node = getDocumentLibrary().childNode()
+ RepositoryNode node = getLibrary().childNode()
.type(FilerTestConstants.Department.DocumentType.NAME)
.property(FilerTestConstants.Department.Aspect.PROP_NAME, departmentNom)
.property(FilerTestConstants.ImportedAspect.PROP_DATE, sourceDate.atZone(ZoneId.systemDefault()))
@@ -180,7 +182,7 @@ public void deleteDocument() {
LocalDateTime date = LocalDateTime.of(2004, 8, 12, 0, 0, 0);
// Create node that will be deleted
- RepositoryNode node = getDocumentLibrary().childNode()
+ RepositoryNode node = getLibrary().childNode()
.type(FilerTestConstants.Department.DocumentType.NAME)
.property(FilerTestConstants.Department.Aspect.PROP_NAME, departmentNom)
.property(FilerTestConstants.ImportedAspect.PROP_DATE, date.atZone(ZoneId.systemDefault()))
@@ -207,13 +209,13 @@ public void multipleDocumentWithSameImportDate() {
String departmentName = randomUUID().toString();
LocalDateTime date = LocalDateTime.of(2004, 8, 12, 0, 0, 0);
- RepositoryNode firstNode = getDocumentLibrary().childNode()
+ RepositoryNode firstNode = getLibrary().childNode()
.type(FilerTestConstants.Department.DocumentType.NAME)
.property(FilerTestConstants.Department.Aspect.PROP_NAME, departmentName)
.property(FilerTestConstants.ImportedAspect.PROP_DATE, date.atZone(ZoneId.systemDefault()))
.build();
- RepositoryNode secondNode = getDocumentLibrary().childNode()
+ RepositoryNode secondNode = getLibrary().childNode()
.type(FilerTestConstants.Department.DocumentType.NAME)
.property(FilerTestConstants.Department.Aspect.PROP_NAME, departmentName)
.property(FilerTestConstants.ImportedAspect.PROP_DATE, date.atZone(ZoneId.systemDefault()))
@@ -234,7 +236,7 @@ public void specialDocumentTypeWithoutImportDate() {
QName type = FilerTestConstants.SpecialDocumentType.NAME;
String departmentName = randomUUID().toString();
- RepositoryNode node = getDocumentLibrary().childNode()
+ RepositoryNode node = getLibrary().childNode()
.type(type)
.property(FilerTestConstants.Department.Aspect.PROP_NAME, departmentName)
.build();
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DepartmentFolderFilerActionTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DepartmentFolderFilerActionTest.java
index eaca0aa..2aae3d0 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DepartmentFolderFilerActionTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DepartmentFolderFilerActionTest.java
@@ -1,6 +1,6 @@
package com.atolcd.alfresco.filer.core.test.domain;
-import static com.atolcd.alfresco.filer.core.test.framework.DocumentLibraryExtension.getDocumentLibrary;
+import static com.atolcd.alfresco.filer.core.test.framework.LibraryExtension.getLibrary;
import static com.atolcd.alfresco.filer.core.util.FilerNodeUtils.getPath;
import static java.util.UUID.randomUUID;
import static org.assertj.core.api.Assertions.assertThat;
@@ -25,7 +25,7 @@ public class DepartmentFolderFilerActionTest extends RepositoryOperations {
public void withDepartmentName() {
String departmentName = randomUUID().toString();
- RepositoryNode node = getDocumentLibrary().childNode()
+ RepositoryNode node = getLibrary().childNode()
.type(FilerTestConstants.Department.FolderType.NAME)
.property(FilerTestConstants.Department.Aspect.PROP_NAME, departmentName)
.build();
@@ -33,13 +33,13 @@ public void withDepartmentName() {
createNode(node);
assertThat(node.getProperty(FilerTestConstants.Department.Aspect.PROP_NAME, String.class)).isEqualTo(departmentName);
- assertThat(getPath(node)).isEqualTo(getDocumentLibrary().getPath());
+ assertThat(getPath(node)).isEqualTo(getLibrary().getPath());
}
@Test
public void updateDepartmentName() {
// Create folder node
- RepositoryNode departmentFolderNode = getDocumentLibrary().childNode()
+ RepositoryNode departmentFolderNode = getLibrary().childNode()
.type(FilerTestConstants.Department.FolderType.NAME)
.property(FilerTestConstants.Department.Aspect.PROP_NAME, randomUUID())
.build();
@@ -48,7 +48,7 @@ public void updateDepartmentName() {
// Create document node in folder
LocalDateTime date = LocalDateTime.of(2004, 8, 12, 0, 0, 0);
- RepositoryNode documentNode = getDocumentLibrary().childNode()
+ RepositoryNode documentNode = getLibrary().childNode()
.type(FilerTestConstants.Department.DocumentType.NAME)
.property(FilerTestConstants.Department.Aspect.PROP_NAME,
departmentFolderNode.getProperty(FilerTestConstants.Department.Aspect.PROP_NAME, String.class))
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/PropertyInheritanceTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/PropertyInheritanceTest.java
index 816c5ab..e6b5e2f 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/PropertyInheritanceTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/PropertyInheritanceTest.java
@@ -1,6 +1,6 @@
package com.atolcd.alfresco.filer.core.test.domain;
-import static com.atolcd.alfresco.filer.core.test.framework.DocumentLibraryExtension.getDocumentLibrary;
+import static com.atolcd.alfresco.filer.core.test.framework.LibraryExtension.getLibrary;
import static java.util.UUID.randomUUID;
import static org.assertj.core.api.Assertions.assertThat;
@@ -26,7 +26,7 @@ public class PropertyInheritanceTest extends RepositoryOperations {
@Test
public void createNodeInDepartmentFolder() {
- RepositoryNode folderNode = getDocumentLibrary().childNode()
+ RepositoryNode folderNode = getLibrary().childNode()
.type(FilerTestConstants.Department.FolderType.NAME)
.property(FilerTestConstants.Department.Aspect.PROP_NAME, randomUUID())
.property(FilerTestConstants.Department.Aspect.PROP_ID, randomUUID())
@@ -35,7 +35,7 @@ public void createNodeInDepartmentFolder() {
createNode(folderNode);
// Create node in department folder and check if properties have been inheritated from department folder
- RepositoryNode testNode = getDocumentLibrary().childNode()
+ RepositoryNode testNode = getLibrary().childNode()
.type(FilerTestConstants.Department.DocumentType.NAME)
.parent(folderNode.getNodeRef())
.build();
@@ -79,7 +79,7 @@ public void createNodeInDepartmentFolder() {
@Test
public void changePropertyOfDepartmentFolder() {
- RepositoryNode testNode = getDocumentLibrary().childNode()
+ RepositoryNode testNode = getLibrary().childNode()
.type(FilerTestConstants.Department.DocumentType.NAME)
.property(FilerTestConstants.Department.Aspect.PROP_NAME, randomUUID())
.property(FilerTestConstants.Department.Aspect.PROP_ID, randomUUID())
@@ -108,7 +108,7 @@ public void changePropertyOfDepartmentFolder() {
@Test
public void createNodeInDepartmentFolderWithWrongProperty() {
- RepositoryNode folderNode = getDocumentLibrary().childNode()
+ RepositoryNode folderNode = getLibrary().childNode()
.type(FilerTestConstants.Department.FolderType.NAME)
.property(FilerTestConstants.Department.Aspect.PROP_NAME, randomUUID())
.property(FilerTestConstants.Department.Aspect.PROP_ID, randomUUID())
@@ -117,7 +117,7 @@ public void createNodeInDepartmentFolderWithWrongProperty() {
createNode(folderNode);
// Create node in department folder with wrong property and check if correct property is inherited from folder
- RepositoryNode testNode = getDocumentLibrary().childNode()
+ RepositoryNode testNode = getLibrary().childNode()
.type(FilerTestConstants.Department.DocumentType.NAME)
.parent(folderNode.getNodeRef())
.property(FilerTestConstants.Department.Aspect.PROP_ID, randomUUID())
@@ -163,7 +163,7 @@ public void createNodeInDepartmentFolderWithWrongProperty() {
@Test
public void createNodeInFolderHierarchyWithMultipleLevelInheritance() {
- RepositoryNode departmentFolderNode = getDocumentLibrary().childNode()
+ RepositoryNode departmentFolderNode = getLibrary().childNode()
.type(FilerTestConstants.Department.FolderType.NAME)
.property(FilerTestConstants.Department.Aspect.PROP_NAME, randomUUID())
.property(FilerTestConstants.Department.Aspect.PROP_ID, randomUUID())
@@ -172,7 +172,7 @@ public void createNodeInFolderHierarchyWithMultipleLevelInheritance() {
createNode(departmentFolderNode);
// Create management folder in department folder and check if department properties are inherited
- RepositoryNode managementFolderNode = getDocumentLibrary().childNode()
+ RepositoryNode managementFolderNode = getLibrary().childNode()
.type(FilerTestConstants.Department.Management.DocumentType.NAME)
.parent(departmentFolderNode.getNodeRef())
.property(FilerTestConstants.Department.Management.Aspect.PROP_ID, randomUUID())
@@ -186,7 +186,7 @@ public void createNodeInFolderHierarchyWithMultipleLevelInheritance() {
.isEqualTo(departmentFolderNode.getProperty(FilerTestConstants.Department.Aspect.PROP_ID, String.class));
// Create node in management folder and check if department and management properties are inherited
- RepositoryNode testNode = getDocumentLibrary().childNode()
+ RepositoryNode testNode = getLibrary().childNode()
.type(FilerTestConstants.Department.Management.DocumentType.NAME)
.parent(managementFolderNode.getParent())
.build();
@@ -203,6 +203,6 @@ public void createNodeInFolderHierarchyWithMultipleLevelInheritance() {
private NodeRef getDepartmentFolder(final RepositoryNode node) {
String departmentName = node.getProperty(FilerTestConstants.Department.Aspect.PROP_NAME, String.class);
- return nodeService.getChildByName(getDocumentLibrary().getNodeRef(), ContentModel.ASSOC_CONTAINS, departmentName);
+ return nodeService.getChildByName(getLibrary().getNodeRef(), ContentModel.ASSOC_CONTAINS, departmentName);
}
}
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/util/NodePathUtils.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/util/NodePathUtils.java
index d57523a..40e8d49 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/util/NodePathUtils.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/util/NodePathUtils.java
@@ -4,12 +4,12 @@
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
-import com.atolcd.alfresco.filer.core.test.framework.DocumentLibraryExtension;
+import com.atolcd.alfresco.filer.core.test.framework.LibraryExtension;
public final class NodePathUtils {
public static Path nodePath(final String departmentName, final LocalDateTime date) {
- return DocumentLibraryExtension.getDocumentLibrary().childPath(
+ return LibraryExtension.getLibrary().childPath(
departmentName,
Integer.toString(date.getYear()),
date.format(DateTimeFormatter.ofPattern("MM")));
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/AuthenticationExtension.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/AuthenticationExtension.java
index f7abadd..4e9bfaa 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/AuthenticationExtension.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/AuthenticationExtension.java
@@ -1,19 +1,134 @@
package com.atolcd.alfresco.filer.core.test.framework;
+import static java.util.UUID.randomUUID;
+
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import org.alfresco.model.ContentModel;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
+import org.alfresco.service.cmr.security.PersonService;
+import org.alfresco.service.namespace.QName;
+import org.alfresco.service.transaction.TransactionService;
+import org.junit.jupiter.api.extension.AfterAllCallback;
import org.junit.jupiter.api.extension.AfterEachCallback;
+import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
+import org.junit.jupiter.api.extension.ExtensionContext.Store;
+import org.junit.platform.commons.util.AnnotationUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.springframework.util.Assert;
+
+public class AuthenticationExtension implements BeforeAllCallback, BeforeEachCallback, AfterEachCallback, AfterAllCallback {
+
+ private static final Namespace NAMESPACE = Namespace.create(AuthenticationExtension.class);
-public class AuthenticationExtension implements BeforeEachCallback, AfterEachCallback {
+ @Autowired
+ private TransactionService transactionService;
+ @Autowired
+ private PersonService personService;
+
+ private static ThreadLocal userHolder = new ThreadLocal<>();
+
+ @Override
+ public void beforeAll(final ExtensionContext context) {
+ Class> clazz = context.getRequiredTestClass();
+ Optional annotation = AnnotationUtils.findAnnotation(clazz, TestAuthentication.class);
+ if (!annotation.isPresent()) {
+ throw new IllegalStateException("Could not find TestAuthentication annotation on class: " + clazz);
+ }
+
+ String userName = annotation.map(TestAuthentication::value).filter(value -> !value.isEmpty())
+ .orElseGet(() -> "user-" + randomUUID().toString());
+
+ ApplicationContext applicationContext = SpringExtension.getApplicationContext(context);
+ applicationContext.getAutowireCapableBeanFactory().autowireBean(this);
+
+ List specialNames = Arrays.asList(AuthenticationUtil.getAdminUserName(), AuthenticationUtil.getSystemUserName(),
+ AuthenticationUtil.getGuestUserName());
+ if (!specialNames.contains(userName)) {
+ createPerson(userName);
+ }
+ setUser(context, userName);
+ }
@Override
public void beforeEach(final ExtensionContext context) {
- AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName());
+ String userName = getUser(context);
+ userHolder.set(userName);
+ AuthenticationUtil.setFullyAuthenticatedUser(userName);
}
@Override
- public void afterEach(final ExtensionContext context) {
+ public void afterEach(final ExtensionContext context) {
AuthenticationUtil.clearCurrentSecurityContext();
+ userHolder.remove();
+ }
+
+ @Override
+ public void afterAll(final ExtensionContext context) {
+ getStore(context).remove(context.getRequiredTestClass());
+ }
+
+ public static String getUser(final ExtensionContext context) {
+ Assert.notNull(context, "ExtensionContext must not be null");
+ Class> testClass = context.getRequiredTestClass();
+ return getStore(context).get(testClass, String.class);
+ }
+
+ private static void setUser(final ExtensionContext context, final String user) {
+ Class> testClass = context.getRequiredTestClass();
+ getStore(context).put(testClass, user);
+ }
+
+ private static Store getStore(final ExtensionContext context) {
+ return context.getStore(NAMESPACE);
+ }
+
+ private void createPerson(final String userName) {
+ // Synchronization is required to avoid race condition: multiple test classes executed in parallel trying to create the same
+ // person at the same time, leading to duplicate child name errors.
+ synchronized (NAMESPACE) {
+ AuthenticationUtil.runAsSystem(() -> {
+ transactionService.getRetryingTransactionHelper().doInTransaction(() -> {
+ if (personService.getPersonOrNull(userName) == null) {
+ Map properties = new HashMap<>();
+ properties.put(ContentModel.PROP_USERNAME, userName);
+ personService.createPerson(properties);
+ }
+ return null;
+ }, false);
+ return null;
+ });
+ }
+ }
+
+ public static String getUser() {
+ return userHolder.get();
+ }
+
+ public static void withUser(final String user, final Runnable task) {
+ String original = getUser();
+ userHolder.set(user);
+ try {
+ AuthenticationUtil.runAs(() -> {
+ task.run();
+ return null;
+ }, user);
+ } finally {
+ if (original == null) {
+ userHolder.remove();
+ } else {
+ userHolder.set(original);
+ }
+ }
}
}
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/DocumentLibrary.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/Library.java
similarity index 90%
rename from alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/DocumentLibrary.java
rename to alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/Library.java
index 66f7551..012b8b5 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/DocumentLibrary.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/Library.java
@@ -11,14 +11,14 @@
import com.atolcd.alfresco.filer.core.model.RepositoryNode;
import com.atolcd.alfresco.filer.core.model.impl.RepositoryNodeBuilder;
-public class DocumentLibrary {
+public class Library {
private final String siteName;
private NodeRef nodeRef;
private Path path;
private final Function pathProvider;
- public DocumentLibrary(final String siteName, final Function pathProvider) {
+ public Library(final String siteName, final Function pathProvider) {
this.siteName = siteName;
this.pathProvider = pathProvider;
}
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/DocumentLibraryExtension.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/LibraryExtension.java
similarity index 50%
rename from alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/DocumentLibraryExtension.java
rename to alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/LibraryExtension.java
index 61a378f..bee6cfc 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/DocumentLibraryExtension.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/LibraryExtension.java
@@ -15,16 +15,21 @@
import org.alfresco.service.cmr.tagging.TaggingService;
import org.alfresco.service.transaction.TransactionService;
import org.junit.jupiter.api.extension.AfterAllCallback;
+import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeAllCallback;
+import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
+import org.junit.jupiter.api.extension.ExtensionContext.Store;
import org.junit.platform.commons.util.AnnotationUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.springframework.util.Assert;
-public class DocumentLibraryExtension implements BeforeAllCallback, AfterAllCallback {
+public class LibraryExtension implements BeforeAllCallback, BeforeEachCallback, AfterEachCallback, AfterAllCallback {
- private static final Object SITE_CREATE_LOCK = new Object();
+ private static final Namespace NAMESPACE = Namespace.create(LibraryExtension.class);
@Autowired
private TransactionService transactionService;
@@ -37,45 +42,76 @@ public class DocumentLibraryExtension implements BeforeAllCallback, AfterAllCall
@Autowired
private PermissionService permissionService;
- private static ThreadLocal documentLibraryHolder = new ThreadLocal<>();
+ private static ThreadLocal libraryHolder = new ThreadLocal<>();
@Override
public void beforeAll(final ExtensionContext context) {
Class> clazz = context.getRequiredTestClass();
- Optional annotation = AnnotationUtils.findAnnotation(clazz, TestDocumentLibrary.class);
+ Optional annotation = AnnotationUtils.findAnnotation(clazz, TestLibrary.class);
if (!annotation.isPresent()) {
- throw new IllegalStateException("Could not find TestDocumentLibrary annotation on class: " + clazz);
+ throw new IllegalStateException("Could not find TestLibrary annotation on class: " + clazz);
}
- String siteName = annotation.map(TestDocumentLibrary::value).filter(value -> !value.isEmpty())
- .orElseGet(() -> randomUUID().toString());
+ String siteName = annotation.map(TestLibrary::value).filter(value -> !value.isEmpty())
+ .orElseGet(() -> "library-" + randomUUID().toString());
ApplicationContext applicationContext = SpringExtension.getApplicationContext(context);
applicationContext.getAutowireCapableBeanFactory().autowireBean(this);
- DocumentLibrary documentLibrary = new DocumentLibrary(siteName, nodeRef -> {
+ Library library = new Library(siteName, nodeRef -> {
return Paths
.get(nodeService.getPath(nodeRef).toDisplayPath(nodeService, permissionService), SiteService.DOCUMENT_LIBRARY);
});
- initDocumentLibrary(documentLibrary);
- documentLibraryHolder.set(documentLibrary);
+ initLibrary(library);
+ setLibrary(context, library);
}
- private void initDocumentLibrary(final DocumentLibrary documentLibrary) {
+ @Override
+ public void beforeEach(final ExtensionContext context) {
+ Library library = getLibrary(context);
+ libraryHolder.set(library);
+ }
+
+ @Override
+ public void afterEach(final ExtensionContext context) {
+ libraryHolder.remove();
+ }
+
+ @Override
+ public void afterAll(final ExtensionContext context) {
+ getStore(context).remove(context.getRequiredTestClass());
+ }
+
+ public static Library getLibrary(final ExtensionContext context) {
+ Assert.notNull(context, "ExtensionContext must not be null");
+ Class> testClass = context.getRequiredTestClass();
+ return getStore(context).get(testClass, Library.class);
+ }
+
+ private static void setLibrary(final ExtensionContext context, final Library library) {
+ Class> testClass = context.getRequiredTestClass();
+ getStore(context).put(testClass, library);
+ }
+
+ private static Store getStore(final ExtensionContext context) {
+ return context.getStore(NAMESPACE);
+ }
+
+ private void initLibrary(final Library library) {
// Synchronization is required to avoid race condition: multiple test classes executed in parallel trying to create the same
// site at the same time, leading to duplicate child name errors.
- synchronized (SITE_CREATE_LOCK) {
+ synchronized (NAMESPACE) {
AuthenticationUtil.runAs(() -> {
transactionService.getRetryingTransactionHelper().doInTransaction(() -> {
- String siteName = documentLibrary.getSiteName();
+ String siteName = library.getSiteName();
if (!siteService.hasSite(siteName)) {
- siteService.createSite(siteName, siteName, siteName, siteName, SiteVisibility.PUBLIC);
+ siteService.createSite(siteName, siteName, siteName, siteName, SiteVisibility.PRIVATE);
}
- NodeRef library = SiteServiceImpl.getSiteContainer(siteName, SiteService.DOCUMENT_LIBRARY, true, siteService,
+ NodeRef documentLibrary = SiteServiceImpl.getSiteContainer(siteName, SiteService.DOCUMENT_LIBRARY, true, siteService,
transactionService, taggingService);
- documentLibrary.setNodeRef(library);
+ library.setNodeRef(documentLibrary);
return null;
}, false);
return null;
@@ -83,25 +119,20 @@ private void initDocumentLibrary(final DocumentLibrary documentLibrary) {
}
}
- @Override
- public void afterAll(final ExtensionContext context) {
- documentLibraryHolder.remove();
- }
-
- public static DocumentLibrary getDocumentLibrary() {
- return documentLibraryHolder.get();
+ public static Library getLibrary() {
+ return libraryHolder.get();
}
- public static void withDocumentLibrary(final DocumentLibrary documentLibrary, final Runnable task) {
- DocumentLibrary original = getDocumentLibrary();
- documentLibraryHolder.set(documentLibrary);
+ public static void withLibrary(final Library library, final Runnable task) {
+ Library original = getLibrary();
+ libraryHolder.set(library);
try {
task.run();
} finally {
if (original == null) {
- documentLibraryHolder.remove();
+ libraryHolder.remove();
} else {
- documentLibraryHolder.set(original);
+ libraryHolder.set(original);
}
}
}
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/LibraryRoleExtension.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/LibraryRoleExtension.java
new file mode 100644
index 0000000..b604702
--- /dev/null
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/LibraryRoleExtension.java
@@ -0,0 +1,61 @@
+package com.atolcd.alfresco.filer.core.test.framework;
+
+import java.util.Optional;
+
+import org.alfresco.repo.security.authentication.AuthenticationUtil;
+import org.alfresco.service.cmr.site.SiteService;
+import org.alfresco.service.transaction.TransactionService;
+import org.junit.jupiter.api.extension.AfterAllCallback;
+import org.junit.jupiter.api.extension.BeforeAllCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.platform.commons.util.AnnotationUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+public class LibraryRoleExtension implements BeforeAllCallback, AfterAllCallback {
+
+ @Autowired
+ private TransactionService transactionService;
+ @Autowired
+ private SiteService siteService;
+
+ @Override
+ public void beforeAll(final ExtensionContext context) {
+ Class> clazz = context.getRequiredTestClass();
+ Optional annotation = AnnotationUtils.findAnnotation(clazz, TestLibraryRole.class);
+ if (!annotation.isPresent()) {
+ throw new IllegalStateException("Could not find TestLibraryRole annotation on class: " + clazz);
+ }
+
+ String role = annotation.map(TestLibraryRole::value).filter(value -> !value.isEmpty())
+ .orElseThrow(() -> new IllegalStateException("TestLibraryRole annotation should specify a role name on class: " + clazz));
+
+ ApplicationContext applicationContext = SpringExtension.getApplicationContext(context);
+ applicationContext.getAutowireCapableBeanFactory().autowireBean(this);
+
+ AuthenticationUtil.runAsSystem(() -> {
+ transactionService.getRetryingTransactionHelper().doInTransaction(() -> {
+ siteService.setMembership(
+ LibraryExtension.getLibrary(context).getSiteName(),
+ AuthenticationExtension.getUser(context),
+ role);
+ return null;
+ });
+ return null;
+ });
+ }
+
+ @Override
+ public void afterAll(final ExtensionContext context) {
+ AuthenticationUtil.runAsSystem(() -> {
+ transactionService.getRetryingTransactionHelper().doInTransaction(() -> {
+ siteService.removeMembership(
+ LibraryExtension.getLibrary(context).getSiteName(),
+ AuthenticationExtension.getUser(context));
+ return null;
+ });
+ return null;
+ });
+ }
+}
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/RepositoryOperations.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/RepositoryOperations.java
index 47a6b5b..73323ab 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/RepositoryOperations.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/RepositoryOperations.java
@@ -17,7 +17,8 @@
import com.atolcd.alfresco.filer.core.model.RepositoryNode;
import com.atolcd.alfresco.filer.core.util.FilerNodeUtils;
-@TestDocumentLibrary
+@TestApplicationContext
+@TestLibrary
public class RepositoryOperations {
@Autowired
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/TestApplicationContext.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/TestApplicationContext.java
index 19d1e61..5b99c9d 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/TestApplicationContext.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/TestApplicationContext.java
@@ -20,5 +20,5 @@
"classpath:context/test-model-context.xml",
"classpath:context/test-action-context.xml"
})
-@ExtendWith(AuthenticationExtension.class)
+@TestAuthentication("admin")
public @interface TestApplicationContext {}
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/TestAuthentication.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/TestAuthentication.java
new file mode 100644
index 0000000..b58a219
--- /dev/null
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/TestAuthentication.java
@@ -0,0 +1,21 @@
+package com.atolcd.alfresco.filer.core.test.framework;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.junit.jupiter.api.extension.ExtendWith;
+
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+@ExtendWith(AuthenticationExtension.class)
+public @interface TestAuthentication {
+
+ /**
+ * User name
+ */
+ String value() default "";
+}
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/TestDocumentLibrary.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/TestLibrary.java
similarity index 79%
rename from alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/TestDocumentLibrary.java
rename to alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/TestLibrary.java
index c633a0d..1a58a93 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/TestDocumentLibrary.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/TestLibrary.java
@@ -11,9 +11,11 @@
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
-@TestApplicationContext
-@ExtendWith(DocumentLibraryExtension.class)
-public @interface TestDocumentLibrary {
+@ExtendWith(LibraryExtension.class)
+public @interface TestLibrary {
+ /**
+ * Site name
+ */
String value() default "";
}
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/TestLibraryRole.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/TestLibraryRole.java
new file mode 100644
index 0000000..4938341
--- /dev/null
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/TestLibraryRole.java
@@ -0,0 +1,21 @@
+package com.atolcd.alfresco.filer.core.test.framework;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.junit.jupiter.api.extension.ExtendWith;
+
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+@ExtendWith(LibraryRoleExtension.class)
+public @interface TestLibraryRole {
+
+ /**
+ * Role name
+ */
+ String value() default "";
+}
From a9c6028b59fd40cd0cdba52239aafa893cd91147 Mon Sep 17 00:00:00 2001
From: Corentin PIANA
Date: Tue, 29 Oct 2019 15:06:22 +0100
Subject: [PATCH 49/58] Execute tests with standard user
With minimum permission instead of running them as administrator.
Change-Id: I5f19f3d62689fe5c7969d2030125c8da7d5ba8be
---
.../alfresco/filer/core/test/content/ContextStartupTest.java | 3 +++
.../core/test/domain/DepartmentContentFilerActionTest.java | 5 +++++
.../filer/core/test/framework/RepositoryOperations.java | 3 +++
.../filer/core/test/framework/TestApplicationContext.java | 1 -
4 files changed, 11 insertions(+), 1 deletion(-)
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/content/ContextStartupTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/content/ContextStartupTest.java
index ef0e1d7..e0db29c 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/content/ContextStartupTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/content/ContextStartupTest.java
@@ -5,6 +5,7 @@
import java.util.UUID;
import org.alfresco.model.ContentModel;
+import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.StoreRef;
@@ -14,8 +15,10 @@
import org.springframework.transaction.support.TransactionSynchronizationManager;
import com.atolcd.alfresco.filer.core.test.framework.TestApplicationContext;
+import com.atolcd.alfresco.filer.core.test.framework.TestAuthentication;
@TestApplicationContext
+@TestAuthentication(AuthenticationUtil.SYSTEM_USER_NAME)
@Transactional
public class ContextStartupTest {
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DepartmentContentFilerActionTest.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DepartmentContentFilerActionTest.java
index a0cf60a..bb85bfc 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DepartmentContentFilerActionTest.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/DepartmentContentFilerActionTest.java
@@ -14,6 +14,7 @@
import java.util.Map;
import org.alfresco.model.ContentModel;
+import org.alfresco.repo.site.SiteModel;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.namespace.QName;
@@ -27,7 +28,9 @@
import com.atolcd.alfresco.filer.core.test.domain.util.NodePathUtils;
import com.atolcd.alfresco.filer.core.test.framework.RepositoryOperations;
import com.atolcd.alfresco.filer.core.test.framework.TestApplicationContext;
+import com.atolcd.alfresco.filer.core.test.framework.TestAuthentication;
import com.atolcd.alfresco.filer.core.test.framework.TestLibrary;
+import com.atolcd.alfresco.filer.core.test.framework.TestLibraryRole;
public class DepartmentContentFilerActionTest extends RepositoryOperations {
@@ -42,6 +45,8 @@ public class DepartmentContentFilerActionTest extends RepositoryOperations {
// See https://github.com/spring-projects/spring-framework/issues/19930
@TestApplicationContext
@TestLibrary
+ @TestAuthentication
+ @TestLibraryRole(SiteModel.SITE_CONTRIBUTOR)
public class DepartmentDocument {
@Test
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/RepositoryOperations.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/RepositoryOperations.java
index 73323ab..de7c2d9 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/RepositoryOperations.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/RepositoryOperations.java
@@ -4,6 +4,7 @@
import java.util.Map;
import org.alfresco.model.ContentModel;
+import org.alfresco.repo.site.SiteModel;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
@@ -19,6 +20,8 @@
@TestApplicationContext
@TestLibrary
+@TestAuthentication
+@TestLibraryRole(SiteModel.SITE_CONTRIBUTOR)
public class RepositoryOperations {
@Autowired
diff --git a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/TestApplicationContext.java b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/TestApplicationContext.java
index 5b99c9d..2a8039b 100644
--- a/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/TestApplicationContext.java
+++ b/alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/framework/TestApplicationContext.java
@@ -20,5 +20,4 @@
"classpath:context/test-model-context.xml",
"classpath:context/test-action-context.xml"
})
-@TestAuthentication("admin")
public @interface TestApplicationContext {}
From e112204f7d504222a2e422deb32128516ad1d5d7 Mon Sep 17 00:00:00 2001
From: Nicolas Barithel
Date: Wed, 30 Oct 2019 16:41:18 +0100
Subject: [PATCH 50/58] Describe the module usage and concepts
Change-Id: I33a2021a23704bc4d81904cbbd782baaad9da9cc
---
README.md | 134 ++++++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 131 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index 33867c0..d16d5a8 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,140 @@
# AtolCD Alfresco Filer
-This is an Alfresco Content Services module to perform filer operations.
+This is an Alfresco Content Services module to perform filer operations. It adds the ability to define rules to move
+an incoming or an updated document into the desired folder structure based on its characteristics (mainly its type, aspects and metadata).
+It offers a fluent API to define each level of the classification and whether they should be created on the fly.
+It also allows to inherit specific metadata that are defined at any level of the folder hierarchy to ensure consistency and availability.
# Few things to notice
- * Runs with Alfresco Content Services 6.2 and JDK 11
+ * Runs with Alfresco Content Services 6.2 and JDK 11 (compatible with ACS 5.2 and JDK 8)
* Standard JAR packaging and layout
* AMP as an assembly
* Tested with JUnit 5, Mockito 3 and PostgreSQL 10
-# TODO
+# Building and testing
+
+The project can be built and tested by running the following Maven command:
+~~~
+mvn -Pdelivery clean package
+~~~
+
+# Artifacts
+
+The artifacts can be added to the dependency of your project in its pom.xml:
+~~~
+
+ com.atolcd.alfresco.filer
+ alfresco-filer-core
+ 0.1.0
+
+~~~
+
+# Using the module
+
+The core of the module is based on Alfresco Content Services policies to detect nodes to be filed and an engine to execute filer actions.
+
+## Filer model
+
+The filer defines 3 concepts:
+* a **fileable**: a node (document or folder) that can be automatically filed,
+* a **filer subscriber**: a container in which nodes are automatically filed,
+* a **filer segment**: a folder that is part of a hierarchy in which a node is filed, it can be deleted automatically if empty
+
+The filer engine uses policies to detect changes in the repository and trigger its own rule mechanism to adapt the node's classification:
+* *onCreateChildAssociation* on a filer subscriber, to label the incoming node as fileable,
+* *onAddAspect* on a fileable node, to trigger the initial classification,
+* *onUpdatePreoperties* and *onMoveNode* on a fileable node, to check for updates that could change the node's classification,
+* *onDeleteNode* on a fileable node, to remove a classification left empty.
+
+## Filer action
+
+A filer action is evaluated by the filer engine to determine whether it applies to the node to be filed and then it performs the selected action.
+
+First, it is required to provide the conditions upon which a filer action will be executed.
+The matching is actually performed in two passes to allow to quickly bypass classification if the node does not supports a filer action based on some general requirements such as the containing site, its aspects or type.
+The second check allows for a more thorough inspection, including for example the properties of the node.
+Finally, it is possible to define the action itself. This is indeed the actual classification, which would trigger the navigation or the creation of the folder structure.
+
+Creating a filer action is done by implementing [`FilerAction`] or directly inheriting [`AbstractFilerAction`].
+
+Let's take a simple example where a document that would contain a particular description with a department data (e.g. department: treasury;) created in 2019 would be filed into the "treasury/2019" path inside the site's document library.
+Here is the corresponding implementation:
+```java
+public class DepartmentFilerAction extends AbstractFilerAction {
+
+ @Override
+ public boolean supportsActionResolution(final FilerEvent event) {
+ return event.getNode().getAspects().contains(ContentModel.ASPECT_TITLED)
+ && event.getNode().getType().equals(ContentModel.TYPE_CONTENT);
+ }
+
+ @Override
+ public boolean supportsActionExecution(final RepositoryNode node) {
+ return node.getProperty(ContentModel.PROP_DESCRIPTION, String.class).matches("department:.+;");
+ }
+
+ @Override
+ protected void execute(final FilerBuilder builder) {
+ builder.root(FilerNodeUtils::getSiteNodeRef)
+ .folder()
+ .named().with(SiteService.DOCUMENT_LIBRARY).get()
+ .folder().asSegment()
+ .named().with(node -> {
+ Pattern regex = Pattern.compile("department:\\s*(.+);");
+ Matcher matcher = regex.matcher(node.getProperty(ContentModel.PROP_DESCRIPTION, String.class));
+ matcher.find();
+ return matcher.group(1);
+ }).getOrCreate()
+ .folder().asSegment()
+ .named().withPropertyDate(ContentModel.PROP_CREATED, "yyyy").getOrCreate()
+ .updateAndMove();
+ }
+}
+```
+You can also look at [example actions] used in the tests and their corresponding [folder structure creation] put together in a dedicated service.
+
+It is possible to create as many actions as needed. They are automatically registered by the [`FilerRegistry`] if they inherits from [`AbstractFilerAction`].
+You just need to define the corresponding Spring bean:
+```xml
+
+```
+You can also look at [example beans] used in the tests.
+
+Actions are evaluated by the [`FilerService`] in order. They are first sorted by the explicit order defined in the action ([`FilerAction`] implements `Ordered`) and then alphabetically by bean name.
+The first action that matches the conditions is selected and its classification is applied.
+
+## Properties inheritance
+
+Another characteristic of this module is the ability to define which properties should be inherited on the fileable node and also on the folder structure.
+It uses a specific marker aspect to label which aspects should have their properties duplicated.
+First, the properties of the inherited aspects are retrieved from the parent folder to supplement the node being filed.
+Then, each level of the classification can define the number of properties they inherit.
+
+For example, instead of using the description property, a custom aspect with a specific department label property can be set directly on the department folder.
+In this case any document created in it could also have the property directly added on them to make a search on the department of documents easier.
+The corresponding action implementation would look like this:
+```java
+ @Override
+ protected void execute(final FilerBuilder builder) {
+ builder.root(FilerNodeUtils::getSiteNodeRef)
+ .folder()
+ .named().with(SiteService.DOCUMENT_LIBRARY).get()
+ .folder(MyModel.TYPE_DEPARTMENT).asSegment()
+ .mandatoryPropertyInheritance(MyModel.ASPECT_DEPARTMENT)
+ .named().withProperty(MyModel.PROP_DEPARTMENT_LABEL).getOrCreate()
+ .folder().asSegment()
+ .named().withPropertyDate(ContentModel.PROP_CREATED, "yyyy").getOrCreate()
+ .updateAndMove();
+ }
+}
+```
+
+[example actions]: alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/action
+[folder structure creation]: alfresco-filer-core/src/test/java/com/atolcd/alfresco/filer/core/test/domain/service/impl/FilerTestActionServiceImpl.java
+[example beans]: alfresco-filer-core/src/test/resources/context/test-action-context.xml
+
+[`FilerAction`]: alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/FilerAction.java
+[`AbstractFilerAction`]: alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/model/impl/AbstractFilerAction.java
+[`FilerRegistry`]: alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/FilerRegistry.java
+[`FilerService`]: alfresco-filer-core/src/main/java/com/atolcd/alfresco/filer/core/service/FilerService.java
From d50c2b514de47f86092e9a62d87a8129742298a2 Mon Sep 17 00:00:00 2001
From: Nicolas Barithel
Date: Thu, 12 Dec 2019 10:54:17 +0100
Subject: [PATCH 51/58] Update Maven Jar Plugin 3.2.0
Change-Id: Ic702059783503a94d22fa4145430fdc231be6587
---
pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pom.xml b/pom.xml
index 8710bbf..806303b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -40,7 +40,7 @@
3.1.03.1.03.8.1
- 3.1.2
+ 3.2.03.1.03.1.12.6
From 3e29f4bf1447b24234f0b1beede2bfa0b0b917a6 Mon Sep 17 00:00:00 2001
From: Nicolas Barithel
Date: Thu, 12 Dec 2019 10:54:55 +0100
Subject: [PATCH 52/58] Update to Alfresco ACS 6.2.0-ga
Change-Id: I671ca9ade7617b0f9215d7087b75a5b09508e801
---
alfresco-filer-core/pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/alfresco-filer-core/pom.xml b/alfresco-filer-core/pom.xml
index ee4bd1f..7dbddf9 100644
--- a/alfresco-filer-core/pom.xml
+++ b/alfresco-filer-core/pom.xml
@@ -20,7 +20,7 @@
${project.name}${project.description}
- 6.2.0-A2
+ 6.2.0-ga${project.build.directory}/generated-test-resources
From f11fc4b1dc911d6927f76c75dfc00b86142f9afe Mon Sep 17 00:00:00 2001
From: Nicolas Barithel
Date: Thu, 12 Dec 2019 11:10:18 +0100
Subject: [PATCH 53/58] Update PostgreSQL embedded 0.13.3
Change-Id: I8427b3cc6978a3dc0e645e514292a67b6cf2a824
---
alfresco-filer-parent/pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/alfresco-filer-parent/pom.xml b/alfresco-filer-parent/pom.xml
index c9373c9..fe1e61a 100644
--- a/alfresco-filer-parent/pom.xml
+++ b/alfresco-filer-parent/pom.xml
@@ -28,7 +28,7 @@
5.1.8.RELEASE3.1.03.12.2
- 0.13.1
+ 0.13.33.0.1
From a73c157c7c8190370bec3ad21bb3e0f023e27ffe Mon Sep 17 00:00:00 2001
From: Nicolas Barithel
Date: Thu, 12 Dec 2019 11:12:04 +0100
Subject: [PATCH 54/58] Update Mockito 3.2.0
Change-Id: Ie1c5d61f9482f0645acc30524a1fa3c17867bf77
---
alfresco-filer-parent/pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/alfresco-filer-parent/pom.xml b/alfresco-filer-parent/pom.xml
index fe1e61a..9f88a6b 100644
--- a/alfresco-filer-parent/pom.xml
+++ b/alfresco-filer-parent/pom.xml
@@ -26,7 +26,7 @@
5.5.21.5.25.1.8.RELEASE
- 3.1.0
+ 3.2.03.12.20.13.33.0.1
From bc1f498bf80f36eb9951c62161ce1b2316fd40a4 Mon Sep 17 00:00:00 2001
From: Nicolas Barithel
Date: Thu, 12 Dec 2019 11:14:54 +0100
Subject: [PATCH 55/58] Update AssertJ 3.14.0
Change-Id: I05ce4b489ca359726fa4c2a89206c731503fe7d2
---
alfresco-filer-parent/pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/alfresco-filer-parent/pom.xml b/alfresco-filer-parent/pom.xml
index 9f88a6b..f1d8ff1 100644
--- a/alfresco-filer-parent/pom.xml
+++ b/alfresco-filer-parent/pom.xml
@@ -27,7 +27,7 @@
1.5.25.1.8.RELEASE3.2.0
- 3.12.2
+ 3.14.00.13.33.0.1
From 5f88ebb50844f2ed61f612df4ff4b8efe60935f4 Mon Sep 17 00:00:00 2001
From: Nicolas Barithel
Date: Thu, 12 Dec 2019 11:18:00 +0100
Subject: [PATCH 56/58] Update Jacoco Maven Plugin 0.8.5
Change-Id: I8fdaf90b37586135f3539141d442280f4ad9e32d
---
alfresco-filer-parent/pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/alfresco-filer-parent/pom.xml b/alfresco-filer-parent/pom.xml
index f1d8ff1..9b570b8 100644
--- a/alfresco-filer-parent/pom.xml
+++ b/alfresco-filer-parent/pom.xml
@@ -21,7 +21,7 @@
6.16.03.1.12.22.22.2
- 0.8.4
+ 0.8.55.5.21.5.2
From 923558695d23c4f4016389fa14e0d3ad58cd178c Mon Sep 17 00:00:00 2001
From: Nicolas Barithel
Date: Thu, 12 Dec 2019 11:25:51 +0100
Subject: [PATCH 57/58] =?UTF-8?q?[release]=C2=A0updating=20poms=20for=200.?=
=?UTF-8?q?1.0=20branch=20with=20snapshot=20versions?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
alfresco-filer-core/pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/alfresco-filer-core/pom.xml b/alfresco-filer-core/pom.xml
index 7dbddf9..248142b 100644
--- a/alfresco-filer-core/pom.xml
+++ b/alfresco-filer-core/pom.xml
@@ -163,7 +163,7 @@
-
+
From 5ce9dbf6270522a14a57e42501555faa405edbad Mon Sep 17 00:00:00 2001
From: Nicolas Barithel
Date: Thu, 12 Dec 2019 11:26:14 +0100
Subject: [PATCH 58/58] =?UTF-8?q?[release]=C2=A0updating=20poms=20for=20br?=
=?UTF-8?q?anch'rl-0.1.0'=20with=20non-snapshot=20versions?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
alfresco-filer-core/pom.xml | 2 +-
alfresco-filer-parent/pom.xml | 8 ++++----
alfresco-filer-quality/pom.xml | 2 +-
pom.xml | 2 +-
4 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/alfresco-filer-core/pom.xml b/alfresco-filer-core/pom.xml
index 248142b..54a9071 100644
--- a/alfresco-filer-core/pom.xml
+++ b/alfresco-filer-core/pom.xml
@@ -5,7 +5,7 @@
com.atolcd.alfresco.fileralfresco-filer-parent
- 0.1.0-SNAPSHOT
+ 0.1.0../alfresco-filer-parent/pom.xml
diff --git a/alfresco-filer-parent/pom.xml b/alfresco-filer-parent/pom.xml
index 9b570b8..cf7cd07 100644
--- a/alfresco-filer-parent/pom.xml
+++ b/alfresco-filer-parent/pom.xml
@@ -5,7 +5,7 @@
com.atolcd.alfresco.fileralfresco-filer
- 0.1.0-SNAPSHOT
+ 0.1.0alfresco-filer-parent
@@ -142,7 +142,7 @@
com.atolcd.alfresco.fileralfresco-filer-quality
- 0.1.0-SNAPSHOT
+ 0.1.0com.puppycrawl.tools
@@ -176,7 +176,7 @@
com.atolcd.alfresco.fileralfresco-filer-quality
- 0.1.0-SNAPSHOT
+ 0.1.0net.sourceforge.pmd
@@ -212,7 +212,7 @@
com.atolcd.alfresco.fileralfresco-filer-quality
- 0.1.0-SNAPSHOT
+ 0.1.0
diff --git a/alfresco-filer-quality/pom.xml b/alfresco-filer-quality/pom.xml
index f8fb0dc..75db0bd 100644
--- a/alfresco-filer-quality/pom.xml
+++ b/alfresco-filer-quality/pom.xml
@@ -5,7 +5,7 @@
com.atolcd.alfresco.fileralfresco-filer
- 0.1.0-SNAPSHOT
+ 0.1.0alfresco-filer-quality
diff --git a/pom.xml b/pom.xml
index 806303b..d344076 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
com.atolcd.alfresco.fileralfresco-filer
- 0.1.0-SNAPSHOT
+ 0.1.0pomAtolCD Alfresco Filer