diff --git a/.asf.yaml b/.asf.yaml
index 106f10cc5..bdac7b620 100644
--- a/.asf.yaml
+++ b/.asf.yaml
@@ -1,4 +1,4 @@
-# https://cwiki.apache.org/confluence/display/INFRA/git+-+.asf.yaml+features#Git.asf.yamlfeatures-GitHubsettings
+# https://github.com/apache/infrastructure-asfyaml/blob/main/README.md
github:
description: "Apache Jackrabbit FileVault"
homepage: https://jackrabbit.apache.org/filevault/index.html
@@ -14,7 +14,10 @@ github:
- JCR
- SLING
- FELIX
- dependabot_alerts: true
+ dependabot_alerts: false
dependabot_updates: false
protected_branches:
master: {}
+ pull_requests:
+ # auto-delete head branches after being merged
+ del_branch_on_merge: true
\ No newline at end of file
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 000000000..bf8b9d511
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,127 @@
+# ~ Licensed to the Apache Software Foundation (ASF) under one
+# ~ or more contributor license agreements. See the NOTICE file
+# ~ distributed with this work for additional information
+# ~ regarding copyright ownership. The ASF licenses this file
+# ~ to you 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.
+
+name: Build
+on:
+ push:
+ branches:
+ - master
+ pull_request:
+ types: [opened, synchronize, reopened]
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+jobs:
+ build:
+ name: ${{ matrix.namePrefix }} Maven build (${{ matrix.os }}, JDK ${{ matrix.jdk }})
+ strategy:
+ matrix:
+ os: [ubuntu-latest]
+ jdk: [17, 21, 25]
+ include:
+ # lengthy build steps should only be performed on linux with Java 21 (deployment)
+ - os: ubuntu-latest
+ jdk: 21
+ isMainBuildEnv: true
+ namePrefix: 'Main '
+ # just build on latest LTS JDK with windows/mac os
+ - os: windows-latest
+ jdk: 25
+ - os: macOS-latest
+ jdk: 25
+ fail-fast: true
+ runs-on: ${{ matrix.os }}
+ steps:
+ - name: Git clone
+ uses: actions/checkout@v6
+ - name: Set up JDK
+ uses: actions/setup-java@v5
+ with:
+ distribution: 'temurin'
+ java-version: ${{ matrix.jdk }}
+ cache: maven
+ server-id: apache.snapshots.https # Value of the distributionManagement/repository/id field of the pom.xml
+ server-username: MAVEN_APACHE_NEXUS_USERNAME # env variable for username in deploy
+ server-password: MAVEN_APACHE_NEXUS_PASSWORD # env variable for token in deploy
+ # sets environment variables to be used in subsequent steps: https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-environment-variable
+ - name: Set environment variables
+ shell: bash
+ run: |
+ if [ "${{github.ref}}" = "refs/heads/master" ] && [ "${{github.event_name}}" = "push" ] && [ "${{github.repository_owner}}" = "apache" ] && [ "${{ matrix.isMainBuildEnv }}" = "true" ]; then
+ echo 'Running on main branch of the canonical repo'
+ echo "MVN_ADDITIONAL_OPTS=-DdeployAtEnd=true -Pdependency-check -DnvdApiKeyEnvironmentVariable=NVD_API_KEY" >> $GITHUB_ENV
+ echo "MVN_GOAL=deploy" >> $GITHUB_ENV
+ else
+ echo 'Running outside main branch/canonical repo'
+ if [ "${{ matrix.isMainBuildEnv }}" = "true" ]; then
+ echo "MVN_ADDITIONAL_OPTS=-Pdependency-check -DnvdApiKeyEnvironmentVariable=NVD_API_KEY" >> $GITHUB_ENV
+ else
+ echo "MVN_ADDITIONAL_OPTS=" >> $GITHUB_ENV
+ fi
+ echo "MVN_GOAL=install site" >> $GITHUB_ENV
+ fi
+ - name: Build
+ env:
+ # secrets were provided in https://issues.apache.org/jira/browse/INFRA-27930?
+ NVD_API_KEY: ${{ secrets.NIST_NVD_API_KEY }}
+ MAVEN_APACHE_NEXUS_USERNAME: ${{ secrets.NEXUS_USER }}
+ MAVEN_APACHE_NEXUS_PASSWORD: ${{ secrets.NEXUS_PW }}
+ shell: bash
+ # executing ITs requires installing artifacts to the local repository
+ run: mvn -B ${{ env.MVN_GOAL }} ${{ env.MVN_ADDITIONAL_OPTS }} -Pjacoco-report -Dlogback.configurationFile=vault-core/src/test/resources/logback-only-errors.xml
+ - name: Upload build result
+ uses: actions/upload-artifact@v7
+ with:
+ name: compiled-classes-and-coverage
+ # compare with https://docs.sonarsource.com/sonarqube-cloud/advanced-setup/languages/java/#java-analysis-and-bytecode
+ path: |
+ **/target/**/*.class
+ **/target/site/jacoco*/*.xml
+
+ # execute analysis in a separate job for better visualization and usage of matrix builds
+ # https://docs.sonarsource.com/sonarcloud/advanced-setup/ci-based-analysis/sonarscanner-for-maven/#invoking-the-goal
+ sonar:
+ name: SonarQube Analysis
+ runs-on: ubuntu-latest
+ needs: build
+ # not supported on forks, https://portal.productboard.com/sonarsource/1-sonarqube-cloud/c/50-sonarcloud-analyzes-external-pull-request
+ if: ${{ github.repository == 'apache/jackrabbit-filevault' }}
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
+ - name: Set up JDK
+ uses: actions/setup-java@v5
+ with:
+ java-version: 21
+ distribution: temurin
+ cache: maven
+ - name: Download compiled classes
+ uses: actions/download-artifact@v8
+ with:
+ name: compiled-classes-and-coverage
+ - name: Cache SonarQube packages
+ uses: actions/cache@v5
+ with:
+ path: ~/.sonar/cache
+ key: ${{ runner.os }}-sonar
+ restore-keys: ${{ runner.os }}-sonar
+ - name: Analyze with SonarQube
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
+ SONAR_TOKEN: ${{ secrets.SONARCLOUD_TOKEN }}
+ run: mvn -B org.sonarsource.scanner.maven:sonar-maven-plugin:5.7.0.6970:sonar -Dsonar.projectKey=apache_jackrabbit-filevault -Dsonar.organization=apache -Dsonar.scanner.skipJreProvisioning=true
\ No newline at end of file
diff --git a/README.md b/README.md
index 5c980d650..9feb8354e 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
[](https://issues.apache.org/jira/projects/JCRVLT/summary)
[](https://www.apache.org/licenses/LICENSE-2.0)
[](https://search.maven.org/artifact//org.apache.jackrabbit.vault/vault-cli)
-[](https://ci-builds.apache.org/job/Jackrabbit/job/filevault/job/master/)
+[](https://github.com/apache/jackrabbit-filevault/actions/workflows/build.yml)
[](https://sonarcloud.io/summary/overall?id=apache_jackrabbit-filevault)
[](https://sonarcloud.io/component_measures?metric=Coverage&view=list&id=apache_jackrabbit-filevault)
[](https://sonarcloud.io/project/issues?resolved=false&types=BUG&id=apache_jackrabbit-filevault)
@@ -25,9 +25,23 @@ Please refer to the documentation at
-Building FileVault
+Runtime Prerequisites
===========================================
+Java 11 or higher
+
+Runtime Dependencies
+--------------------
+
+- Jackrabbit 2.20.17+ (JCR Commons, SPI, SPI Commons)
+- Oak Jackrabbit API 1.22.4+
+- Commons IO 2.18.0+
+- Commons Collections 4.1+
+- SLF4J 1.7+
+
+Build
+=========
+
You can build FileVault like this:
mvn clean install
@@ -38,7 +52,7 @@ For more instructions, please see the documentation at:
Building FileVault Site
-============================================
+-----------------------
The FileVault documentation lives as Markdown files in `src/site/markdown` such
that it easy to view e.g. from GitHub. The Maven site plugin
diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index d6b838d82..d812210b6 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -1,4 +1,4 @@
-Release Notes -- Apache Jackrabbit FileVault -- Version 3.8.4
+Release Notes -- Apache Jackrabbit FileVault -- Version 4.2.0
Introduction
------------
@@ -10,6 +10,28 @@ The Vault Command Line Interface aka "vlt" provides a subversion like
utility to work and develop with repository content.
+Changes in Jackrabbit FileVault 4.2.0
+--------------------------------------
+
+Release Notes - Jackrabbit FileVault - Version 4.2.0
+This version requires Java 11 or above
+The OSGi bundles depend on Jackrabbit 2.20.17+ (JCR Commons, SPI, SPI Commons), Oak JR API 1.22.4+, Commons IO 2.18+, Commons Collections 4.1+ and SLF4J 1.7+
+
+Improvement
+ [JCRVLT-782] - Introduce spotless-maven-plugin
+ [JCRVLT-825] - Remove Patch Support (extracting files from packages to filesystem)
+ [JCRVLT-831] - For collection of namespace prefixes, avoid iterating over sibling nodes not contained in the filter(s)
+ [JCRVLT-832] - No test coverage for handling namespace information in PATH typed property values
+ [JCRVLT-839] - Unconditional child node iteration in AggregateImpl.walk()
+
+Test
+ [JCRVLT-834] - add test coverage for namespace prefixes on sibling nodes in ordered collections
+
+
+Task
+ [JCRVLT-823] - add .DS_Store to .gitignore
+ [JCRVLT-836] - log durations and number of nodes in AggregateImpl node walk
+
Changes in Jackrabbit FileVault 4.1.4
--------------------------------------
@@ -18,16 +40,16 @@ Release Notes - Jackrabbit FileVault - Version 4.1.4
This version requires Java 11 or above
The OSGi bundles depend on Jackrabbit 2.20.17+ (JCR Commons, SPI, SPI Commons), Oak JR API 1.22.4+, Commons IO 2.18+, Commons Collections 4.1+ and SLF4J 1.7+
-** Improvement
- * [JCRVLT-816] - Fix Security warnings
- * [JCRVLT-817] - All properties are checked twice if they are protected
- * [JCRVLT-818] - Vault CLI contains embedded vulnerable libraries
- * [JCRVLT-819] - Upgrade JLine 1.0 to 3.x
- * [JCRVLT-822] - avoid costly check if a property is protected
+Improvement
+ [JCRVLT-816] - Fix Security warnings
+ [JCRVLT-817] - All properties are checked twice if they are protected
+ [JCRVLT-818] - Vault CLI contains embedded vulnerable libraries
+ [JCRVLT-819] - Upgrade JLine 1.0 to 3.x
+ [JCRVLT-822] - avoid costly check if a property is protected
-** Task
- * [JCRVLT-813] - read node and property definitions lazy
- * [JCRVLT-814] - Remove redundant calculations in DocViewImporer
+Task
+ [JCRVLT-813] - read node and property definitions lazy
+ [JCRVLT-814] - Remove redundant calculations in DocViewImporer
Changes in Jackrabbit FileVault 4.1.2
@@ -47,7 +69,7 @@ This version requires Java 11 or above
The OSGi bundles depend on Jackrabbit 2.20.17+ (JCR Commons, SPI, SPI Commons), Oak JR API 1.22.4+, Commons IO 2.7+, Commons Collections 4.1+ and SLF4J 1.7+
Bug
-
+ [JCRVLT-800] - Version command is broken
[JCRVLT-807] - PackageProperties.getDateProperty() should not log exception on log level
Improvement
@@ -55,6 +77,8 @@ Improvement
[JCRVLT-748] - Fix XSD namespaces
[JCRVLT-764] - Require Java 11
[JCRVLT-790] - Upgrade embedded Jackrabbit/Oak version to 2.22.1/1.82.0
+ [JCRVLT-801] - jackrabbit-packagetype validator should detect conflicting filter rules
+ [JCRVLT-801] - Update ASF Parent to version 24
[JCRVLT-805] - DocViewImporter: remove redundant name calculation
[JCRVLT-808] - Improve multi-module code coverage
[JCRVLT-809] - DocViewImporter: logIgnoredProtectedProperties can slow down package import
diff --git a/parent/pom.xml b/parent/pom.xml
index bbb95d4e6..8471b7285 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -75,7 +75,7 @@ Apache Jackrabbit FileVault is a project of the Apache Software Foundation.
scm:git:https://gitbox.apache.org/repos/asf/jackrabbit-filevault.git
scm:git:https://gitbox.apache.org/repos/asf/jackrabbit-filevault.git
- master
+ jackrabbit-filevault-4.2.0
https://github.com/apache/jackrabbit-filevault/tree/${project.scm.tag}
@@ -85,13 +85,13 @@ Apache Jackrabbit FileVault is a project of the Apache Software Foundation.
- Jenkins
- https://ci-builds.apache.org/blue/organizations/jenkins/Jackrabbit%2Ffilevault/activity
+ github
+ https://github.com/apache/jackrabbit-filevault/actions/workflows/build.yml
- 4.1.5-SNAPSHOT
+ 4.2.1-SNAPSHOT
2.20.17
@@ -104,7 +104,7 @@ Apache Jackrabbit FileVault is a project of the Apache Software Foundation.2.18.0
true
- 7.2.1
+ 7.3.0
17
11
http://localhost:4502
@@ -112,7 +112,7 @@ Apache Jackrabbit FileVault is a project of the Apache Software Foundation.2.27.6
- 2025-10-21T10:22:56Z
+ 2026-02-23T13:52:50Z
@@ -357,11 +357,14 @@ Apache Jackrabbit FileVault is a project of the Apache Software Foundation.
+
+
spotless-format
${spotless.action}
- process-sources
+ verify
@@ -514,7 +517,7 @@ Bundle-Category: jackrabbit
org.owasp
dependency-check-maven
- 12.1.6
+ 12.2.2
@@ -530,6 +533,29 @@ Bundle-Category: jackrabbit
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pom.xml b/pom.xml
index faad90e71..1c025e22b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -62,7 +62,7 @@
- 2025-10-21T10:22:56Z
+ 2026-02-23T13:52:50Z
${project.basedir}/target/site/jacoco-aggregate/jacoco.xml
diff --git a/src/site/markdown/acls.md b/src/site/markdown/acls.md
index bfaf7c65b..a399367c5 100644
--- a/src/site/markdown/acls.md
+++ b/src/site/markdown/acls.md
@@ -18,7 +18,7 @@
Access Control Lists
=================
-
+
Overview
----------
diff --git a/src/site/markdown/authorizables.md b/src/site/markdown/authorizables.md
index abd1600cd..89f750f24 100644
--- a/src/site/markdown/authorizables.md
+++ b/src/site/markdown/authorizables.md
@@ -18,7 +18,7 @@
Authorizables
=================
-
+
Overview
----------
diff --git a/src/site/markdown/config.md b/src/site/markdown/config.md
index d3cfebdb4..915945372 100644
--- a/src/site/markdown/config.md
+++ b/src/site/markdown/config.md
@@ -18,7 +18,7 @@
Configuration
===========
-
+
## FileVault Core Bundle
diff --git a/src/site/markdown/docview.md b/src/site/markdown/docview.md
index 5b0b81b69..cb83a0774 100644
--- a/src/site/markdown/docview.md
+++ b/src/site/markdown/docview.md
@@ -18,7 +18,7 @@
FileVault Document View (DocView) Format
=================
-
+
Overview
----------
diff --git a/src/site/markdown/filter.md b/src/site/markdown/filter.md
index 8b65e8976..05b4ad4c2 100644
--- a/src/site/markdown/filter.md
+++ b/src/site/markdown/filter.md
@@ -22,7 +22,7 @@ the `META-INF/vault` directory. The `filter.xml` is used to load and initialize
the [WorkspaceFilter][api.WorkspaceFilter]. The workspace filter defines what parts of the JCR repository are
imported or exported during the respective operations through `vlt` or package management.
-
+
General Structure
-----------------
@@ -92,6 +92,20 @@ Since FileVault 3.1.28 ([JCRVLT-120](https://issues.apache.org/jira/browse/JCRVL
Then the `pattern` is matched against property paths instead of node paths.
If the attribute `matchProperties` is not set or `false` all properties directly below the given node paths are included/excluded, otherwise the pattern is compared with the full property path (in case properties are written/read) allowing to include/exclude only specific properties below an included node.
+### Evaluating filters
+To determine which filter applies for a specific path, the path is evaluated against each of the filters of the package, in order, comparing it against the root of each filter. The include and exclude patterns are not considered for this. The first found match is used. (see [JCRVLT-96]( https://issues.apache.org/jira/browse/JCRVLT-96) )
+
+e.g.
+```
+
+
+
+
+```
+Using these two entries, the content under /conf/app/settings/conf1 will be ignored since the first filter came first, even if the exclude rule will omit the node during installation.
+Since this can cause confusion, it is recommended to avoid overlapping filter roots. If overlapping roots are actually needed, the most specific one can be put first so it applies.
+
+
### XML Schema
One can leverage the [XML schema][xml.schema] provided at to validate a `filter.xml` of a content package. This schema also provides some documentation on the elements and attributes, so in most IDEs some help is exposed on hovering those.
diff --git a/src/site/markdown/importmode.md b/src/site/markdown/importmode.md
index 3879fe5ef..f01b3fcf6 100644
--- a/src/site/markdown/importmode.md
+++ b/src/site/markdown/importmode.md
@@ -21,7 +21,7 @@ The import mode defines how imported content affects existing content in the rep
Details on how node ids are treated during import are outlined at [Referenceable Nodes](referenceablenodes.html)
-
+
Regular content
----------------
diff --git a/src/site/markdown/installhooks.md b/src/site/markdown/installhooks.md
index a17f68a5f..0abbc7852 100644
--- a/src/site/markdown/installhooks.md
+++ b/src/site/markdown/installhooks.md
@@ -38,7 +38,7 @@ External install hooks are loaded through the class loader by their fully qualif
The following class loaders are used by default to load the given class:
-1. The class loader which loaded the `InstallHookProcessorImpl` class (in an OSGi container this is the bundle class loader of the FileVault bundle)
+1. The class loader which loaded the `InstallHookProcessorImpl` class (in an OSGi runtime this is the bundle class loader of the FileVault bundle)
2. The [context class loader of the current thread](https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html#getContextClassLoader--).
The class loader can be overridden by calling [`ImportOptions.setHookClassLoader(...)`][api.ImportOptions] and pass the import options then to the package importer.
diff --git a/src/site/markdown/rcp.md b/src/site/markdown/rcp.md
index f0ad776a1..0888c5f1f 100644
--- a/src/site/markdown/rcp.md
+++ b/src/site/markdown/rcp.md
@@ -21,7 +21,7 @@ Vault Remote Copy (rcp)
Jackrabbit Vault offers a simple method to copy nodes between repositories with Vault Remote Copy (RCP). It uses standard JCR API to create/update nodes at the destination repository and falls back to JCR SysView import for protected nodes. Protected properties in standard JCR API import mode are silently skipped. It internally relies on the class [`RepositoryCopier`](https://github.com/apache/jackrabbit-filevault/blob/master/vault-core/src/main/java/org/apache/jackrabbit/vault/util/RepositoryCopier.java).
-
+
Prerequisites
---------
diff --git a/src/site/markdown/referenceablenodes.md b/src/site/markdown/referenceablenodes.md
index 62cd2dba7..00ec52c6d 100644
--- a/src/site/markdown/referenceablenodes.md
+++ b/src/site/markdown/referenceablenodes.md
@@ -17,7 +17,7 @@
# Referenceable Nodes
-
+
## Overview
diff --git a/src/site/markdown/usage.md b/src/site/markdown/usage.md
index 34db3971e..8588e7e96 100644
--- a/src/site/markdown/usage.md
+++ b/src/site/markdown/usage.md
@@ -21,7 +21,7 @@ Usage Vault Console Tool (VLT)
**NOTE**: Parts of the following documentation are outdated and need review
- - -
-
+
------------------
The console tool is called `vlt` and has the following usage:
diff --git a/src/site/markdown/validation.md b/src/site/markdown/validation.md
index 13258eda4..94efa8c17 100644
--- a/src/site/markdown/validation.md
+++ b/src/site/markdown/validation.md
@@ -17,7 +17,7 @@
-->
# Validation
-
+
## Overview
@@ -58,7 +58,7 @@ ID | Description | Options | Incremental Execution Limitations
`jackrabbit-emptyelements` | Check for empty elements within DocView files (used for ordering purposes, compare with [(extended) Document View Format](docview.html)) which are included in the filter with import=replace as those are actually not replaced! | none | none
`jackrabbit-mergelimitations` | Checks for the limitation of import mode=merge outlined at [JCRVLT-255][jcrvlt-255]. | none | none
`jackrabbit-oakindex` | Checks if the package (potentially) modifies/creates an [Oak index definition](https://jackrabbit.apache.org/oak/docs/query/indexing.html#index-defnitions). This is done by evaluating both the filter.xml for potential matches as well as the actual content for nodes with jcr:primaryType `oak:indexDefinition`. | none | none
-`jackrabbit-packagetype` | Checks if the package type is correctly set for this package, i.e. is compliant with all rules outlined at [Package Types](packagetypes.html). | *jcrInstallerNodePathRegex*: the regular expression which all JCR paths of OSGi bundles and configurations within packages must match (default=`/([^/]*/){0,4}?(install|config)[\./].*`). This should match the paths being picked up by [JCR Installer](https://sling.apache.org/documentation/bundles/jcr-installer-provider.html). Paths of OSGi configurations based on `sling:OsgiConfig` nodes are tested against this pattern as well.
*additionalJcrInstallerFileNodePathRegex*: the regular expression which the JCR paths of all file-based OSGi bundles and configurations within packages must match in addition to *jcrInstallerPathRegex* (default=`.*\.(config|cfg|cfg\.json|jar)`). This should match the paths being picked up by [JCR Installer](https://sling.apache.org/documentation/bundles/jcr-installer-provider.html). OSGi configurations based on `sling:OsgiConfig` nodes are not tested against this pattern.
*legacyTypeSeverity*: the severity of the validation message for package type `mixed` (default = `warn`).
*noTypeSeverity*: the severity of the validation message when package type is not set at all (default = `warn`).
*prohibitMutableContent*: boolean flag determining whether package type `content` or `mixed` (mutable content) leads to a validation message with severity error (default = `false`). Useful when used with [Oak Composite NodeStore](https://jackrabbit.apache.org/oak/docs/nodestore/compositens.html).
*prohibitImmutableContent*: boolean flag determining whether package type `app`, `container` or `mixed` (immutable content) leads to a validation message with severity error (default = `false`). Useful when used with [Oak Composite NodeStore](https://jackrabbit.apache.org/oak/docs/nodestore/compositens.html).
*allowComplexFilterRulesInApplicationPackages*: boolean flag determining whether complex rules (containing includes/excludes) are allowed in application content packages (default = `false`).
*allowInstallHooksInApplicationPackages*: boolean flag determining whether [install hooks](installhooks.html) are allowed in application content packages (default = `false`).
*immutableRootNodeNames*: comma-separated list of immutable root node names (default = `"apps,libs"`) | none
+`jackrabbit-packagetype` | Checks if the package type is correctly set for this package, i.e. is compliant with all rules outlined at [Package Types](packagetypes.html). | *jcrInstallerNodePathRegex*: the regular expression which all JCR paths of OSGi bundles and configurations within packages must match (default=`/([^/]*/){0,4}?(install|config)[\./].*`). This should match the paths being picked up by [JCR Installer](https://sling.apache.org/documentation/bundles/jcr-installer-provider.html). Paths of OSGi configurations based on `sling:OsgiConfig` nodes are tested against this pattern as well.
*additionalJcrInstallerFileNodePathRegex*: the regular expression which the JCR paths of all file-based OSGi bundles and configurations within packages must match in addition to *jcrInstallerPathRegex* (default=`.*\.(config|cfg|cfg\.json|jar)`). This should match the paths being picked up by [JCR Installer](https://sling.apache.org/documentation/bundles/jcr-installer-provider.html). OSGi configurations based on `sling:OsgiConfig` nodes are not tested against this pattern.
*legacyTypeSeverity*: the severity of the validation message for package type `mixed` (default = `warn`).
*noTypeSeverity*: the severity of the validation message when package type is not set at all (default = `warn`).
*prohibitMutableContent*: boolean flag determining whether package type `content` or `mixed` (mutable content) leads to a validation message with severity error (default = `false`). Useful when used with [Oak Composite NodeStore](https://jackrabbit.apache.org/oak/docs/nodestore/compositens.html).
*prohibitImmutableContent*: boolean flag determining whether package type `app`, `container` or `mixed` (immutable content) leads to a validation message with severity error (default = `false`). Useful when used with [Oak Composite NodeStore](https://jackrabbit.apache.org/oak/docs/nodestore/compositens.html).
*allowComplexFilterRulesInApplicationPackages*: boolean flag determining whether complex rules (containing includes/excludes) are allowed in application content packages (default = `false`). This is often used when overlays are contained in packages ([JCRVLT-403](https://issues.apache.org/jira/browse/JCRVLT-403)).
*allowInstallHooksInApplicationPackages*: boolean flag determining whether [install hooks](installhooks.html) are allowed in application content packages (default = `false`).
*immutableRootNodeNames*: comma-separated list of immutable root node names (default = `"apps,libs"`) | none
`jackrabbit-nodetypes` | Checks if all non empty elements within [DocView files](docview.html) have the mandatory property `jcr:primaryType` set and follow the [node type definition of their given type](https://jackrabbit.apache.org/jcr/node-types.html). | *cnds*: A URI pointing to one or multiple [CNDs](https://jackrabbit.apache.org/jcr/node-type-notation.html) (separated by `,`) which define the additional namespaces and node types used apart from the [default ones defined in JCR 2.0](https://s.apache.org/jcr-2.0-spec/3_Repository_Model.html#3.7.11%20Standard%20Application%20Node%20Types) and the ones defined in the package's metadata. If a URI is pointing to a JAR, the validator will leverage all the node types being mentioned in the [`Sling-Nodetypes` manifest header](https://sling.apache.org/documentation/bundles/content-loading-jcr-contentloader.html#declared-node-type-registration). Apart from the [standard protocols](https://docs.oracle.com/javase/7/docs/api/java/net/URL.html#URL(java.lang.String,%20java.lang.String,%20int,%20java.lang.String)) the scheme `tccl` can be used to reference names from the [Thread's context class loader](https://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html#getContextClassLoader()). In the Maven plugin context this is the [plugin classloader](http://maven.apache.org/guides/mini/guide-maven-classloading.html?ref=driverlayer.com/web#3-plugin-classloaders).
*defaultNodeType*: the node type in expanded or qualified form which is used for unknown ancestor nodes which are not given otherwise (default = `nt:folder`). *Note* **Using the default is pretty conservative but the safest approach. It may lead to a lot of issues as `nt:folder` is heavily restricted. In general you cannot know with which type the parent node already exists in the repository and FileVault itself for a long time created `nt:folder` nodes as [intermediates](filter.html#Uncovered_ancestor_nodes) so this is the safest option. If you are sure that all intermediate node types are of the correct type, you should use a type with no restrictions (`nt:unstructured`)**.
*severityForDefaultNodeTypeViolations*: The severity of issues being emitted due to violations against the default node type (for implicit ancestor nodes, default = `WARN`).
*severityForUnknownNodetypes*: The severity of issues being emitted due to an unknown primary/mixin type set on a node (default = `WARN`).
*validNameSpaces*: Configure list of namespaces that are known to be valid. Syntax: `prefix1=http://uri1,prefix2=http://uri2,...`. | Child node validity, mandatory properties and mandatory child nodes are not checked as they might not be fully visible.
`jackrabbit-accesscontrol` | Checks that [access control list nodes (primary type `rep:ACL`, `rep:CugPolicy` and `rep:PrincipalPolicy`)](https://jackrabbit.apache.org/oak/docs/security/accesscontrol/default.html#Representation_in_the_Repository) are only used when the [package property's](./properties.html) `acHandling` is set to something but `ignore` or `clear` and also that there is at least one access control list node otherwise. | none | Validation message in case no access control list node is found but acHandling is set to anything but `ignore` or `clear` is suppressed.
`jackrabbit-duplicateuuid` | Checks that every value of property `jcr:uuid` is unique. Compare with [Referenceable Nodes](./referenceablenodes.html). | none | might emit false negatives (i.e. not detect duplicates)
@@ -113,4 +113,4 @@ The Validation API is currently used by the [FileVault Package Maven Plugin][fil
[javadoc.serviceloader]: https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html
[filevault.maven]: http://jackrabbit.apache.org/filevault-package-maven-plugin/
[jcrvlt-255]: https://issues.apache.org/jira/browse/JCRVLT-255
-[jcrvlt-170]: https://issues.apache.org/jira/browse/JCRVLT-170
\ No newline at end of file
+[jcrvlt-170]: https://issues.apache.org/jira/browse/JCRVLT-170
diff --git a/src/site/resources/asf_logo.png b/src/site/resources/asf_logo.png
deleted file mode 100644
index 94f91cfa4..000000000
Binary files a/src/site/resources/asf_logo.png and /dev/null differ
diff --git a/vault-core-it/vault-core-integration-tests/src/main/java/org/apache/jackrabbit/vault/packaging/integration/IsSubtreeFullyOverwrittenIT.java b/vault-core-it/vault-core-integration-tests/src/main/java/org/apache/jackrabbit/vault/packaging/integration/IsSubtreeFullyOverwrittenIT.java
new file mode 100644
index 000000000..30663455a
--- /dev/null
+++ b/vault-core-it/vault-core-integration-tests/src/main/java/org/apache/jackrabbit/vault/packaging/integration/IsSubtreeFullyOverwrittenIT.java
@@ -0,0 +1,326 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+package org.apache.jackrabbit.vault.packaging.integration;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import java.io.IOException;
+
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.jackrabbit.commons.JcrUtils;
+import org.apache.jackrabbit.vault.fs.api.ImportMode;
+import org.apache.jackrabbit.vault.fs.api.PathFilter;
+import org.apache.jackrabbit.vault.fs.api.PathFilterSet;
+import org.apache.jackrabbit.vault.fs.config.ConfigurationException;
+import org.apache.jackrabbit.vault.fs.config.DefaultWorkspaceFilter;
+import org.apache.jackrabbit.vault.fs.filter.DefaultPathFilter;
+import org.apache.jackrabbit.vault.fs.io.Archive;
+import org.apache.jackrabbit.vault.fs.io.ImportOptions;
+import org.apache.jackrabbit.vault.fs.io.Importer;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Integration tests for WorkspaceFilter#isSubtreeFullyOverwritten()
+ */
+public class IsSubtreeFullyOverwrittenIT extends IntegrationTestBase {
+
+ private static final String TEST_ROOT = "/tmp/isSubtreeFullyOverwritten";
+ private Node rootNode;
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ clean(TEST_ROOT);
+
+ rootNode = JcrUtils.getOrCreateByPath(TEST_ROOT, JcrConstants.NT_UNSTRUCTURED, admin);
+ }
+
+ /**
+ * Path is outside all filter roots: no covering filter set.
+ * Expects false (early exit, no repository traversal).
+ */
+ @Test
+ public void returnsFalseWhenPathNotCoveredByAnyFilter() throws RepositoryException, ConfigurationException {
+ DefaultWorkspaceFilter filter = new DefaultWorkspaceFilter();
+ PathFilterSet set = new PathFilterSet("/other/root");
+ set.addInclude(new DefaultPathFilter("/other/root(/.*)?"));
+ filter.add(set);
+
+ Node root = JcrUtils.getOrCreateByPath(TEST_ROOT + "/content", JcrConstants.NT_UNSTRUCTURED, admin);
+ admin.save();
+
+ assertFalse(filter.isSubtreeFullyCovered(rootNode));
+ }
+
+ /**
+ * Filter has MERGE_PROPERTIES (not REPLACE). Subtree must not be considered fully overwritten.
+ * Expects false (import mode check).
+ */
+ @Test
+ public void returnsFalseWhenImportModeIsNotReplace() throws RepositoryException, ConfigurationException {
+ DefaultWorkspaceFilter filter = new DefaultWorkspaceFilter();
+ PathFilterSet set = new PathFilterSet(TEST_ROOT);
+ set.addInclude(new DefaultPathFilter(TEST_ROOT + "(/.*)?"));
+ set.setImportMode(ImportMode.MERGE_PROPERTIES);
+ filter.add(set);
+
+ Node n = JcrUtils.getOrCreateByPath(TEST_ROOT + "/node", JcrConstants.NT_UNSTRUCTURED, admin);
+ admin.save();
+
+ assertFalse(filter.isSubtreeFullyCovered(n));
+ }
+
+ /**
+ * Path matches global-ignored filter. Must not traverse or consider overwritten.
+ * Expects false (global ignored check).
+ */
+ @Test
+ public void returnsFalseWhenPathIsGloballyIgnored() throws RepositoryException, ConfigurationException {
+ DefaultWorkspaceFilter filter = new DefaultWorkspaceFilter();
+ PathFilterSet set = new PathFilterSet(TEST_ROOT);
+ set.addInclude(new DefaultPathFilter(TEST_ROOT + "(/.*)?"));
+ filter.add(set);
+ filter.setGlobalIgnored(PathFilter.ALL);
+
+ Node n = JcrUtils.getOrCreateByPath(TEST_ROOT + "/node", JcrConstants.NT_UNSTRUCTURED, admin);
+ admin.save();
+
+ assertFalse(filter.isSubtreeFullyCovered(n));
+ }
+
+ /**
+ * Parent is included, but a child is excluded by filter. Recursive check finds child not contained.
+ * Expects false (contains() fails for excluded descendant).
+ */
+ @Test
+ public void returnsFalseWhenChildNodeIsExcludedByFilter() throws RepositoryException, ConfigurationException {
+ DefaultWorkspaceFilter filter = new DefaultWorkspaceFilter();
+ PathFilterSet set = new PathFilterSet(TEST_ROOT);
+ set.addInclude(new DefaultPathFilter(TEST_ROOT + "(/.*)?"));
+ set.addExclude(new DefaultPathFilter(TEST_ROOT + "/parent/excluded(/.*)?"));
+ filter.add(set);
+
+ Node p = JcrUtils.getOrCreateByPath(TEST_ROOT + "/parent", JcrConstants.NT_UNSTRUCTURED, admin);
+ JcrUtils.getOrCreateByPath(TEST_ROOT + "/parent/excluded", JcrConstants.NT_UNSTRUCTURED, admin);
+ admin.save();
+
+ assertFalse(filter.isSubtreeFullyCovered(p));
+ }
+
+ /**
+ * Subtree exists, REPLACE mode, all nodes and properties included. Recursive traversal succeeds.
+ * Expects true (full overwrite allowed).
+ */
+ @Test
+ public void returnsTrueWhenSubtreeExistsAndFullyIncluded() throws RepositoryException, ConfigurationException {
+ DefaultWorkspaceFilter filter = new DefaultWorkspaceFilter();
+ PathFilterSet set = new PathFilterSet(TEST_ROOT);
+ set.addInclude(new DefaultPathFilter(TEST_ROOT + "(/.*)?"));
+ filter.add(set);
+
+ Node p = JcrUtils.getOrCreateByPath(TEST_ROOT + "/parent", JcrConstants.NT_UNSTRUCTURED, admin);
+ JcrUtils.getOrCreateByPath(TEST_ROOT + "/parent/child", JcrConstants.NT_UNSTRUCTURED, admin);
+ admin.save();
+
+ assertTrue(filter.isSubtreeFullyCovered(p));
+ }
+
+ /**
+ * Single node, no children. All properties (e.g. jcr:primaryType) included. Edge case for recursion.
+ * Expects true.
+ */
+ @Test
+ public void returnsTrueWhenLeafNodeHasNoChildren() throws RepositoryException, ConfigurationException {
+ DefaultWorkspaceFilter filter = new DefaultWorkspaceFilter();
+ PathFilterSet set = new PathFilterSet(TEST_ROOT);
+ set.addInclude(new DefaultPathFilter(TEST_ROOT + "(/.*)?"));
+ filter.add(set);
+
+ Node l = JcrUtils.getOrCreateByPath(TEST_ROOT + "/leaf", JcrConstants.NT_UNSTRUCTURED, admin);
+ admin.save();
+
+ assertTrue(filter.isSubtreeFullyCovered(l));
+ }
+
+ /**
+ * Global-ignored filter matches a different path; test path is not ignored. Check proceeds normally.
+ * Expects true (global ignored does not apply).
+ */
+ @Test
+ public void returnsTrueWhenGlobalIgnoredDoesNotMatchPath() throws RepositoryException, ConfigurationException {
+ DefaultWorkspaceFilter filter = new DefaultWorkspaceFilter();
+ PathFilterSet set = new PathFilterSet(TEST_ROOT);
+ set.addInclude(new DefaultPathFilter(TEST_ROOT + "(/.*)?"));
+ filter.add(set);
+ filter.setGlobalIgnored(new DefaultPathFilter("/other/ignored(/.*)?"));
+
+ Node n = JcrUtils.getOrCreateByPath(TEST_ROOT + "/node", JcrConstants.NT_UNSTRUCTURED, admin);
+ admin.save();
+
+ assertTrue(filter.isSubtreeFullyCovered(n));
+ }
+
+ /**
+ * Property filter excludes a property on the node. includesProperty() fails during traversal.
+ * Expects false (property exclusion prevents full overwrite).
+ */
+ @Test
+ public void returnsFalseWhenPropertyIsExcludedByFilter() throws RepositoryException, ConfigurationException {
+ PathFilterSet nodeSet = new PathFilterSet(TEST_ROOT);
+ nodeSet.addInclude(new DefaultPathFilter(TEST_ROOT + "(/.*)?"));
+ PathFilterSet propSet = new PathFilterSet(TEST_ROOT);
+ propSet.addInclude(new DefaultPathFilter(TEST_ROOT + "(/.*)?"));
+ propSet.addExclude(new DefaultPathFilter(".*/customProp"));
+ DefaultWorkspaceFilter filter = new DefaultWorkspaceFilter();
+ filter.add(nodeSet, propSet);
+
+ Node node = JcrUtils.getOrCreateByPath(TEST_ROOT + "/withProp", JcrConstants.NT_UNSTRUCTURED, admin);
+ node.setProperty("customProp", "value");
+ admin.save();
+
+ assertFalse(filter.isSubtreeFullyCovered(node));
+ }
+
+ /**
+ * JCRVLT-830: Repo has a parent (e.g. content/mysite/en) and a child (page) that is excluded by the filter.
+ * When importing a package that does not contain that child, the importer may only remove it if the subtree
+ * is fully overwritten. Here the child is excluded, so the subtree is not fully overwritten.
+ * Expects false so the importer keeps the existing child instead of removing it.
+ */
+ @Test
+ public void jcrvlt830ReturnsFalseWhenExistingChildInRepoIsExcludedByFilter()
+ throws RepositoryException, ConfigurationException {
+ String contentRoot = TEST_ROOT + "/content/mysite";
+ DefaultWorkspaceFilter filter = new DefaultWorkspaceFilter();
+ PathFilterSet set = new PathFilterSet(contentRoot);
+ set.addInclude(new DefaultPathFilter(contentRoot + "(/.*)?"));
+ set.addExclude(new DefaultPathFilter(contentRoot + "/en/page(/.*)?"));
+ filter.add(set);
+ filter.setExtraValidationBeforeSubtreeRemoval(true);
+
+ Node n = JcrUtils.getOrCreateByPath(contentRoot + "/en", JcrConstants.NT_UNSTRUCTURED, admin);
+ JcrUtils.getOrCreateByPath(contentRoot + "/en/page", JcrConstants.NT_UNSTRUCTURED, admin);
+ admin.save();
+
+ assertFalse(filter.isSubtreeFullyCovered(n));
+ }
+
+ /**
+ * Same tree as {@link #jcrvlt830ReturnsFalseWhenExistingChildInRepoIsExcludedByFilter} but with extra validation
+ * disabled: {@link DefaultWorkspaceFilter#setExtraValidationBeforeSubtreeRemoval(boolean)} {@code false}.
+ */
+ @Test
+ public void whenExtraValidationBeforeSubtreeRemovalDisabled_excludedChildReportsSubtreeCovered()
+ throws RepositoryException, ConfigurationException {
+ String contentRoot = TEST_ROOT + "/content/mysite/disabled";
+ DefaultWorkspaceFilter filter = new DefaultWorkspaceFilter();
+ PathFilterSet set = new PathFilterSet(contentRoot);
+ set.addInclude(new DefaultPathFilter(contentRoot + "(/.*)?"));
+ set.addExclude(new DefaultPathFilter(contentRoot + "/en/page(/.*)?"));
+ filter.add(set);
+ filter.setExtraValidationBeforeSubtreeRemoval(false);
+
+ Node n = JcrUtils.getOrCreateByPath(contentRoot + "/en", JcrConstants.NT_UNSTRUCTURED, admin);
+ JcrUtils.getOrCreateByPath(contentRoot + "/en/page", JcrConstants.NT_UNSTRUCTURED, admin);
+ admin.save();
+
+ assertTrue(filter.isSubtreeFullyCovered(n));
+ }
+
+ /**
+ * JCRVLT-830: When extra validation before subtree removal is disabled, a full import run must actually remove
+ * nodes that are no longer in the package, even if a sibling is excluded by the filter (legacy behavior).
+ */
+ @Test
+ public void whenExtraValidationBeforeSubtreeRemovalDisabled_subtreeIsRemovedOnReimport()
+ throws IOException, RepositoryException, ConfigurationException {
+ String importRoot = "/tmp";
+ clean(importRoot);
+
+ DefaultWorkspaceFilter filter = new DefaultWorkspaceFilter();
+ PathFilterSet set = new PathFilterSet(importRoot);
+ set.addInclude(new DefaultPathFilter(importRoot + "(/.*)?"));
+ set.addExclude(new DefaultPathFilter(importRoot + "/foo/bar/excluded(/.*)?"));
+ filter.add(set);
+ filter.setExtraValidationBeforeSubtreeRemoval(false);
+
+ ImportOptions opts = getDefaultOptions();
+ opts.setFilter(filter);
+ Importer importer = new Importer(opts);
+ Node rootNode = admin.getRootNode();
+
+ try (Archive archive = getFileArchive("/test-packages/tmp.zip")) {
+ archive.open(true);
+ importer.run(archive, rootNode);
+ }
+ assertNodeExists("/tmp/foo/bar/tobi");
+
+ JcrUtils.getOrCreateByPath("/tmp/foo/bar/excluded", JcrConstants.NT_UNSTRUCTURED, admin);
+ admin.save();
+
+ try (Archive archive = getFileArchive("/test-packages/tmp_less.zip")) {
+ archive.open(true);
+ importer.run(archive, rootNode);
+ }
+ assertNodeMissing("/tmp/foo/bar/tobi");
+ }
+
+ /**
+ * JCRVLT-830: When extra validation before subtree removal is enabled (default), a full import run must not remove
+ * nodes when a sibling is excluded by the filter (subtree not fully covered).
+ */
+ @Test
+ public void whenExtraValidationBeforeSubtreeRemovalEnabled_subtreeIsPreservedOnReimport()
+ throws IOException, RepositoryException, ConfigurationException {
+ String importRoot = "/tmp";
+ clean(importRoot);
+
+ DefaultWorkspaceFilter filter = new DefaultWorkspaceFilter();
+ PathFilterSet set = new PathFilterSet(importRoot);
+ set.addInclude(new DefaultPathFilter(importRoot + "(/.*)?"));
+ set.addExclude(new DefaultPathFilter(importRoot + "/foo/bar/excluded(/.*)?"));
+ filter.add(set);
+ filter.setExtraValidationBeforeSubtreeRemoval(true);
+
+ ImportOptions opts = getDefaultOptions();
+ opts.setFilter(filter);
+ Importer importer = new Importer(opts);
+ Node rootNode = admin.getRootNode();
+
+ try (Archive archive = getFileArchive("/test-packages/tmp.zip")) {
+ archive.open(true);
+ importer.run(archive, rootNode);
+ }
+ assertNodeExists("/tmp/foo/bar/tobi");
+
+ JcrUtils.getOrCreateByPath("/tmp/foo/bar/excluded", JcrConstants.NT_UNSTRUCTURED, admin);
+ admin.save();
+
+ try (Archive archive = getFileArchive("/test-packages/tmp_less.zip")) {
+ archive.open(true);
+ importer.run(archive, rootNode);
+ }
+ assertNodeExists("/tmp/foo/bar/tobi");
+ }
+}
diff --git a/vault-core-it/vault-core-it-execution/vault-core-it-execution-oak-min/pom.xml b/vault-core-it/vault-core-it-execution/vault-core-it-execution-oak-min/pom.xml
index 00e32bab5..66654128c 100644
--- a/vault-core-it/vault-core-it-execution/vault-core-it-execution-oak-min/pom.xml
+++ b/vault-core-it/vault-core-it-execution/vault-core-it-execution-oak-min/pom.xml
@@ -1,4 +1,5 @@
-
- 4.0.0
-
-
-
-
- org.apache.jackrabbit.vault
- vault-core-it-execution
- ${revision}
-
+ 4.0.0
+
+
+
+
+ org.apache.jackrabbit.vault
+ vault-core-it-execution
+ ${revision}
+
-
-
-
- vault-core-it-execution-oak-min
+
+
+
+ vault-core-it-execution-oak-min
- Apache Jackrabbit FileVault Core IT Execution Oak Minimum Version
- Executes Core ITs with minimally supported Oak version
+ Apache Jackrabbit FileVault Core IT Execution Oak Minimum Version
+ Executes Core ITs with minimally supported Oak version
-
-
-
-
-
-
- maven-failsafe-plugin
-
-
-
+
+
+
+
+
+ org.apache.jackrabbit.vault
+ vault-core-it-support-oak
+ ${project.version}
+ runtime
+
+
+ org.apache.jackrabbit
+ oak-core
+ ${oak.min.version}
+ runtime
+
+
+ org.apache.jackrabbit
+ oak-core
+ ${oak.min.version}
+ tests
+ runtime
+
+
+ org.apache.jackrabbit
+ jackrabbit-data
+ 2.20.1
+ runtime
+
+
+ org.apache.jackrabbit
+ oak-jcr
+ ${oak.min.version}
+ runtime
+
+
+ org.apache.jackrabbit
+ oak-segment-tar
+ ${oak.min.version}
+ runtime
+
+
+
+ io.dropwizard.metrics
+ metrics-core
+ 3.2.3
+ runtime
+
+
+ org.apache.jackrabbit
+ oak-authorization-principalbased
+ ${oak.min.version}
+ runtime
+
+
+ org.apache.jackrabbit
+ oak-authorization-cug
+ ${oak.min.version}
+ runtime
+
+
-
-
-
-
-
- org.apache.jackrabbit.vault
- vault-core-it-support-oak
- ${project.version}
- runtime
-
-
- org.apache.jackrabbit
- oak-core
- ${oak.min.version}
- runtime
-
-
- org.apache.jackrabbit
- oak-core
- ${oak.min.version}
- tests
- runtime
-
-
- org.apache.jackrabbit
- jackrabbit-data
- 2.20.1
- runtime
-
-
- org.apache.jackrabbit
- oak-jcr
- ${oak.min.version}
- runtime
-
-
- org.apache.jackrabbit
- oak-segment-tar
- ${oak.min.version}
- runtime
-
-
-
- io.dropwizard.metrics
- metrics-core
- 3.2.3
- runtime
-
-
- org.apache.jackrabbit
- oak-authorization-principalbased
- ${oak.min.version}
- runtime
-
-
- org.apache.jackrabbit
- oak-authorization-cug
- ${oak.min.version}
- runtime
-
-
+
+
+
+
+
+
+ maven-failsafe-plugin
+
+
+
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/api/WorkspaceFilter.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/api/WorkspaceFilter.java
index 8b41fb250..1e17c8675 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/api/WorkspaceFilter.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/api/WorkspaceFilter.java
@@ -182,4 +182,25 @@ void dumpCoverage(@NotNull Session session, @NotNull ProgressTrackerListener lis
* @return {@code true} if the property is included in the filter
*/
boolean includesProperty(String propertyPath);
+
+ /**
+ * Returns whether the given path's subtree is supposed to be fully covered during import,
+ * by traversing the repository and checking that every node and property in the subtree is
+ * included by this filter. Returns {@code true} only when:
+ *
+ * - the path is covered by this filter and import mode is {@link ImportMode#REPLACE},
+ * - the node at {@code path} exists in the repository, and
+ * - for every descendant node, {@link #contains(String)} is {@code true}, and
+ * - for every property in the subtree, {@link #includesProperty(String)} is {@code true}.
+ *
+ * When this method returns {@code true}, an importer may safely remove the existing node at
+ * the path and replace it and its children with the package content. When it returns {@code false},
+ * removal should be avoided or done selectively.
+ * @param subTree the subtree to validate
+ *
+ * @return {@code true} if every node and property on the node {subTree} and its children is included and mode is REPLACE,
+ * {@code false} otherwise.
+ * @throws RepositoryException if the path does not exist or traversal fails
+ */
+ boolean isSubtreeFullyCovered(@NotNull Node subTree) throws RepositoryException;
}
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/api/package-info.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/api/package-info.java
index 82607baef..d9655674a 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/api/package-info.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/api/package-info.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-@Version("2.13.0")
+@Version("2.14.0")
package org.apache.jackrabbit.vault.fs.api;
import org.osgi.annotation.versioning.Version;
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/config/DefaultWorkspaceFilter.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/config/DefaultWorkspaceFilter.java
index da970466e..8c98fb1d0 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/config/DefaultWorkspaceFilter.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/config/DefaultWorkspaceFilter.java
@@ -19,6 +19,8 @@
package org.apache.jackrabbit.vault.fs.config;
import javax.jcr.NodeIterator;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.xml.parsers.DocumentBuilder;
@@ -106,6 +108,13 @@ public class DefaultWorkspaceFilter implements Dumpable, WorkspaceFilter {
*/
private ImportMode importMode;
+ /**
+ * When {@code true} (default), {@link #isSubtreeFullyCovered(javax.jcr.Node)} performs the full subtree check
+ * (JCRVLT-830). When {@code false}, {@link #isSubtreeFullyCovered(javax.jcr.Node)} always returns {@code true} so importers behave as before the
+ * extra validation. This value is not persisted.
+ */
+ private boolean extraValidationBeforeSubtreeRemoval = true;
+
/**
* Add a #PathFilterSet for nodes items.
* @param set the set of filters to add.
@@ -278,6 +287,61 @@ public boolean includesProperty(String propertyPath) {
return false;
}
+ /**
+ * Enables or disables extra validation reflected by {@link #isSubtreeFullyCovered(javax.jcr.Node)} (JCRVLT-830).
+ * @param extraValidationBeforeSubtreeRemoval {@code true} to perform the subtree check; {@code false} to always
+ * report fully covered (legacy behavior for removal gating)
+ */
+ public void setExtraValidationBeforeSubtreeRemoval(boolean extraValidationBeforeSubtreeRemoval) {
+ this.extraValidationBeforeSubtreeRemoval = extraValidationBeforeSubtreeRemoval;
+ }
+
+ @Override
+ public boolean isSubtreeFullyCovered(javax.jcr.Node subTree) throws RepositoryException {
+ /**
+ * if this validation is explicitly disabled, just assume that the subtree is fully covered,
+ * which is the default behavior before this check was introduced with JCRVLT-830
+ */
+ if (!extraValidationBeforeSubtreeRemoval) {
+ return true;
+ }
+ if (subTree == null) {
+ return false;
+ }
+ String path = subTree.getPath();
+ if (isGloballyIgnored(path)) {
+ return false;
+ }
+ if (getCoveringFilterSet(path) == null) {
+ return false;
+ }
+ if (getImportMode(path) != ImportMode.REPLACE) {
+ return false;
+ }
+ return isSubtreeFullyOverwrittenRecursive(subTree);
+ }
+
+ private boolean isSubtreeFullyOverwrittenRecursive(javax.jcr.Node node) throws RepositoryException {
+ String nodePath = node.getPath();
+ if (!contains(nodePath)) {
+ return false;
+ }
+ PropertyIterator props = node.getProperties();
+ while (props.hasNext()) {
+ Property prop = props.nextProperty();
+ if (!includesProperty(prop.getPath())) {
+ return false;
+ }
+ }
+ NodeIterator children = node.getNodes();
+ while (children.hasNext()) {
+ if (!isSubtreeFullyOverwrittenRecursive(children.nextNode())) {
+ return false;
+ }
+ }
+ return true;
+ }
+
/**
* {@inheritDoc}
*/
@@ -333,6 +397,7 @@ public WorkspaceFilter translate(PathMapping mapping) {
for (PathFilterSet set : propsFilterSets) {
mapped.propsFilterSets.add(set.translate(mapping));
}
+ mapped.setExtraValidationBeforeSubtreeRemoval(extraValidationBeforeSubtreeRemoval);
return mapped;
}
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/config/package-info.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/config/package-info.java
index a29b5cbfa..5794b22a8 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/config/package-info.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/config/package-info.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-@Version("2.9.0")
+@Version("2.10.0")
package org.apache.jackrabbit.vault.fs.config;
import org.osgi.annotation.versioning.Version;
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/DocViewImporter.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/DocViewImporter.java
index 983febdba..81e7d547d 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/DocViewImporter.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/DocViewImporter.java
@@ -379,6 +379,7 @@ public void startDocViewNode(
throws IOException, RepositoryException {
stack.addName(docViewNode.getSnsAwareName());
Node node = stack.getNode();
+ log.debug("startDocViewNode(), nodePath= {}, node={}", nodePath, node != null ? node.getPath() : null);
if (node == null) {
stack = stack.push();
DocViewAdapter xform = stack.getAdapter();
@@ -494,8 +495,12 @@ public void endDocViewNode(
log.trace("Sysview transformation complete.");
}
} else {
+ log.debug("endDocViewNode(), nodePath= {}, node={}", nodePath, node.getPath());
NodeIterator iter = node.getNodes();
EffectiveNodeType entParent = null; // initialize once when required
+ // JCRVLT-830: parent's subtree coverage doesn't change across children of the same parent,
+ // so evaluate the (potentially expensive) recursive check at most once, and only if actually needed
+ SubtreeCoverage subtreeCoverage = SubtreeCoverage.UNKNOWN;
while (iter.hasNext()) {
numChildren++;
Node child = iter.nextNode();
@@ -505,9 +510,15 @@ public void endDocViewNode(
if (!childNames.contains(label)
&& !hints.contains(path)
&& isIncluded(child, child.getDepth() - rootDepth)) {
- // if the child is in the filter, it belongs to
- // this aggregate and needs to be removed
- if (aclManagement.isACLNode(child)) {
+ if (subtreeCoverage == SubtreeCoverage.UNKNOWN) {
+ subtreeCoverage = SubtreeCoverage.of(wspFilter.isSubtreeFullyCovered(node));
+ }
+ // Only remove or clear when the parent's subtree is fully overwritten by the filter (JCRVLT-830)
+ if (subtreeCoverage == SubtreeCoverage.NOT_FULLY_COVERED) {
+ log.debug(
+ "Skipping removal of child node {} because parent's subtree is not fully overwritten",
+ path);
+ } else if (aclManagement.isACLNode(child)) {
if (acHandling == AccessControlHandling.OVERWRITE
|| acHandling == AccessControlHandling.CLEAR) {
importInfo.onDeleted(path);
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/FolderArtifactHandler.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/FolderArtifactHandler.java
index 969bf306a..2abbd96ee 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/FolderArtifactHandler.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/FolderArtifactHandler.java
@@ -155,10 +155,20 @@ public ImportInfoImpl accept(
modifyPrimaryType(node, info);
}
NodeIterator iter = node.getNodes();
+ // JCRVLT-830: parent's subtree coverage doesn't change across children of the same parent,
+ // so evaluate the (potentially expensive) recursive check at most once, and only if actually needed
+ SubtreeCoverage subtreeCoverage = SubtreeCoverage.UNKNOWN;
while (iter.hasNext()) {
Node child = iter.nextNode();
String path = child.getPath();
if (wspFilter.contains(path) && wspFilter.getImportMode(path) == ImportMode.REPLACE) {
+ // Only remove when parent's subtree is fully overwritten (JCRVLT-830)
+ if (subtreeCoverage == SubtreeCoverage.UNKNOWN) {
+ subtreeCoverage = SubtreeCoverage.of(wspFilter.isSubtreeFullyCovered(node));
+ }
+ if (subtreeCoverage == SubtreeCoverage.NOT_FULLY_COVERED) {
+ continue;
+ }
if (!hints.contains(path)) {
// if the child is in the filter, it belongs to
// this aggregate and needs to be removed
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/SubtreeCoverage.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/SubtreeCoverage.java
new file mode 100644
index 000000000..97d598e2b
--- /dev/null
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/SubtreeCoverage.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+package org.apache.jackrabbit.vault.fs.impl.io;
+
+/**
+ * Lazily-cached result of {@code WorkspaceFilter#isSubtreeFullyCovered(javax.jcr.Node)} for a given parent node,
+ * evaluated at most once per parent even though it is queried once per candidate child (JCRVLT-830).
+ */
+enum SubtreeCoverage {
+ UNKNOWN,
+ FULLY_COVERED,
+ NOT_FULLY_COVERED;
+
+ static SubtreeCoverage of(boolean fullyCovered) {
+ return fullyCovered ? FULLY_COVERED : NOT_FULLY_COVERED;
+ }
+}
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/Importer.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/Importer.java
index 2fba58fad..a94cfad9c 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/Importer.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/Importer.java
@@ -285,6 +285,12 @@ public class Importer {
private final boolean isStrictByDefault;
private final boolean overwritePrimaryTypesOfFoldersByDefault;
+ /**
+ * When non-null, applied to {@link DefaultWorkspaceFilter} during import (JCRVLT-830 / OSGi). {@code null} leaves the
+ * filter unchanged (e.g. direct {@code Importer} use with {@link ImportOptions#setFilter}).
+ */
+ private final Boolean extraValidationBeforeSubtreeRemoval;
+
/**
* JCRVLT-683 feature flag. This variable is used to enable the new behavior of stashing principal policies when an
* Archive's package properties do not specify a {@code vault.feature.stashPrincipalPolicies} property.
@@ -341,10 +347,23 @@ public Importer(
boolean isStrictByDefault,
boolean overwritePrimaryTypesOfFoldersByDefault,
IdConflictPolicy defaultIdConflictPolicy) {
+ this(opts, isStrictByDefault, overwritePrimaryTypesOfFoldersByDefault, defaultIdConflictPolicy, null);
+ }
+
+ /**
+ * @param extraValidationBeforeSubtreeRemoval if non-null, applied to the workspace filter (OSGi / package install path)
+ */
+ public Importer(
+ ImportOptions opts,
+ boolean isStrictByDefault,
+ boolean overwritePrimaryTypesOfFoldersByDefault,
+ IdConflictPolicy defaultIdConflictPolicy,
+ Boolean extraValidationBeforeSubtreeRemoval) {
this.opts = opts;
this.isStrict = opts.isStrict(isStrictByDefault);
this.isStrictByDefault = isStrictByDefault;
this.overwritePrimaryTypesOfFoldersByDefault = overwritePrimaryTypesOfFoldersByDefault;
+ this.extraValidationBeforeSubtreeRemoval = extraValidationBeforeSubtreeRemoval;
if (!this.opts.hasIdConflictPolicyBeenSet() && defaultIdConflictPolicy != null) {
this.opts.setIdConflictPolicy(defaultIdConflictPolicy);
}
@@ -492,6 +511,10 @@ public void run(Archive archive, Session session, String parentPath)
filter.getClass().getName());
}
}
+ if (filter instanceof DefaultWorkspaceFilter && extraValidationBeforeSubtreeRemoval != null) {
+ ((DefaultWorkspaceFilter) filter)
+ .setExtraValidationBeforeSubtreeRemoval(extraValidationBeforeSubtreeRemoval);
+ }
// build filter tree
for (PathFilterSet set : filter.getFilterSets()) {
filterTree.put(set.getRoot(), set);
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/ZipNioArchive.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/ZipNioArchive.java
index f3a1b6ffe..42f6b2d34 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/ZipNioArchive.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/ZipNioArchive.java
@@ -223,6 +223,7 @@ private static final class VaultInputSourceImpl extends VaultInputSource {
private VaultInputSourceImpl(EntryImpl entryImpl) {
this.entryImpl = entryImpl;
+ setSystemId(entryImpl.getName());
}
@Override
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/package-info.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/package-info.java
index 9783d907f..8f991720e 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/package-info.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/package-info.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-@Version("2.16.0")
+@Version("2.17.0")
package org.apache.jackrabbit.vault.fs.io;
import org.osgi.annotation.versioning.Version;
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/InstallHookProcessorImpl.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/InstallHookProcessorImpl.java
index f73a3002b..cce5fa734 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/InstallHookProcessorImpl.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/InstallHookProcessorImpl.java
@@ -26,6 +26,8 @@
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.Enumeration;
+import java.util.LinkedList;
+import java.util.List;
import java.util.Properties;
import java.util.TreeMap;
import java.util.jar.JarFile;
@@ -38,6 +40,7 @@
import org.apache.jackrabbit.vault.packaging.InstallHook;
import org.apache.jackrabbit.vault.packaging.InstallHookProcessor;
import org.apache.jackrabbit.vault.packaging.PackageException;
+import org.apache.jackrabbit.vault.packaging.PackageId;
import org.apache.jackrabbit.vault.packaging.VaultPackage;
import org.apache.jackrabbit.vault.util.Constants;
import org.slf4j.Logger;
@@ -68,6 +71,7 @@ public void registerHooks(Archive archive, ClassLoader classLoader) throws Packa
log.warn("Archive {} does not have a {} directory.", archive, Constants.VAULT_DIR);
return;
}
+ PackageId packageId = archive.getMetaInf().getPackageProperties().getId();
root = root.getChild(Constants.HOOKS_DIR);
if (root == null) {
log.debug("Archive {} does not have a {} directory.", archive, Constants.HOOKS_DIR);
@@ -93,7 +97,8 @@ public void registerHooks(Archive archive, ClassLoader classLoader) throws Packa
throw new PackageException("Invalid installhook property: " + name);
}
Hook hook = new Hook(segs[0], props.getProperty(name), classLoader);
- initHook(hook);
+
+ initHook(hook, packageId);
}
}
}
@@ -117,14 +122,13 @@ public void registerHook(VaultInputSource input, ClassLoader classLoader) throws
}
throw e;
}
- initHook(hook);
+ initHook(hook, null);
}
- private void initHook(Hook hook) throws IOException, PackageException {
+ private void initHook(Hook hook, PackageId packageId) throws IOException, PackageException {
try {
- hook.init();
+ hook.init(packageId);
} catch (IOException | PackageException e) {
- log.error("Error while initializing hook: {}", e.toString());
try {
hook.destroy();
} catch (IOException ioeDuringDestroy) {
@@ -133,7 +137,7 @@ private void initHook(Hook hook) throws IOException, PackageException {
throw e;
}
hooks.put(hook.name, hook);
- log.info("Hook {} registered.", hook.name);
+ log.info("Hook {} registered for package {}.", hook.name, packageId);
}
public boolean hasHooks() {
@@ -178,6 +182,9 @@ public void close() throws IOException {
private class Hook {
+ /**
+ * The install hook's name, either derived from the jar file name or from the property name.
+ */
private final String name;
private final Path jarFile;
@@ -214,18 +221,21 @@ private void destroy() throws IOException {
}
}
- private void init() throws IOException, PackageException {
+ private void init(PackageId packageId) throws IOException, PackageException {
+ List suppressedExceptions = new LinkedList<>();
try {
if (jarFile != null) {
// open jar file and extract classname from manifest
try (JarFile jar = new JarFile(jarFile.toFile())) {
Manifest mf = jar.getManifest();
if (mf == null) {
- throw new PackageException("hook jar file does not have a manifest: " + name);
+ throw new PackageException(
+ "Hook JAR file does not have a manifest: " + name + " in package " + packageId);
}
mainClassName = mf.getMainAttributes().getValue("Main-Class");
if (mainClassName == null) {
- throw new PackageException("hook manifest file does not have a Main-Class entry: " + name);
+ throw new PackageException("Hook manifest file does not have a Main-Class entry: " + name
+ + " in package " + packageId);
}
}
// create classloader
@@ -236,19 +246,20 @@ private void init() throws IOException, PackageException {
urlClassLoader = URLClassLoader.newInstance(
new URL[] {jarFile.toUri().toURL()},
this.getClass().getClassLoader());
- loadMainClass(urlClassLoader);
+ loadMainClass(urlClassLoader, packageId);
} catch (ClassNotFoundException cnfe) {
+ suppressedExceptions.add(cnfe);
urlClassLoader.close();
// 2nd fallback is the thread context classloader
urlClassLoader = URLClassLoader.newInstance(
new URL[] {jarFile.toUri().toURL()},
Thread.currentThread().getContextClassLoader());
- loadMainClass(urlClassLoader);
+ loadMainClass(urlClassLoader, packageId);
}
} else {
urlClassLoader = URLClassLoader.newInstance(
new URL[] {jarFile.toUri().toURL()}, parentClassLoader);
- loadMainClass(urlClassLoader);
+ loadMainClass(urlClassLoader, packageId);
}
} else {
// create classloader
@@ -256,34 +267,41 @@ private void init() throws IOException, PackageException {
try {
// 1st fallback is the current classes classloader (the bundle classloader in the OSGi
// context)
- loadMainClass(this.getClass().getClassLoader());
+ loadMainClass(this.getClass().getClassLoader(), packageId);
} catch (ClassNotFoundException cnfe) {
+ suppressedExceptions.add(cnfe);
// 2nd fallback is the thread context classloader
- loadMainClass(Thread.currentThread().getContextClassLoader());
+ loadMainClass(Thread.currentThread().getContextClassLoader(), packageId);
}
} else {
- loadMainClass(parentClassLoader);
+ loadMainClass(parentClassLoader, packageId);
}
}
} catch (ClassNotFoundException cnfe) {
- throw new PackageException("hook's main class " + mainClassName + " not found: " + name, cnfe);
+ PackageException pe = new PackageException(
+ "Hook's main class " + mainClassName + " not found: " + name + " for package " + packageId,
+ cnfe);
+ suppressedExceptions.forEach(pe::addSuppressed);
}
}
- private void loadMainClass(ClassLoader classLoader) throws PackageException, ClassNotFoundException {
- log.info("Loading Hook {}: Main-Class = {}", name, mainClassName);
+ private void loadMainClass(ClassLoader classLoader, PackageId packageId)
+ throws PackageException, ClassNotFoundException {
+ log.info("Loading Hook {}: Main-Class = {} for package {}", name, mainClassName, packageId);
// find main class
Class> clazz = classLoader.loadClass(mainClassName);
if (!InstallHook.class.isAssignableFrom(clazz)) {
- throw new PackageException("hook's main class " + mainClassName
- + " does not implement the InstallHook interface: " + name);
+ throw new PackageException("Hook's main class " + mainClassName
+ + " does not implement the InstallHook interface: " + name + " for package " + packageId);
}
// create instance
try {
hook = (InstallHook) clazz.getDeclaredConstructor().newInstance();
} catch (Exception e) {
- throw new PackageException("hook's main class " + mainClassName + " could not be instantiated.", e);
+ throw new PackageException(
+ "Hook's main class " + mainClassName + " could not be instantiated for package " + packageId,
+ e);
}
}
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/JcrPackageImpl.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/JcrPackageImpl.java
index 293194f9c..7695591f4 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/JcrPackageImpl.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/JcrPackageImpl.java
@@ -391,7 +391,8 @@ private void extract(
mgr.getSecurityConfig(),
mgr.isStrictByDefault(),
mgr.overwritePrimaryTypesOfFoldersByDefault(),
- mgr.getDefaultIdConflictPolicy());
+ mgr.getDefaultIdConflictPolicy(),
+ mgr.isExtraValidationBeforeSubtreeRemovalByDefault());
JcrPackage snap = null;
if (!opts.isDryRun() && createSnapshot) {
ExportOptions eOpts = new ExportOptions();
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/JcrPackageManagerImpl.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/JcrPackageManagerImpl.java
index 559a23e51..2cac422c9 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/JcrPackageManagerImpl.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/JcrPackageManagerImpl.java
@@ -107,12 +107,33 @@ public JcrPackageManagerImpl(
boolean isStrict,
boolean overwritePrimaryTypesOfFoldersByDefault,
IdConflictPolicy idConflictPolicy) {
+ this(
+ session,
+ roots,
+ authIdsForHookExecution,
+ authIdsForRootInstallation,
+ isStrict,
+ overwritePrimaryTypesOfFoldersByDefault,
+ idConflictPolicy,
+ true);
+ }
+
+ public JcrPackageManagerImpl(
+ @NotNull Session session,
+ @Nullable String[] roots,
+ @Nullable String[] authIdsForHookExecution,
+ @Nullable String[] authIdsForRootInstallation,
+ boolean isStrict,
+ boolean overwritePrimaryTypesOfFoldersByDefault,
+ IdConflictPolicy idConflictPolicy,
+ boolean extraValidationBeforeSubtreeRemovalByDefault) {
this(new JcrPackageRegistry(
session,
new AbstractPackageRegistry.SecurityConfig(authIdsForHookExecution, authIdsForRootInstallation),
isStrict,
overwritePrimaryTypesOfFoldersByDefault,
idConflictPolicy,
+ extraValidationBeforeSubtreeRemovalByDefault,
roots));
}
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/PackagingImpl.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/PackagingImpl.java
index ce47f24bc..3cce42ff3 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/PackagingImpl.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/PackagingImpl.java
@@ -123,6 +123,14 @@ public PackagingImpl() {}
name = "Default ID Conflict Policy",
description = "Default node id conflict policy to use during import")
IdConflictPolicy defaultIdConflictPolicy() default IdConflictPolicy.FAIL;
+
+ @AttributeDefinition(
+ name = "Extra Validation Before Subtree Removal",
+ description =
+ "When enabled (default), nodes are only removed during import if the parent's subtree is fully "
+ + "covered by the filter (JCRVLT-830). When disabled, legacy behavior: remove when path is in "
+ + "filter and in REPLACE mode.")
+ boolean extraValidationBeforeSubtreeRemoval() default true;
}
@Activate
@@ -150,7 +158,8 @@ public JcrPackageManager getPackageManager(Session session) {
config.authIdsForRootInstallation(),
config.isStrict(),
config.overwritePrimaryTypesOfFolders(),
- config.defaultIdConflictPolicy());
+ config.defaultIdConflictPolicy(),
+ config.extraValidationBeforeSubtreeRemoval());
mgr.setDispatcher(eventDispatcher);
setBaseRegistry(mgr.getInternalRegistry(), registries);
return mgr;
@@ -212,6 +221,7 @@ private JcrPackageRegistry getJcrPackageRegistry(Session session, boolean useBas
config.isStrict(),
config.overwritePrimaryTypesOfFolders(),
config.defaultIdConflictPolicy(),
+ config.extraValidationBeforeSubtreeRemoval(),
config.packageRoots());
registry.setDispatcher(eventDispatcher);
if (useBaseRegistry) {
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/ZipVaultPackage.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/ZipVaultPackage.java
index 28a16cccd..2c41e8c59 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/ZipVaultPackage.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/ZipVaultPackage.java
@@ -46,6 +46,7 @@
import org.apache.jackrabbit.vault.packaging.VaultPackage;
import org.apache.jackrabbit.vault.packaging.registry.impl.AbstractPackageRegistry;
import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -177,6 +178,25 @@ public void extract(
boolean isOverwritePrimaryTypesOfFolders,
IdConflictPolicy defaultIdConflictPolicy)
throws PackageException, RepositoryException {
+ extract(
+ session,
+ opts,
+ securityConfig,
+ isStrict,
+ isOverwritePrimaryTypesOfFolders,
+ defaultIdConflictPolicy,
+ null);
+ }
+
+ public void extract(
+ Session session,
+ ImportOptions opts,
+ @NotNull AbstractPackageRegistry.SecurityConfig securityConfig,
+ boolean isStrict,
+ boolean isOverwritePrimaryTypesOfFolders,
+ IdConflictPolicy defaultIdConflictPolicy,
+ @Nullable Boolean extraValidationBeforeSubtreeRemoval)
+ throws PackageException, RepositoryException {
extract(
prepareExtract(
session,
@@ -184,7 +204,8 @@ public void extract(
securityConfig,
isStrict,
isOverwritePrimaryTypesOfFolders,
- defaultIdConflictPolicy),
+ defaultIdConflictPolicy,
+ extraValidationBeforeSubtreeRemoval),
null);
}
@@ -211,6 +232,7 @@ public PackageProperties getProperties() {
* @param isStrictByDefault is true if packages should be installed in strict mode by default (if not set otherwise in {@code opts})
* @param overwritePrimaryTypesOfFoldersByDefault if folder aggregates' JCR primary type should be changed if the node is already existing or not
* @param defaultIdConflictPolicy the default {@link IdConflictPolicy} to use if no policy is set in {@code opts}. May be {@code null}.
+ * @param extraValidationBeforeSubtreeRemoval JCRVLT-830 from OSGi when non-null; {@code null} leaves filter default
*
* @throws javax.jcr.RepositoryException if a repository error during installation occurs.
* @throws org.apache.jackrabbit.vault.packaging.PackageException if an error during packaging occurs
@@ -223,7 +245,8 @@ protected InstallContextImpl prepareExtract(
@NotNull AbstractPackageRegistry.SecurityConfig securityConfig,
boolean isStrictByDefault,
boolean overwritePrimaryTypesOfFoldersByDefault,
- IdConflictPolicy defaultIdConflictPolicy)
+ IdConflictPolicy defaultIdConflictPolicy,
+ @Nullable Boolean extraValidationBeforeSubtreeRemoval)
throws PackageException, RepositoryException {
if (!isValid()) {
throw new IllegalStateException("Package not valid.");
@@ -245,7 +268,11 @@ protected InstallContextImpl prepareExtract(
}
Importer importer = new Importer(
- opts, isStrictByDefault, overwritePrimaryTypesOfFoldersByDefault, defaultIdConflictPolicy);
+ opts,
+ isStrictByDefault,
+ overwritePrimaryTypesOfFoldersByDefault,
+ defaultIdConflictPolicy,
+ extraValidationBeforeSubtreeRemoval);
AccessControlHandling ac = getACHandling();
if (opts.getAccessControlHandling() == null) {
opts.setAccessControlHandling(ac);
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/package-info.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/package-info.java
index ae88c4a99..b9b1ec4eb 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/package-info.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/package-info.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-@Version("2.16.0")
+@Version("2.17.0")
package org.apache.jackrabbit.vault.packaging;
import org.osgi.annotation.versioning.Version;
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/AbstractPackageRegistry.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/AbstractPackageRegistry.java
index 3f10d057b..e76b8d979 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/AbstractPackageRegistry.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/AbstractPackageRegistry.java
@@ -90,11 +90,22 @@ public String[] getAuthIdsForRootInstallation() {
private final IdConflictPolicy defaultIdConflictPolicy;
+ private final boolean extraValidationBeforeSubtreeRemovalByDefault;
+
public AbstractPackageRegistry(
SecurityConfig securityConfig,
boolean isStrictByDefault,
boolean overwritePrimaryTypesOfFoldersByDefault,
IdConflictPolicy defaultIdConflictPolicy) {
+ this(securityConfig, isStrictByDefault, overwritePrimaryTypesOfFoldersByDefault, defaultIdConflictPolicy, true);
+ }
+
+ AbstractPackageRegistry(
+ SecurityConfig securityConfig,
+ boolean isStrictByDefault,
+ boolean overwritePrimaryTypesOfFoldersByDefault,
+ IdConflictPolicy defaultIdConflictPolicy,
+ boolean extraValidationBeforeSubtreeRemovalByDefault) {
if (securityConfig != null) {
this.securityConfig = securityConfig;
} else {
@@ -103,6 +114,7 @@ public AbstractPackageRegistry(
this.isStrictByDefault = isStrictByDefault;
this.overwritePrimaryTypesOfFoldersByDefault = overwritePrimaryTypesOfFoldersByDefault;
this.defaultIdConflictPolicy = defaultIdConflictPolicy;
+ this.extraValidationBeforeSubtreeRemovalByDefault = extraValidationBeforeSubtreeRemovalByDefault;
}
public boolean isStrictByDefault() {
@@ -117,6 +129,10 @@ public IdConflictPolicy getDefaultIdConflictPolicy() {
return defaultIdConflictPolicy;
}
+ public boolean isExtraValidationBeforeSubtreeRemovalByDefault() {
+ return extraValidationBeforeSubtreeRemovalByDefault;
+ }
+
/**
* {@inheritDoc}
*/
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/FSPackageRegistry.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/FSPackageRegistry.java
index 779854750..52c5c52cc 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/FSPackageRegistry.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/FSPackageRegistry.java
@@ -103,6 +103,8 @@ public class FSPackageRegistry extends AbstractPackageRegistry {
private InstallationScope scope = InstallationScope.UNSCOPED;
+ private boolean extraValidationBeforeSubtreeRemoval = true;
+
/**
* Creates a new FSPackageRegistry based on the given home directory.
*
@@ -179,6 +181,7 @@ public void activate(BundleContext context, Config config) throws IOException {
}
log.info("Jackrabbit Filevault FS Package Registry initialized with home location {}", homeDir.getPath());
this.scope = InstallationScope.valueOf(config.scope());
+ this.extraValidationBeforeSubtreeRemoval = config.extraValidationBeforeSubtreeRemoval();
this.securityConfig = new AbstractPackageRegistry.SecurityConfig(
config.authIdsForHookExecution(), config.authIdsForRootInstallation());
this.stateCache = new FSInstallStateCache(homeDir.toPath());
@@ -213,6 +216,13 @@ public void activate(BundleContext context, Config config) throws IOException {
description =
"The authorizable ids which are allowed to install packages with the 'requireRoot' flag (in addition to 'admin', 'administrators' and 'system'")
String[] authIdsForRootInstallation();
+
+ @AttributeDefinition(
+ name = "Extra Validation Before Subtree Removal",
+ description =
+ "When enabled (default), nodes are only removed during import if the parent's subtree is fully "
+ + "covered by the filter (JCRVLT-830). When disabled, legacy behavior applies.")
+ boolean extraValidationBeforeSubtreeRemoval() default true;
}
/**
@@ -670,7 +680,8 @@ public void installPackage(
getSecurityConfig(),
isStrictByDefault(),
overwritePrimaryTypesOfFoldersByDefault(),
- getDefaultIdConflictPolicy());
+ getDefaultIdConflictPolicy(),
+ extraValidationBeforeSubtreeRemoval);
dispatch(PackageEvent.Type.EXTRACT, pkg.getId(), null);
stateCache.updatePackageStatus(vltPkg.getId(), FSPackageStatus.EXTRACTED);
} else {
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/JcrPackageRegistry.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/JcrPackageRegistry.java
index d6e11eea2..7e1e059d9 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/JcrPackageRegistry.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/JcrPackageRegistry.java
@@ -132,7 +132,30 @@ public JcrPackageRegistry(
boolean overwritePrimaryTypesOfFoldersByDefault,
IdConflictPolicy defaultIdConflictPolicy,
@Nullable String... roots) {
- super(securityConfig, isStrict, overwritePrimaryTypesOfFoldersByDefault, defaultIdConflictPolicy);
+ this(
+ session,
+ securityConfig,
+ isStrict,
+ overwritePrimaryTypesOfFoldersByDefault,
+ defaultIdConflictPolicy,
+ true,
+ roots);
+ }
+
+ public JcrPackageRegistry(
+ @NotNull Session session,
+ @Nullable AbstractPackageRegistry.SecurityConfig securityConfig,
+ boolean isStrict,
+ boolean overwritePrimaryTypesOfFoldersByDefault,
+ IdConflictPolicy defaultIdConflictPolicy,
+ boolean extraValidationBeforeSubtreeRemovalByDefault,
+ @Nullable String... roots) {
+ super(
+ securityConfig,
+ isStrict,
+ overwritePrimaryTypesOfFoldersByDefault,
+ defaultIdConflictPolicy,
+ extraValidationBeforeSubtreeRemovalByDefault);
this.session = session;
if (roots == null || roots.length == 0) {
packRootPaths = new String[] {DEFAULT_PACKAGE_ROOT_PATH};
diff --git a/vault-core/src/test/java/org/apache/jackrabbit/vault/fs/filter/WorkspaceFilterTest.java b/vault-core/src/test/java/org/apache/jackrabbit/vault/fs/filter/WorkspaceFilterTest.java
index 3270dd28e..3b9c59f15 100644
--- a/vault-core/src/test/java/org/apache/jackrabbit/vault/fs/filter/WorkspaceFilterTest.java
+++ b/vault-core/src/test/java/org/apache/jackrabbit/vault/fs/filter/WorkspaceFilterTest.java
@@ -18,6 +18,9 @@
*/
package org.apache.jackrabbit.vault.fs.filter;
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
@@ -36,6 +39,7 @@
import org.apache.jackrabbit.vault.fs.config.ConfigurationException;
import org.apache.jackrabbit.vault.fs.config.DefaultWorkspaceFilter;
import org.junit.Test;
+import org.mockito.Mockito;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -310,4 +314,24 @@ private static void assertSetsEquals(Set> expected, Set> actual) {
assertTrue("Sets differ: " + diff2 + " unexpected", diff2.isEmpty());
}
}
+
+ @Test
+ public void extraValidationBeforeSubtreeRemovalDisabled_shortCircuitsIsSubtreeFullyCovered()
+ throws RepositoryException {
+ DefaultWorkspaceFilter filter = new DefaultWorkspaceFilter();
+ filter.setExtraValidationBeforeSubtreeRemoval(false);
+ Node anyNode = Mockito.mock(Node.class);
+ assertTrue(filter.isSubtreeFullyCovered(anyNode));
+ }
+
+ @Test
+ public void translatePreservesExtraValidationBeforeSubtreeRemovalFlag() throws RepositoryException {
+ DefaultWorkspaceFilter filter = new DefaultWorkspaceFilter();
+ PathFilterSet set = new PathFilterSet("/a");
+ filter.add(set);
+ filter.setExtraValidationBeforeSubtreeRemoval(false);
+ DefaultWorkspaceFilter mapped = (DefaultWorkspaceFilter) filter.translate(new SimplePathMapping("/a", "/b"));
+ Node anyNode = Mockito.mock(Node.class);
+ assertTrue(mapped.isSubtreeFullyCovered(anyNode));
+ }
}
diff --git a/vault-validation/pom.xml b/vault-validation/pom.xml
index e542829db..db7ac428f 100644
--- a/vault-validation/pom.xml
+++ b/vault-validation/pom.xml
@@ -95,6 +95,12 @@
maven-artifact
3.8.4
provided
+
+
+ *
+ *
+
+