From 56f45f2e544239242028f4da1783b4909c60d136 Mon Sep 17 00:00:00 2001 From: Joerg Hoh Date: Mon, 2 Feb 2026 11:22:12 +0100 Subject: [PATCH 01/35] JCRVLT-830 IT on DocView level to showcase the problem --- .../integration/AggregationJCRVLT830_2.java | 226 ++++++++++++++++++ .../vault/fs/impl/io/DocViewImporter.java | 2 + 2 files changed, 228 insertions(+) create mode 100644 vault-core-it/vault-core-integration-tests/src/main/java/org/apache/jackrabbit/vault/packaging/integration/AggregationJCRVLT830_2.java diff --git a/vault-core-it/vault-core-integration-tests/src/main/java/org/apache/jackrabbit/vault/packaging/integration/AggregationJCRVLT830_2.java b/vault-core-it/vault-core-integration-tests/src/main/java/org/apache/jackrabbit/vault/packaging/integration/AggregationJCRVLT830_2.java new file mode 100644 index 000000000..db40f1c4d --- /dev/null +++ b/vault-core-it/vault-core-integration-tests/src/main/java/org/apache/jackrabbit/vault/packaging/integration/AggregationJCRVLT830_2.java @@ -0,0 +1,226 @@ +/* + * 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.NodeIterator; +import javax.jcr.Property; +import javax.jcr.PropertyIterator; +import javax.jcr.RepositoryException; +import javax.jcr.Value; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; + +import org.apache.jackrabbit.commons.cnd.CndImporter; +import org.apache.jackrabbit.vault.fs.api.ArtifactType; +import org.apache.jackrabbit.vault.fs.api.IdConflictPolicy; +import org.apache.jackrabbit.vault.fs.api.ImportMode; +import org.apache.jackrabbit.vault.fs.api.PathFilterSet; +import org.apache.jackrabbit.vault.fs.api.SerializationType; +import org.apache.jackrabbit.vault.fs.api.VaultInputSource; +import org.apache.jackrabbit.vault.fs.config.DefaultWorkspaceFilter; +import org.apache.jackrabbit.vault.fs.filter.DefaultPathFilter; +import org.apache.jackrabbit.vault.fs.impl.ArtifactSetImpl; +import org.apache.jackrabbit.vault.fs.impl.io.GenericArtifactHandler; +import org.apache.jackrabbit.vault.fs.impl.io.InputSourceArtifact; +import org.apache.jackrabbit.vault.fs.io.AccessControlHandling; +import org.apache.jackrabbit.vault.fs.io.ImportOptions; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +public class AggregationJCRVLT830_2 extends IntegrationTestBase { + + private GenericArtifactHandler handler; + DefaultWorkspaceFilter wspFilter; + private Node contentNode; + + private static final String NODETYPES = "<'cq'='http://www.day.com/jcr/cq/1.0'>\n" + + "<'sling'='http://sling.apache.org/jcr/sling/1.0'>\n" + + "<'nt'='http://www.jcp.org/jcr/nt/1.0'>\n" + + "<'rep'='internal'>\n" + + "\n" + + "[sling:OrderedFolder] > sling:Folder\n" + + " orderable\n" + + " + * (nt:base) = sling:OrderedFolder version\n" + + "\n" + + "[sling:Folder] > nt:folder\n" + + " - * (undefined) multiple\n" + + " - * (undefined)\n" + + " + * (nt:base) = sling:Folder version\n"; + + @Before + public void setup() throws Exception { + // create the necessary nodetypes + CndImporter.registerNodeTypes(new InputStreamReader(new ByteArrayInputStream(NODETYPES.getBytes())), admin); + + handler = new GenericArtifactHandler(); + handler.setAcHandling(AccessControlHandling.OVERWRITE); + + // create a filter accepting everything + wspFilter = new DefaultWorkspaceFilter(); + PathFilterSet filterSet = new PathFilterSet(); + filterSet.addInclude(new DefaultPathFilter(".*")); + wspFilter.add(filterSet); + } + + @Test + public void importContent() throws Exception { + String xml = "\n" + + "\n" + + " \n" + + ""; + + // create basic node structure /content/mysite/en/page + contentNode = admin.getRootNode().addNode("content", "nt:unstructured"); + Node mysite = contentNode.addNode("mysite", "sling:Folder"); + Node en = mysite.addNode("en", "sling:Folder"); + Node page = en.addNode("page", "nt:unstructured"); + admin.save(); + + // import the docview + importDocViewXml(xml, contentNode, "mysite"); + admin.save(); + dumpRepositoryStructure(contentNode); + + assertNodeExists("/content/mysite/en"); + assertNodeExists("/content/mysite/en/page"); + assertEquals( + "bar", admin.getNode("/content/mysite/en").getProperty("foo").toString()); + assertProperty("/content/mysite/en/foo", "bar"); + } + + /** + * Helper method to import DocView XML using GenericArtifactHandler + */ + private void importDocViewXml(String xml, Node parentNode, String nodeName) + throws RepositoryException, IOException { + ArtifactSetImpl artifacts = new ArtifactSetImpl(); + + // Create VaultInputSource from the XML string + VaultInputSource vaultSource = new VaultInputSource() { + @Override + public InputStream getByteStream() { + return new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8)); + } + + @Override + public long getContentLength() { + return xml.length(); + } + + @Override + public long getLastModified() { + return System.currentTimeMillis(); + } + }; + + // Create the artifact using InputSourceArtifact + InputSourceArtifact artifact = new InputSourceArtifact( + null, // no parent + nodeName, // name + ".xml", // extension + ArtifactType.PRIMARY, // type + vaultSource, // input source + SerializationType.XML_DOCVIEW // serialization type + ); + + artifacts.add(artifact); + + ImportOptions options = new ImportOptions(); + options.setStrict(true); + options.setIdConflictPolicy(IdConflictPolicy.FAIL); + options.setImportMode(ImportMode.REPLACE); + options.setListener(getLoggingProgressTrackerListener()); + assertNotNull(handler.accept(options, true, wspFilter, parentNode, nodeName, artifacts)); + } + + /** + * Dumps the repository structure below the provided Node (including the node itself). + * Recursively prints all nodes, their properties and values with proper indentation. + * + * @param node the node to start dumping from + * @throws RepositoryException if an error occurs accessing the repository + */ + private void dumpRepositoryStructure(Node node) throws RepositoryException { + dumpNode(node, 0); + } + + /** + * Recursively dumps a node and its descendants with indentation. + * + * @param node the node to dump + * @param depth the current depth for indentation + * @throws RepositoryException if an error occurs accessing the repository + */ + private void dumpNode(Node node, int depth) throws RepositoryException { + String indent = " ".repeat(depth); + + // Print node path and primary type + System.out.println(indent + "+ " + node.getName() + " [" + + node.getPrimaryNodeType().getName() + "]"); + + // Print properties + PropertyIterator properties = node.getProperties(); + while (properties.hasNext()) { + Property property = properties.nextProperty(); + String propertyIndent = " ".repeat(depth + 1); + + // Skip jcr:primaryType as it's already shown + if ("jcr:primaryType".equals(property.getName())) { + continue; + } + + // Handle multi-value properties + if (property.isMultiple()) { + Value[] values = property.getValues(); + if (values.length > 0) { + System.out.print(propertyIndent + "- " + property.getName() + " = ["); + for (int i = 0; i < values.length; i++) { + if (i > 0) System.out.print(", "); + System.out.print(values[i].getString()); + } + System.out.println("]"); + } else { + System.out.println(propertyIndent + "- " + property.getName() + " = []"); + } + } else { + System.out.println(propertyIndent + "- " + property.getName() + " = " + property.getString()); + } + } + + // Recursively dump child nodes + NodeIterator children = node.getNodes(); + while (children.hasNext()) { + Node child = children.nextNode(); + dumpNode(child, depth + 1); + } + } +} 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..018b6e224 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.getPath()); if (node == null) { stack = stack.push(); DocViewAdapter xform = stack.getAdapter(); @@ -494,6 +495,7 @@ 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 while (iter.hasNext()) { From 7d2557d7eb2a4f867777986cf75ac09f4fabe90e Mon Sep 17 00:00:00 2001 From: Joerg Hoh Date: Thu, 26 Feb 2026 13:30:53 +0100 Subject: [PATCH 02/35] JCRVLT-830 add an extension of the WorkspaceFilter --- .../IsSubtreeFullyOverwrittenIT.java | 233 ++++++++++++++++++ .../vault/fs/api/WorkspaceFilter.java | 21 ++ .../jackrabbit/vault/fs/api/package-info.java | 2 +- .../fs/config/DefaultWorkspaceFilter.java | 43 ++++ .../vault/fs/config/package-info.java | 2 +- .../vault/fs/impl/io/DocViewImporter.java | 2 +- .../vault/packaging/package-info.java | 2 +- 7 files changed, 301 insertions(+), 4 deletions(-) create mode 100644 vault-core-it/vault-core-integration-tests/src/main/java/org/apache/jackrabbit/vault/packaging/integration/IsSubtreeFullyOverwrittenIT.java 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..57728d2a8 --- /dev/null +++ b/vault-core-it/vault-core-integration-tests/src/main/java/org/apache/jackrabbit/vault/packaging/integration/IsSubtreeFullyOverwrittenIT.java @@ -0,0 +1,233 @@ +/* + * 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 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.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"; + + @Before + public void setUp() throws Exception { + super.setUp(); + clean(TEST_ROOT); + } + + /** + * 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.isSubtreeFullyOverwritten(admin, root.getPath())); + } + + /** + * Path is covered by filter but node does not exist in repository. + * Expects false (nodeExists check). + */ + @Test + public void returnsFalseWhenNodeDoesNotExist() throws RepositoryException, ConfigurationException { + DefaultWorkspaceFilter filter = new DefaultWorkspaceFilter(); + PathFilterSet set = new PathFilterSet(TEST_ROOT); + set.addInclude(new DefaultPathFilter(TEST_ROOT + "(/.*)?")); + filter.add(set); + + assertFalse(filter.isSubtreeFullyOverwritten(admin, TEST_ROOT + "/nonexistent")); + } + + /** + * 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); + + JcrUtils.getOrCreateByPath(TEST_ROOT + "/node", JcrConstants.NT_UNSTRUCTURED, admin); + admin.save(); + + assertFalse(filter.isSubtreeFullyOverwritten(admin, TEST_ROOT + "/node")); + } + + /** + * 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); + + JcrUtils.getOrCreateByPath(TEST_ROOT + "/node", JcrConstants.NT_UNSTRUCTURED, admin); + admin.save(); + + assertFalse(filter.isSubtreeFullyOverwritten(admin, TEST_ROOT + "/node")); + } + + /** + * 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); + + JcrUtils.getOrCreateByPath(TEST_ROOT + "/parent", JcrConstants.NT_UNSTRUCTURED, admin); + JcrUtils.getOrCreateByPath(TEST_ROOT + "/parent/excluded", JcrConstants.NT_UNSTRUCTURED, admin); + admin.save(); + + assertFalse(filter.isSubtreeFullyOverwritten(admin, TEST_ROOT + "/parent")); + } + + /** + * 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); + + JcrUtils.getOrCreateByPath(TEST_ROOT + "/parent", JcrConstants.NT_UNSTRUCTURED, admin); + JcrUtils.getOrCreateByPath(TEST_ROOT + "/parent/child", JcrConstants.NT_UNSTRUCTURED, admin); + admin.save(); + + assertTrue(filter.isSubtreeFullyOverwritten(admin, TEST_ROOT + "/parent")); + } + + /** + * 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); + + JcrUtils.getOrCreateByPath(TEST_ROOT + "/leaf", JcrConstants.NT_UNSTRUCTURED, admin); + admin.save(); + + assertTrue(filter.isSubtreeFullyOverwritten(admin, TEST_ROOT + "/leaf")); + } + + /** + * 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(/.*)?")); + + JcrUtils.getOrCreateByPath(TEST_ROOT + "/node", JcrConstants.NT_UNSTRUCTURED, admin); + admin.save(); + + assertTrue(filter.isSubtreeFullyOverwritten(admin, TEST_ROOT + "/node")); + } + + /** + * 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.isSubtreeFullyOverwritten(admin, TEST_ROOT + "/withProp")); + } + + /** + * 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); + + JcrUtils.getOrCreateByPath(contentRoot + "/en", JcrConstants.NT_UNSTRUCTURED, admin); + JcrUtils.getOrCreateByPath(contentRoot + "/en/page", JcrConstants.NT_UNSTRUCTURED, admin); + admin.save(); + + assertFalse(filter.isSubtreeFullyOverwritten(admin, contentRoot + "/en")); + } +} 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..9c7c31d49 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 overwritten 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: + * + * 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 session the session to use for traversing the subtree + * @param path the node path to check (subtree root, must exist) + * @return {@code true} if every node and property in the subtree is included and mode is REPLACE + * @throws RepositoryException if the path does not exist or traversal fails + */ + boolean isSubtreeFullyOverwritten(@NotNull Session session, @NotNull String path) 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..17526e893 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; @@ -60,6 +62,7 @@ import org.apache.jackrabbit.vault.util.RejectingEntityResolver; import org.apache.jackrabbit.vault.util.xml.serialize.FormattingXmlStreamWriter; import org.apache.jackrabbit.vault.util.xml.serialize.OutputFormat; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -278,6 +281,46 @@ public boolean includesProperty(String propertyPath) { return false; } + @Override + public boolean isSubtreeFullyOverwritten(@NotNull Session session, @NotNull String path) + throws RepositoryException { + if (isGloballyIgnored(path)) { + return false; + } + if (getCoveringFilterSet(path) == null) { + return false; + } + if (getImportMode(path) != ImportMode.REPLACE) { + return false; + } + if (!session.nodeExists(path)) { + return false; + } + javax.jcr.Node node = session.getNode(path); + return isSubtreeFullyOverwrittenRecursive(node); + } + + 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((javax.jcr.Node) children.nextNode())) { + return false; + } + } + return true; + } + /** * {@inheritDoc} */ 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 018b6e224..b8015bfc1 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,7 +379,7 @@ public void startDocViewNode( throws IOException, RepositoryException { stack.addName(docViewNode.getSnsAwareName()); Node node = stack.getNode(); - log.debug("startDocViewNode(), nodePath= {}, node={}", nodePath, node.getPath()); + log.debug("startDocViewNode(), nodePath= {}, node={}", nodePath, node != null ? node.getPath() : null); if (node == null) { stack = stack.push(); DocViewAdapter xform = stack.getAdapter(); 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; From b16c845c360a80aea8acff6bba5d02ac6cd47871 Mon Sep 17 00:00:00 2001 From: Joerg Hoh Date: Thu, 26 Feb 2026 14:46:21 +0100 Subject: [PATCH 03/35] JCRVLT-830 integrate this new check into the importer --- .../jackrabbit/vault/fs/impl/io/DocViewImporter.java | 9 ++++++--- .../vault/fs/impl/io/FolderArtifactHandler.java | 5 ++++- 2 files changed, 10 insertions(+), 4 deletions(-) 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 b8015bfc1..2ba2a05af 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 @@ -507,9 +507,12 @@ 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)) { + // Only remove or clear when the parent's subtree is fully overwritten by the filter (JCRVLT-830) + if (!wspFilter.isSubtreeFullyOverwritten(session, node.getPath())) { + 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..90a92947d 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 @@ -158,7 +158,10 @@ public ImportInfoImpl accept( 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 (wspFilter.contains(path) + && wspFilter.getImportMode(path) == ImportMode.REPLACE + && wspFilter.isSubtreeFullyOverwritten(node.getSession(), node.getPath())) { if (!hints.contains(path)) { // if the child is in the filter, it belongs to // this aggregate and needs to be removed From 72b8e2507f03a16748fe7c55b3642caaf65b4d3c Mon Sep 17 00:00:00 2001 From: Joerg Hoh Date: Thu, 26 Feb 2026 14:50:51 +0100 Subject: [PATCH 04/35] remove unnecessary demo file --- .../integration/AggregationJCRVLT830_2.java | 226 ------------------ 1 file changed, 226 deletions(-) delete mode 100644 vault-core-it/vault-core-integration-tests/src/main/java/org/apache/jackrabbit/vault/packaging/integration/AggregationJCRVLT830_2.java diff --git a/vault-core-it/vault-core-integration-tests/src/main/java/org/apache/jackrabbit/vault/packaging/integration/AggregationJCRVLT830_2.java b/vault-core-it/vault-core-integration-tests/src/main/java/org/apache/jackrabbit/vault/packaging/integration/AggregationJCRVLT830_2.java deleted file mode 100644 index db40f1c4d..000000000 --- a/vault-core-it/vault-core-integration-tests/src/main/java/org/apache/jackrabbit/vault/packaging/integration/AggregationJCRVLT830_2.java +++ /dev/null @@ -1,226 +0,0 @@ -/* - * 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.NodeIterator; -import javax.jcr.Property; -import javax.jcr.PropertyIterator; -import javax.jcr.RepositoryException; -import javax.jcr.Value; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; - -import org.apache.jackrabbit.commons.cnd.CndImporter; -import org.apache.jackrabbit.vault.fs.api.ArtifactType; -import org.apache.jackrabbit.vault.fs.api.IdConflictPolicy; -import org.apache.jackrabbit.vault.fs.api.ImportMode; -import org.apache.jackrabbit.vault.fs.api.PathFilterSet; -import org.apache.jackrabbit.vault.fs.api.SerializationType; -import org.apache.jackrabbit.vault.fs.api.VaultInputSource; -import org.apache.jackrabbit.vault.fs.config.DefaultWorkspaceFilter; -import org.apache.jackrabbit.vault.fs.filter.DefaultPathFilter; -import org.apache.jackrabbit.vault.fs.impl.ArtifactSetImpl; -import org.apache.jackrabbit.vault.fs.impl.io.GenericArtifactHandler; -import org.apache.jackrabbit.vault.fs.impl.io.InputSourceArtifact; -import org.apache.jackrabbit.vault.fs.io.AccessControlHandling; -import org.apache.jackrabbit.vault.fs.io.ImportOptions; -import org.junit.Before; -import org.junit.Test; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; - -public class AggregationJCRVLT830_2 extends IntegrationTestBase { - - private GenericArtifactHandler handler; - DefaultWorkspaceFilter wspFilter; - private Node contentNode; - - private static final String NODETYPES = "<'cq'='http://www.day.com/jcr/cq/1.0'>\n" - + "<'sling'='http://sling.apache.org/jcr/sling/1.0'>\n" - + "<'nt'='http://www.jcp.org/jcr/nt/1.0'>\n" - + "<'rep'='internal'>\n" - + "\n" - + "[sling:OrderedFolder] > sling:Folder\n" - + " orderable\n" - + " + * (nt:base) = sling:OrderedFolder version\n" - + "\n" - + "[sling:Folder] > nt:folder\n" - + " - * (undefined) multiple\n" - + " - * (undefined)\n" - + " + * (nt:base) = sling:Folder version\n"; - - @Before - public void setup() throws Exception { - // create the necessary nodetypes - CndImporter.registerNodeTypes(new InputStreamReader(new ByteArrayInputStream(NODETYPES.getBytes())), admin); - - handler = new GenericArtifactHandler(); - handler.setAcHandling(AccessControlHandling.OVERWRITE); - - // create a filter accepting everything - wspFilter = new DefaultWorkspaceFilter(); - PathFilterSet filterSet = new PathFilterSet(); - filterSet.addInclude(new DefaultPathFilter(".*")); - wspFilter.add(filterSet); - } - - @Test - public void importContent() throws Exception { - String xml = "\n" - + "\n" - + " \n" - + ""; - - // create basic node structure /content/mysite/en/page - contentNode = admin.getRootNode().addNode("content", "nt:unstructured"); - Node mysite = contentNode.addNode("mysite", "sling:Folder"); - Node en = mysite.addNode("en", "sling:Folder"); - Node page = en.addNode("page", "nt:unstructured"); - admin.save(); - - // import the docview - importDocViewXml(xml, contentNode, "mysite"); - admin.save(); - dumpRepositoryStructure(contentNode); - - assertNodeExists("/content/mysite/en"); - assertNodeExists("/content/mysite/en/page"); - assertEquals( - "bar", admin.getNode("/content/mysite/en").getProperty("foo").toString()); - assertProperty("/content/mysite/en/foo", "bar"); - } - - /** - * Helper method to import DocView XML using GenericArtifactHandler - */ - private void importDocViewXml(String xml, Node parentNode, String nodeName) - throws RepositoryException, IOException { - ArtifactSetImpl artifacts = new ArtifactSetImpl(); - - // Create VaultInputSource from the XML string - VaultInputSource vaultSource = new VaultInputSource() { - @Override - public InputStream getByteStream() { - return new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8)); - } - - @Override - public long getContentLength() { - return xml.length(); - } - - @Override - public long getLastModified() { - return System.currentTimeMillis(); - } - }; - - // Create the artifact using InputSourceArtifact - InputSourceArtifact artifact = new InputSourceArtifact( - null, // no parent - nodeName, // name - ".xml", // extension - ArtifactType.PRIMARY, // type - vaultSource, // input source - SerializationType.XML_DOCVIEW // serialization type - ); - - artifacts.add(artifact); - - ImportOptions options = new ImportOptions(); - options.setStrict(true); - options.setIdConflictPolicy(IdConflictPolicy.FAIL); - options.setImportMode(ImportMode.REPLACE); - options.setListener(getLoggingProgressTrackerListener()); - assertNotNull(handler.accept(options, true, wspFilter, parentNode, nodeName, artifacts)); - } - - /** - * Dumps the repository structure below the provided Node (including the node itself). - * Recursively prints all nodes, their properties and values with proper indentation. - * - * @param node the node to start dumping from - * @throws RepositoryException if an error occurs accessing the repository - */ - private void dumpRepositoryStructure(Node node) throws RepositoryException { - dumpNode(node, 0); - } - - /** - * Recursively dumps a node and its descendants with indentation. - * - * @param node the node to dump - * @param depth the current depth for indentation - * @throws RepositoryException if an error occurs accessing the repository - */ - private void dumpNode(Node node, int depth) throws RepositoryException { - String indent = " ".repeat(depth); - - // Print node path and primary type - System.out.println(indent + "+ " + node.getName() + " [" - + node.getPrimaryNodeType().getName() + "]"); - - // Print properties - PropertyIterator properties = node.getProperties(); - while (properties.hasNext()) { - Property property = properties.nextProperty(); - String propertyIndent = " ".repeat(depth + 1); - - // Skip jcr:primaryType as it's already shown - if ("jcr:primaryType".equals(property.getName())) { - continue; - } - - // Handle multi-value properties - if (property.isMultiple()) { - Value[] values = property.getValues(); - if (values.length > 0) { - System.out.print(propertyIndent + "- " + property.getName() + " = ["); - for (int i = 0; i < values.length; i++) { - if (i > 0) System.out.print(", "); - System.out.print(values[i].getString()); - } - System.out.println("]"); - } else { - System.out.println(propertyIndent + "- " + property.getName() + " = []"); - } - } else { - System.out.println(propertyIndent + "- " + property.getName() + " = " + property.getString()); - } - } - - // Recursively dump child nodes - NodeIterator children = node.getNodes(); - while (children.hasNext()) { - Node child = children.nextNode(); - dumpNode(child, depth + 1); - } - } -} From 08af40cf3dde36bb2ec214bd29fd6bc7df29d42a Mon Sep 17 00:00:00 2001 From: Joerg Hoh Date: Thu, 26 Feb 2026 15:41:49 +0100 Subject: [PATCH 05/35] renamed method (PR feedback) --- .../IsSubtreeFullyOverwrittenIT.java | 20 +++++++++---------- .../vault/fs/api/WorkspaceFilter.java | 4 ++-- .../fs/config/DefaultWorkspaceFilter.java | 2 +- .../vault/fs/impl/io/DocViewImporter.java | 2 +- .../fs/impl/io/FolderArtifactHandler.java | 2 +- 5 files changed, 15 insertions(+), 15 deletions(-) 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 index 57728d2a8..e193e0600 100644 --- 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 @@ -62,7 +62,7 @@ public void returnsFalseWhenPathNotCoveredByAnyFilter() throws RepositoryExcepti Node root = JcrUtils.getOrCreateByPath(TEST_ROOT + "/content", JcrConstants.NT_UNSTRUCTURED, admin); admin.save(); - assertFalse(filter.isSubtreeFullyOverwritten(admin, root.getPath())); + assertFalse(filter.isSubtreeFullyCovered(admin, root.getPath())); } /** @@ -76,7 +76,7 @@ public void returnsFalseWhenNodeDoesNotExist() throws RepositoryException, Confi set.addInclude(new DefaultPathFilter(TEST_ROOT + "(/.*)?")); filter.add(set); - assertFalse(filter.isSubtreeFullyOverwritten(admin, TEST_ROOT + "/nonexistent")); + assertFalse(filter.isSubtreeFullyCovered(admin, TEST_ROOT + "/nonexistent")); } /** @@ -94,7 +94,7 @@ public void returnsFalseWhenImportModeIsNotReplace() throws RepositoryException, JcrUtils.getOrCreateByPath(TEST_ROOT + "/node", JcrConstants.NT_UNSTRUCTURED, admin); admin.save(); - assertFalse(filter.isSubtreeFullyOverwritten(admin, TEST_ROOT + "/node")); + assertFalse(filter.isSubtreeFullyCovered(admin, TEST_ROOT + "/node")); } /** @@ -112,7 +112,7 @@ public void returnsFalseWhenPathIsGloballyIgnored() throws RepositoryException, JcrUtils.getOrCreateByPath(TEST_ROOT + "/node", JcrConstants.NT_UNSTRUCTURED, admin); admin.save(); - assertFalse(filter.isSubtreeFullyOverwritten(admin, TEST_ROOT + "/node")); + assertFalse(filter.isSubtreeFullyCovered(admin, TEST_ROOT + "/node")); } /** @@ -131,7 +131,7 @@ public void returnsFalseWhenChildNodeIsExcludedByFilter() throws RepositoryExcep JcrUtils.getOrCreateByPath(TEST_ROOT + "/parent/excluded", JcrConstants.NT_UNSTRUCTURED, admin); admin.save(); - assertFalse(filter.isSubtreeFullyOverwritten(admin, TEST_ROOT + "/parent")); + assertFalse(filter.isSubtreeFullyCovered(admin, TEST_ROOT + "/parent")); } /** @@ -149,7 +149,7 @@ public void returnsTrueWhenSubtreeExistsAndFullyIncluded() throws RepositoryExce JcrUtils.getOrCreateByPath(TEST_ROOT + "/parent/child", JcrConstants.NT_UNSTRUCTURED, admin); admin.save(); - assertTrue(filter.isSubtreeFullyOverwritten(admin, TEST_ROOT + "/parent")); + assertTrue(filter.isSubtreeFullyCovered(admin, TEST_ROOT + "/parent")); } /** @@ -166,7 +166,7 @@ public void returnsTrueWhenLeafNodeHasNoChildren() throws RepositoryException, C JcrUtils.getOrCreateByPath(TEST_ROOT + "/leaf", JcrConstants.NT_UNSTRUCTURED, admin); admin.save(); - assertTrue(filter.isSubtreeFullyOverwritten(admin, TEST_ROOT + "/leaf")); + assertTrue(filter.isSubtreeFullyCovered(admin, TEST_ROOT + "/leaf")); } /** @@ -184,7 +184,7 @@ public void returnsTrueWhenGlobalIgnoredDoesNotMatchPath() throws RepositoryExce JcrUtils.getOrCreateByPath(TEST_ROOT + "/node", JcrConstants.NT_UNSTRUCTURED, admin); admin.save(); - assertTrue(filter.isSubtreeFullyOverwritten(admin, TEST_ROOT + "/node")); + assertTrue(filter.isSubtreeFullyCovered(admin, TEST_ROOT + "/node")); } /** @@ -205,7 +205,7 @@ public void returnsFalseWhenPropertyIsExcludedByFilter() throws RepositoryExcept node.setProperty("customProp", "value"); admin.save(); - assertFalse(filter.isSubtreeFullyOverwritten(admin, TEST_ROOT + "/withProp")); + assertFalse(filter.isSubtreeFullyCovered(admin, TEST_ROOT + "/withProp")); } /** @@ -228,6 +228,6 @@ public void jcrvlt830ReturnsFalseWhenExistingChildInRepoIsExcludedByFilter() JcrUtils.getOrCreateByPath(contentRoot + "/en/page", JcrConstants.NT_UNSTRUCTURED, admin); admin.save(); - assertFalse(filter.isSubtreeFullyOverwritten(admin, contentRoot + "/en")); + assertFalse(filter.isSubtreeFullyCovered(admin, contentRoot + "/en")); } } 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 9c7c31d49..2371c6275 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 @@ -184,7 +184,7 @@ void dumpCoverage(@NotNull Session session, @NotNull ProgressTrackerListener lis boolean includesProperty(String propertyPath); /** - * Returns whether the given path's subtree is supposed to be fully overwritten during import, + * 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: *
    @@ -202,5 +202,5 @@ void dumpCoverage(@NotNull Session session, @NotNull ProgressTrackerListener lis * @return {@code true} if every node and property in the subtree is included and mode is REPLACE * @throws RepositoryException if the path does not exist or traversal fails */ - boolean isSubtreeFullyOverwritten(@NotNull Session session, @NotNull String path) throws RepositoryException; + boolean isSubtreeFullyCovered(@NotNull Session session, @NotNull String path) throws RepositoryException; } 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 17526e893..55c004973 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 @@ -282,7 +282,7 @@ public boolean includesProperty(String propertyPath) { } @Override - public boolean isSubtreeFullyOverwritten(@NotNull Session session, @NotNull String path) + public boolean isSubtreeFullyCovered(@NotNull Session session, @NotNull String path) throws RepositoryException { if (isGloballyIgnored(path)) { return false; 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 2ba2a05af..14dd64a56 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 @@ -508,7 +508,7 @@ public void endDocViewNode( && !hints.contains(path) && isIncluded(child, child.getDepth() - rootDepth)) { // Only remove or clear when the parent's subtree is fully overwritten by the filter (JCRVLT-830) - if (!wspFilter.isSubtreeFullyOverwritten(session, node.getPath())) { + if (!wspFilter.isSubtreeFullyCovered(session, node.getPath())) { log.debug( "Skipping removal of child node {} because parent's subtree is not fully overwritten", 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 90a92947d..ee19ca216 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 @@ -161,7 +161,7 @@ public ImportInfoImpl accept( // Only remove when parent's subtree is fully overwritten (JCRVLT-830) if (wspFilter.contains(path) && wspFilter.getImportMode(path) == ImportMode.REPLACE - && wspFilter.isSubtreeFullyOverwritten(node.getSession(), node.getPath())) { + && wspFilter.isSubtreeFullyCovered(node.getSession(), node.getPath())) { if (!hints.contains(path)) { // if the child is in the filter, it belongs to // this aggregate and needs to be removed From b5275ef9bc2cc45672821c3e62b7d8712530e418 Mon Sep 17 00:00:00 2001 From: Joerg Hoh Date: Thu, 26 Feb 2026 15:46:36 +0100 Subject: [PATCH 06/35] spotless --- .../jackrabbit/vault/fs/config/DefaultWorkspaceFilter.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 55c004973..1d7cbbaaf 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 @@ -282,8 +282,7 @@ public boolean includesProperty(String propertyPath) { } @Override - public boolean isSubtreeFullyCovered(@NotNull Session session, @NotNull String path) - throws RepositoryException { + public boolean isSubtreeFullyCovered(@NotNull Session session, @NotNull String path) throws RepositoryException { if (isGloballyIgnored(path)) { return false; } From 8711844f9d3df2872b50bf6254da6c4e99039f61 Mon Sep 17 00:00:00 2001 From: Joerg Hoh Date: Fri, 27 Feb 2026 09:01:02 +0100 Subject: [PATCH 07/35] improve signature --- .../IsSubtreeFullyOverwrittenIT.java | 49 +++++++------------ .../vault/fs/api/WorkspaceFilter.java | 7 ++- .../fs/config/DefaultWorkspaceFilter.java | 13 +++-- .../vault/fs/impl/io/DocViewImporter.java | 2 +- .../fs/impl/io/FolderArtifactHandler.java | 2 +- 5 files changed, 30 insertions(+), 43 deletions(-) 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 index e193e0600..aa72c215e 100644 --- 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 @@ -41,11 +41,14 @@ 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); } /** @@ -62,21 +65,7 @@ public void returnsFalseWhenPathNotCoveredByAnyFilter() throws RepositoryExcepti Node root = JcrUtils.getOrCreateByPath(TEST_ROOT + "/content", JcrConstants.NT_UNSTRUCTURED, admin); admin.save(); - assertFalse(filter.isSubtreeFullyCovered(admin, root.getPath())); - } - - /** - * Path is covered by filter but node does not exist in repository. - * Expects false (nodeExists check). - */ - @Test - public void returnsFalseWhenNodeDoesNotExist() throws RepositoryException, ConfigurationException { - DefaultWorkspaceFilter filter = new DefaultWorkspaceFilter(); - PathFilterSet set = new PathFilterSet(TEST_ROOT); - set.addInclude(new DefaultPathFilter(TEST_ROOT + "(/.*)?")); - filter.add(set); - - assertFalse(filter.isSubtreeFullyCovered(admin, TEST_ROOT + "/nonexistent")); + assertFalse(filter.isSubtreeFullyCovered(rootNode)); } /** @@ -91,10 +80,10 @@ public void returnsFalseWhenImportModeIsNotReplace() throws RepositoryException, set.setImportMode(ImportMode.MERGE_PROPERTIES); filter.add(set); - JcrUtils.getOrCreateByPath(TEST_ROOT + "/node", JcrConstants.NT_UNSTRUCTURED, admin); + Node n = JcrUtils.getOrCreateByPath(TEST_ROOT + "/node", JcrConstants.NT_UNSTRUCTURED, admin); admin.save(); - assertFalse(filter.isSubtreeFullyCovered(admin, TEST_ROOT + "/node")); + assertFalse(filter.isSubtreeFullyCovered(n)); } /** @@ -109,10 +98,10 @@ public void returnsFalseWhenPathIsGloballyIgnored() throws RepositoryException, filter.add(set); filter.setGlobalIgnored(PathFilter.ALL); - JcrUtils.getOrCreateByPath(TEST_ROOT + "/node", JcrConstants.NT_UNSTRUCTURED, admin); + Node n = JcrUtils.getOrCreateByPath(TEST_ROOT + "/node", JcrConstants.NT_UNSTRUCTURED, admin); admin.save(); - assertFalse(filter.isSubtreeFullyCovered(admin, TEST_ROOT + "/node")); + assertFalse(filter.isSubtreeFullyCovered(n)); } /** @@ -127,11 +116,11 @@ public void returnsFalseWhenChildNodeIsExcludedByFilter() throws RepositoryExcep set.addExclude(new DefaultPathFilter(TEST_ROOT + "/parent/excluded(/.*)?")); filter.add(set); - JcrUtils.getOrCreateByPath(TEST_ROOT + "/parent", JcrConstants.NT_UNSTRUCTURED, admin); + 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(admin, TEST_ROOT + "/parent")); + assertFalse(filter.isSubtreeFullyCovered(p)); } /** @@ -145,11 +134,11 @@ public void returnsTrueWhenSubtreeExistsAndFullyIncluded() throws RepositoryExce set.addInclude(new DefaultPathFilter(TEST_ROOT + "(/.*)?")); filter.add(set); - JcrUtils.getOrCreateByPath(TEST_ROOT + "/parent", JcrConstants.NT_UNSTRUCTURED, admin); + 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(admin, TEST_ROOT + "/parent")); + assertTrue(filter.isSubtreeFullyCovered(p)); } /** @@ -163,10 +152,10 @@ public void returnsTrueWhenLeafNodeHasNoChildren() throws RepositoryException, C set.addInclude(new DefaultPathFilter(TEST_ROOT + "(/.*)?")); filter.add(set); - JcrUtils.getOrCreateByPath(TEST_ROOT + "/leaf", JcrConstants.NT_UNSTRUCTURED, admin); + Node l = JcrUtils.getOrCreateByPath(TEST_ROOT + "/leaf", JcrConstants.NT_UNSTRUCTURED, admin); admin.save(); - assertTrue(filter.isSubtreeFullyCovered(admin, TEST_ROOT + "/leaf")); + assertTrue(filter.isSubtreeFullyCovered(l)); } /** @@ -181,10 +170,10 @@ public void returnsTrueWhenGlobalIgnoredDoesNotMatchPath() throws RepositoryExce filter.add(set); filter.setGlobalIgnored(new DefaultPathFilter("/other/ignored(/.*)?")); - JcrUtils.getOrCreateByPath(TEST_ROOT + "/node", JcrConstants.NT_UNSTRUCTURED, admin); + Node n = JcrUtils.getOrCreateByPath(TEST_ROOT + "/node", JcrConstants.NT_UNSTRUCTURED, admin); admin.save(); - assertTrue(filter.isSubtreeFullyCovered(admin, TEST_ROOT + "/node")); + assertTrue(filter.isSubtreeFullyCovered(n)); } /** @@ -205,7 +194,7 @@ public void returnsFalseWhenPropertyIsExcludedByFilter() throws RepositoryExcept node.setProperty("customProp", "value"); admin.save(); - assertFalse(filter.isSubtreeFullyCovered(admin, TEST_ROOT + "/withProp")); + assertFalse(filter.isSubtreeFullyCovered(node)); } /** @@ -224,10 +213,10 @@ public void jcrvlt830ReturnsFalseWhenExistingChildInRepoIsExcludedByFilter() set.addExclude(new DefaultPathFilter(contentRoot + "/en/page(/.*)?")); filter.add(set); - JcrUtils.getOrCreateByPath(contentRoot + "/en", JcrConstants.NT_UNSTRUCTURED, admin); + Node n = JcrUtils.getOrCreateByPath(contentRoot + "/en", JcrConstants.NT_UNSTRUCTURED, admin); JcrUtils.getOrCreateByPath(contentRoot + "/en/page", JcrConstants.NT_UNSTRUCTURED, admin); admin.save(); - assertFalse(filter.isSubtreeFullyCovered(admin, contentRoot + "/en")); + assertFalse(filter.isSubtreeFullyCovered(n)); } } 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 2371c6275..e17657af9 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 @@ -196,11 +196,10 @@ void dumpCoverage(@NotNull Session session, @NotNull ProgressTrackerListener lis * 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 TODO * - * @param session the session to use for traversing the subtree - * @param path the node path to check (subtree root, must exist) - * @return {@code true} if every node and property in the subtree is included and mode is REPLACE + * @return {@code true} if every node and property on the node {subTree} and its children is included and mode is REPLACE * @throws RepositoryException if the path does not exist or traversal fails */ - boolean isSubtreeFullyCovered(@NotNull Session session, @NotNull String path) throws RepositoryException; + boolean isSubtreeFullyCovered(@NotNull Node subTree) throws RepositoryException; } 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 1d7cbbaaf..f2b8b82f9 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 @@ -62,7 +62,6 @@ import org.apache.jackrabbit.vault.util.RejectingEntityResolver; import org.apache.jackrabbit.vault.util.xml.serialize.FormattingXmlStreamWriter; import org.apache.jackrabbit.vault.util.xml.serialize.OutputFormat; -import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -282,7 +281,11 @@ public boolean includesProperty(String propertyPath) { } @Override - public boolean isSubtreeFullyCovered(@NotNull Session session, @NotNull String path) throws RepositoryException { + public boolean isSubtreeFullyCovered(javax.jcr.Node subTree) throws RepositoryException { + if (subTree == null) { + return false; + } + String path = subTree.getPath(); if (isGloballyIgnored(path)) { return false; } @@ -292,11 +295,7 @@ public boolean isSubtreeFullyCovered(@NotNull Session session, @NotNull String p if (getImportMode(path) != ImportMode.REPLACE) { return false; } - if (!session.nodeExists(path)) { - return false; - } - javax.jcr.Node node = session.getNode(path); - return isSubtreeFullyOverwrittenRecursive(node); + return isSubtreeFullyOverwrittenRecursive(subTree); } private boolean isSubtreeFullyOverwrittenRecursive(javax.jcr.Node node) throws RepositoryException { 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 14dd64a56..2588ed89d 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 @@ -508,7 +508,7 @@ public void endDocViewNode( && !hints.contains(path) && isIncluded(child, child.getDepth() - rootDepth)) { // Only remove or clear when the parent's subtree is fully overwritten by the filter (JCRVLT-830) - if (!wspFilter.isSubtreeFullyCovered(session, node.getPath())) { + if (!wspFilter.isSubtreeFullyCovered(child)) { log.debug( "Skipping removal of child node {} because parent's subtree is not fully overwritten", 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 ee19ca216..1f4949768 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 @@ -161,7 +161,7 @@ public ImportInfoImpl accept( // Only remove when parent's subtree is fully overwritten (JCRVLT-830) if (wspFilter.contains(path) && wspFilter.getImportMode(path) == ImportMode.REPLACE - && wspFilter.isSubtreeFullyCovered(node.getSession(), node.getPath())) { + && wspFilter.isSubtreeFullyCovered(child)) { if (!hints.contains(path)) { // if the child is in the filter, it belongs to // this aggregate and needs to be removed From 4b2462cd3aede3192f0e1a07f28aed87c4d6ad76 Mon Sep 17 00:00:00 2001 From: Joerg Hoh Date: Wed, 18 Mar 2026 17:59:10 +0100 Subject: [PATCH 08/35] JCRVLT-830 fix incorrect parameter --- .../org/apache/jackrabbit/vault/fs/impl/io/DocViewImporter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 2588ed89d..ed4e93aff 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 @@ -508,7 +508,7 @@ public void endDocViewNode( && !hints.contains(path) && isIncluded(child, child.getDepth() - rootDepth)) { // Only remove or clear when the parent's subtree is fully overwritten by the filter (JCRVLT-830) - if (!wspFilter.isSubtreeFullyCovered(child)) { + if (!wspFilter.isSubtreeFullyCovered(node)) { log.debug( "Skipping removal of child node {} because parent's subtree is not fully overwritten", path); From 24f55dbbf1796df11d3562dfa6f6d1f25d03798e Mon Sep 17 00:00:00 2001 From: Joerg Hoh Date: Wed, 18 Mar 2026 18:03:32 +0100 Subject: [PATCH 09/35] JCRVLT-830 make this new behavior configurable (not yet exposed) --- .../IsSubtreeFullyOverwrittenIT.java | 104 +++++++++++ .../vault-core-it-execution-oak-min/pom.xml | 175 +++++++++--------- .../fs/config/DefaultWorkspaceFilter.java | 20 ++ .../fs/impl/io/FolderArtifactHandler.java | 2 +- .../vault/fs/filter/WorkspaceFilterTest.java | 25 +++ 5 files changed, 238 insertions(+), 88 deletions(-) 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 index aa72c215e..30663455a 100644 --- 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 @@ -21,6 +21,8 @@ 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; @@ -29,6 +31,9 @@ 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; @@ -212,6 +217,7 @@ public void jcrvlt830ReturnsFalseWhenExistingChildInRepoIsExcludedByFilter() 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); @@ -219,4 +225,102 @@ public void jcrvlt830ReturnsFalseWhenExistingChildInRepoIsExcludedByFilter() 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/config/DefaultWorkspaceFilter.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/config/DefaultWorkspaceFilter.java index f2b8b82f9..5defc016d 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 @@ -108,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}, that method always returns {@code true} so importers behave as before the + * extra validation. Not persisted; external configuration will be wired in a later step. + */ + private boolean extraValidationBeforeSubtreeRemoval = true; + /** * Add a #PathFilterSet for nodes items. * @param set the set of filters to add. @@ -280,8 +287,20 @@ 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 (!extraValidationBeforeSubtreeRemoval) { + return true; + } if (subTree == null) { return false; } @@ -374,6 +393,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/impl/io/FolderArtifactHandler.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/FolderArtifactHandler.java index 1f4949768..ebb0cc709 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 @@ -161,7 +161,7 @@ public ImportInfoImpl accept( // Only remove when parent's subtree is fully overwritten (JCRVLT-830) if (wspFilter.contains(path) && wspFilter.getImportMode(path) == ImportMode.REPLACE - && wspFilter.isSubtreeFullyCovered(child)) { + && wspFilter.isSubtreeFullyCovered(node)) { 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/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..204f53ed2 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,25 @@ 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 ConfigurationException, 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)); + } } From 966b78abfd3e188d715595ecfcca5da2b93a0965 Mon Sep 17 00:00:00 2001 From: Joerg Hoh Date: Wed, 18 Mar 2026 21:28:04 +0100 Subject: [PATCH 10/35] JCRVLT-830 allow to configure the behavior via OSGI --- .../jackrabbit/vault/fs/io/Importer.java | 23 +++++++++++++ .../jackrabbit/vault/fs/io/package-info.java | 2 +- .../vault/packaging/impl/JcrPackageImpl.java | 3 +- .../packaging/impl/JcrPackageManagerImpl.java | 21 ++++++++++++ .../vault/packaging/impl/PackagingImpl.java | 12 ++++++- .../vault/packaging/impl/ZipVaultPackage.java | 33 +++++++++++++++++-- .../impl/AbstractPackageRegistry.java | 16 +++++++++ .../registry/impl/FSPackageRegistry.java | 13 +++++++- .../registry/impl/JcrPackageRegistry.java | 25 +++++++++++++- 9 files changed, 140 insertions(+), 8 deletions(-) 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/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/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/registry/impl/AbstractPackageRegistry.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/AbstractPackageRegistry.java index 3f10d057b..a749a34a8 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); + } + + public 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}; From d71a9b5b9d238fd1c81e1ba26ca483082bc652fd Mon Sep 17 00:00:00 2001 From: Joerg Hoh Date: Wed, 17 Jun 2026 13:45:46 +0200 Subject: [PATCH 11/35] JCRVLT-830 update some javadoc --- .../apache/jackrabbit/vault/fs/api/WorkspaceFilter.java | 5 +++-- .../vault/fs/config/DefaultWorkspaceFilter.java | 8 ++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) 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 e17657af9..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 @@ -196,9 +196,10 @@ void dumpCoverage(@NotNull Session session, @NotNull ProgressTrackerListener lis * 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 TODO + * @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 + * @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/config/DefaultWorkspaceFilter.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/config/DefaultWorkspaceFilter.java index 5defc016d..53cb779f3 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 @@ -110,8 +110,8 @@ public class DefaultWorkspaceFilter implements Dumpable, WorkspaceFilter { /** * When {@code true} (default), {@link #isSubtreeFullyCovered(javax.jcr.Node)} performs the full subtree check - * (JCRVLT-830). When {@code false}, that method always returns {@code true} so importers behave as before the - * extra validation. Not persisted; external configuration will be wired in a later step. + * (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; @@ -298,6 +298,10 @@ public void setExtraValidationBeforeSubtreeRemoval(boolean extraValidationBefore @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; } From 0b2a9d1f2fc9316a9a4e41a5aa90dcf2d3b51f1c Mon Sep 17 00:00:00 2001 From: Joerg Hoh Date: Wed, 17 Jun 2026 18:11:32 +0200 Subject: [PATCH 12/35] JCRVLT-830 sonar feedback --- .../jackrabbit/vault/fs/config/DefaultWorkspaceFilter.java | 2 +- .../vault/packaging/registry/impl/AbstractPackageRegistry.java | 2 +- .../apache/jackrabbit/vault/fs/filter/WorkspaceFilterTest.java | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) 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 53cb779f3..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 @@ -335,7 +335,7 @@ private boolean isSubtreeFullyOverwrittenRecursive(javax.jcr.Node node) throws R } NodeIterator children = node.getNodes(); while (children.hasNext()) { - if (!isSubtreeFullyOverwrittenRecursive((javax.jcr.Node) children.nextNode())) { + if (!isSubtreeFullyOverwrittenRecursive(children.nextNode())) { return false; } } 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 a749a34a8..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 @@ -100,7 +100,7 @@ public AbstractPackageRegistry( this(securityConfig, isStrictByDefault, overwritePrimaryTypesOfFoldersByDefault, defaultIdConflictPolicy, true); } - public AbstractPackageRegistry( + AbstractPackageRegistry( SecurityConfig securityConfig, boolean isStrictByDefault, boolean overwritePrimaryTypesOfFoldersByDefault, 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 204f53ed2..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 @@ -325,8 +325,7 @@ public void extraValidationBeforeSubtreeRemovalDisabled_shortCircuitsIsSubtreeFu } @Test - public void translatePreservesExtraValidationBeforeSubtreeRemovalFlag() - throws ConfigurationException, RepositoryException { + public void translatePreservesExtraValidationBeforeSubtreeRemovalFlag() throws RepositoryException { DefaultWorkspaceFilter filter = new DefaultWorkspaceFilter(); PathFilterSet set = new PathFilterSet("/a"); filter.add(set); From b88c7987dd842895b7cc4b46bf3d079b1ac972c4 Mon Sep 17 00:00:00 2001 From: Joerg Hoh Date: Mon, 6 Jul 2026 12:03:14 +0200 Subject: [PATCH 13/35] JCRVLT-830 reduce the invocations of subtree coverage checks --- .../vault/fs/impl/io/DocViewImporter.java | 8 ++++- .../fs/impl/io/FolderArtifactHandler.java | 15 ++++++--- .../vault/fs/impl/io/SubtreeCoverage.java | 33 +++++++++++++++++++ 3 files changed, 51 insertions(+), 5 deletions(-) create mode 100644 vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/SubtreeCoverage.java 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 ed4e93aff..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 @@ -498,6 +498,9 @@ public void endDocViewNode( 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(); @@ -507,8 +510,11 @@ public void endDocViewNode( if (!childNames.contains(label) && !hints.contains(path) && isIncluded(child, child.getDepth() - rootDepth)) { + 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 (!wspFilter.isSubtreeFullyCovered(node)) { + if (subtreeCoverage == SubtreeCoverage.NOT_FULLY_COVERED) { log.debug( "Skipping removal of child node {} because parent's subtree is not fully overwritten", 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 ebb0cc709..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,13 +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(); - // Only remove when parent's subtree is fully overwritten (JCRVLT-830) - if (wspFilter.contains(path) - && wspFilter.getImportMode(path) == ImportMode.REPLACE - && wspFilter.isSubtreeFullyCovered(node)) { + 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; + } +} From 504ee17047e25119e45ecf8b80ead5647ba1ce37 Mon Sep 17 00:00:00 2001 From: Julian Reschke Date: Mon, 23 Feb 2026 14:11:57 +0100 Subject: [PATCH 14/35] JCRVLT-842: FileVault 4.2.0 Candidate Release Notes (#415) --- RELEASE-NOTES.txt | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index d6b838d82..25b0c9db5 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 -------------------------------------- From cbef017d875ed63bfbf36c77c87bf402cfca5f4a Mon Sep 17 00:00:00 2001 From: Julian Reschke Date: Mon, 23 Feb 2026 14:52:38 +0100 Subject: [PATCH 15/35] [maven-release-plugin] prepare release jackrabbit-filevault-4.2.0 --- parent/pom.xml | 6 +++--- pom.xml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/parent/pom.xml b/parent/pom.xml index bbb95d4e6..7bca6c221 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} @@ -91,7 +91,7 @@ Apache Jackrabbit FileVault is a project of the Apache Software Foundation. - 4.1.5-SNAPSHOT + 4.2.0 2.20.17 @@ -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:39:01Z diff --git a/pom.xml b/pom.xml index faad90e71..74f2400f3 100644 --- a/pom.xml +++ b/pom.xml @@ -62,7 +62,7 @@ - 2025-10-21T10:22:56Z + 2026-02-23T13:39:01Z ${project.basedir}/target/site/jacoco-aggregate/jacoco.xml From 4c61aa9219e060a9d91f223e590cb9a49129fab0 Mon Sep 17 00:00:00 2001 From: Julian Reschke Date: Mon, 23 Feb 2026 14:52:53 +0100 Subject: [PATCH 16/35] [maven-release-plugin] prepare for next development iteration --- parent/pom.xml | 4 ++-- pom.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/parent/pom.xml b/parent/pom.xml index 7bca6c221..3934d2be4 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -91,7 +91,7 @@ Apache Jackrabbit FileVault is a project of the Apache Software Foundation. - 4.2.0 + 4.2.1-SNAPSHOT 2.20.17 @@ -112,7 +112,7 @@ Apache Jackrabbit FileVault is a project of the Apache Software Foundation.2.27.6 - 2026-02-23T13:39:01Z + 2026-02-23T13:52:50Z diff --git a/pom.xml b/pom.xml index 74f2400f3..1c025e22b 100644 --- a/pom.xml +++ b/pom.xml @@ -62,7 +62,7 @@ - 2026-02-23T13:39:01Z + 2026-02-23T13:52:50Z ${project.basedir}/target/site/jacoco-aggregate/jacoco.xml From a28b661a92c5aed9b5bc5165fc45cb56d7de7970 Mon Sep 17 00:00:00 2001 From: Konrad Windszus Date: Fri, 17 Apr 2026 21:14:25 +0200 Subject: [PATCH 17/35] trivial: Fix TOCs --- src/site/markdown/acls.md | 2 +- src/site/markdown/authorizables.md | 2 +- src/site/markdown/config.md | 2 +- src/site/markdown/docview.md | 2 +- src/site/markdown/filter.md | 2 +- src/site/markdown/importmode.md | 2 +- src/site/markdown/rcp.md | 2 +- src/site/markdown/referenceablenodes.md | 2 +- src/site/markdown/usage.md | 2 +- src/site/markdown/validation.md | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) 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..a1cdd9543 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 ----------------- 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/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..829c2f73a 100644 --- a/src/site/markdown/validation.md +++ b/src/site/markdown/validation.md @@ -17,7 +17,7 @@ --> # Validation - + ## Overview From 57fa0f6cf41f05134f418e596c360c6bd48f70aa Mon Sep 17 00:00:00 2001 From: Konrad Windszus Date: Tue, 28 Apr 2026 12:29:20 +0200 Subject: [PATCH 18/35] Remove transitive unused Maven dependencies Those suffer from vulnerabilities which otherwise lead to false positives with dependency-checker. --- vault-validation/pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) 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 + + + * + * + + From c160a7449ccf929eeed52577d0cb37344ba6ba90 Mon Sep 17 00:00:00 2001 From: Konrad Windszus Date: Tue, 28 Apr 2026 11:43:22 +0200 Subject: [PATCH 19/35] JCRVLT-844 Improve logging when loading InstallHook fails Add suppressed exceptions for every failed attempt. Log package id. --- .../jackrabbit/vault/fs/io/ZipNioArchive.java | 1 + .../impl/InstallHookProcessorImpl.java | 62 ++++++++++++------- 2 files changed, 42 insertions(+), 21 deletions(-) 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/packaging/impl/InstallHookProcessorImpl.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/InstallHookProcessorImpl.java index f73a3002b..38f0a0b39 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,9 @@ 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() != null + ? archive.getMetaInf().getPackageProperties().getId() + : null; root = root.getChild(Constants.HOOKS_DIR); if (root == null) { log.debug("Archive {} does not have a {} directory.", archive, Constants.HOOKS_DIR); @@ -93,7 +99,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 +124,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 +139,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 +184,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 +223,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 +248,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 +269,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); } } From 12c4551fa0ccd3de7eb2c490d809074138fbd4e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Santiago=20Garc=C3=ADa=20Pimentel?= Date: Fri, 22 May 2026 18:14:42 +0200 Subject: [PATCH 20/35] Explain in more detail how filters are chosen (#420) Giving an example of filters with overlapping paths Recommend against overlapping roots --- src/site/markdown/filter.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/site/markdown/filter.md b/src/site/markdown/filter.md index a1cdd9543..05b4ad4c2 100644 --- a/src/site/markdown/filter.md +++ b/src/site/markdown/filter.md @@ -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. From cd52fb7563abfcc8f220bbd4c6ef3837f4c42945 Mon Sep 17 00:00:00 2001 From: Konrad Windszus Date: Sun, 7 Jun 2026 18:46:23 +0200 Subject: [PATCH 21/35] Remove unnecessary null guard --- .../vault/packaging/impl/InstallHookProcessorImpl.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) 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 38f0a0b39..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 @@ -71,9 +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() != null - ? archive.getMetaInf().getPackageProperties().getId() - : null; + 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); From 5d75044d8fb07b96ebf6d60f40f4c0fc23cf5e9e Mon Sep 17 00:00:00 2001 From: Konrad Windszus Date: Thu, 11 Jun 2026 18:24:19 +0200 Subject: [PATCH 22/35] Update terminology for OSGi container to OSGi runtime --- src/site/markdown/installhooks.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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. From df5f63a7af64688a28d2c0b29e10d4fd54632cce Mon Sep 17 00:00:00 2001 From: Konrad Windszus Date: Fri, 15 May 2026 18:30:32 +0200 Subject: [PATCH 23/35] Build with GHA --- .asf.yaml | 2 +- .github/workflows/build.yml | 127 ++++++++++++++++++++++++++++++++++++ parent/pom.xml | 4 +- 3 files changed, 130 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/build.yml diff --git a/.asf.yaml b/.asf.yaml index 106f10cc5..4320b9081 100644 --- a/.asf.yaml +++ b/.asf.yaml @@ -14,7 +14,7 @@ github: - JCR - SLING - FELIX - dependabot_alerts: true + dependabot_alerts: false dependabot_updates: false protected_branches: master: {} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000..ebdb76cd5 --- /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_USERNAME: ${{ secrets.NEXUS_USER }} + MAVEN_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/parent/pom.xml b/parent/pom.xml index 3934d2be4..6b27ae768 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -85,8 +85,8 @@ 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 From b91573e33b7eade4f693c4f985a209c8e1b04934 Mon Sep 17 00:00:00 2001 From: Konrad Windszus Date: Fri, 12 Jun 2026 14:24:20 +0200 Subject: [PATCH 24/35] Fix environment variables used for Nexus Deployment from GHA --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ebdb76cd5..bf8b9d511 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -78,8 +78,8 @@ jobs: env: # secrets were provided in https://issues.apache.org/jira/browse/INFRA-27930? NVD_API_KEY: ${{ secrets.NIST_NVD_API_KEY }} - MAVEN_USERNAME: ${{ secrets.NEXUS_USER }} - MAVEN_PASSWORD: ${{ secrets.NEXUS_PW }} + 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 From 89235c40ce3307c62f65befd05f4dc22d61a7ca1 Mon Sep 17 00:00:00 2001 From: Konrad Windszus Date: Fri, 12 Jun 2026 14:35:55 +0200 Subject: [PATCH 25/35] Use Build Status Badge for GHA instead of Jenkins --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5c980d650..710802865 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ [![ASF Jira](https://img.shields.io/badge/ASF%20JIRA-JCRVLT-orange)](https://issues.apache.org/jira/projects/JCRVLT/summary) [![License](https://img.shields.io/badge/License-Apache%202.0-red.svg)](https://www.apache.org/licenses/LICENSE-2.0) [![Maven Central](https://img.shields.io/maven-central/v/org.apache.jackrabbit.vault/vault-cli.svg?label=Maven%20Central)](https://search.maven.org/artifact//org.apache.jackrabbit.vault/vault-cli) -[![Build Status](https://ci-builds.apache.org/buildStatus/icon?job=Jackrabbit%2Ffilevault%2Fmaster)](https://ci-builds.apache.org/job/Jackrabbit/job/filevault/job/master/) +[![Build Status](https://github.com/apache/jackrabbit-filevault/actions/workflows/build.yml/badge.svg)](https://github.com/apache/jackrabbit-filevault/actions/workflows/build.yml) [![SonarCloud Status](https://sonarcloud.io/api/project_badges/measure?project=apache_jackrabbit-filevault&metric=alert_status)](https://sonarcloud.io/summary/overall?id=apache_jackrabbit-filevault) [![SonarCloud Coverage](https://sonarcloud.io/api/project_badges/measure?project=apache_jackrabbit-filevault&metric=coverage)](https://sonarcloud.io/component_measures?metric=Coverage&view=list&id=apache_jackrabbit-filevault) [![SonarCloud Bugs](https://sonarcloud.io/api/project_badges/measure?project=apache_jackrabbit-filevault&metric=bugs)](https://sonarcloud.io/project/issues?resolved=false&types=BUG&id=apache_jackrabbit-filevault) From b21b9ed8f169f0742e70e13396f39ea879a76c43 Mon Sep 17 00:00:00 2001 From: Konrad Windszus Date: Fri, 12 Jun 2026 14:23:02 +0200 Subject: [PATCH 26/35] Update dependency-check-m-p to 12.2.2 --- parent/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parent/pom.xml b/parent/pom.xml index 6b27ae768..56e1a278c 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -514,7 +514,7 @@ Bundle-Category: jackrabbit org.owasp dependency-check-maven - 12.1.6 + 12.2.2 From ae6b77ff68ee159428fcb8756eb56a03218366e4 Mon Sep 17 00:00:00 2001 From: Konrad Windszus Date: Fri, 12 Jun 2026 14:58:50 +0200 Subject: [PATCH 27/35] Update README with prerequisites and build instructions Clarified runtime vs build prerequisites --- README.md | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 710802865..f16c817da 100644 --- a/README.md +++ b/README.md @@ -25,9 +25,23 @@ Please refer to the documentation at -Building FileVault +Prerequisites =========================================== +Runtime +------- +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 @@ -37,8 +51,7 @@ For more instructions, please see the documentation at: -Building FileVault Site -============================================ +### 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 From c6817624318ea84ea00cd45f60f9f9b849f4982c Mon Sep 17 00:00:00 2001 From: Konrad Windszus Date: Fri, 12 Jun 2026 14:52:07 +0200 Subject: [PATCH 28/35] Upgrade to Bnd 7.3.0 --- parent/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parent/pom.xml b/parent/pom.xml index 56e1a278c..0241ea967 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -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 From 0ba59b5c94d6775cc3cf839f555060c6fefabff6 Mon Sep 17 00:00:00 2001 From: Konrad Windszus Date: Thu, 18 Jun 2026 09:39:08 +0200 Subject: [PATCH 29/35] Remove unused old ASF logo --- src/site/resources/asf_logo.png | Bin 14186 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/site/resources/asf_logo.png diff --git a/src/site/resources/asf_logo.png b/src/site/resources/asf_logo.png deleted file mode 100644 index 94f91cfa4e54882b9f886f30fb2ab585e9e4e142..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14186 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 Xuf9SeZYqv{o|j3A{uHSa(hvAwpXDJS From 6da7e953bcf65a5f99d310bb1c319e465e9b8a5b Mon Sep 17 00:00:00 2001 From: Konrad Windszus Date: Thu, 18 Jun 2026 10:21:50 +0200 Subject: [PATCH 30/35] JCRVLT-847 Move spotless execution to "verify" phase This minimizes interruption of ongoing work in the IDE and with the CLI as one can easily leave that phase out (if not desired). --- parent/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parent/pom.xml b/parent/pom.xml index 0241ea967..15cd90f46 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -361,7 +361,7 @@ Apache Jackrabbit FileVault is a project of the Apache Software Foundation. ${spotless.action} - process-sources + verify From c0d73b92ee68ec1455733eeec5fcccddcb930c72 Mon Sep 17 00:00:00 2001 From: Konrad Windszus Date: Thu, 18 Jun 2026 13:03:49 +0200 Subject: [PATCH 31/35] Auto-delete branches on merge --- .asf.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.asf.yaml b/.asf.yaml index 4320b9081..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 @@ -18,3 +18,6 @@ github: 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 From fa23333898223f505686bfb8f62a9f3c0b3c1930 Mon Sep 17 00:00:00 2001 From: Konrad Windszus Date: Thu, 18 Jun 2026 13:06:22 +0200 Subject: [PATCH 32/35] Documentation: Separate Build from Runtime Prerequisites --- README.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index f16c817da..9feb8354e 100644 --- a/README.md +++ b/README.md @@ -25,14 +25,14 @@ Please refer to the documentation at -Prerequisites +Runtime Prerequisites =========================================== -Runtime -------- Java 11 or higher -### Runtime Dependencies +Runtime Dependencies +-------------------- + - Jackrabbit 2.20.17+ (JCR Commons, SPI, SPI Commons) - Oak Jackrabbit API 1.22.4+ - Commons IO 2.18.0+ @@ -40,7 +40,7 @@ Java 11 or higher - SLF4J 1.7+ Build ------- +========= You can build FileVault like this: @@ -51,7 +51,8 @@ For more instructions, please see the documentation at: -### Building FileVault Site +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 From 995f01d4c4350002601b7fbc47af390b59048297 Mon Sep 17 00:00:00 2001 From: Konrad Windszus Date: Fri, 19 Jun 2026 11:26:37 +0200 Subject: [PATCH 33/35] JCRVLT-847 Explicitly ignore spotless execution in m2e --- parent/pom.xml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/parent/pom.xml b/parent/pom.xml index 15cd90f46..8471b7285 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -357,6 +357,9 @@ Apache Jackrabbit FileVault is a project of the Apache Software Foundation. + + spotless-format ${spotless.action} @@ -530,6 +533,29 @@ Bundle-Category: jackrabbit + + + + + + + + + + + + + + + + + + + + + + + From c6c62aed88a6aedc3f29263d4c7e88626408b979 Mon Sep 17 00:00:00 2001 From: Konrad Windszus Date: Sat, 20 Jun 2026 13:11:09 +0200 Subject: [PATCH 34/35] Update jackrabbit-packagetype validation details Add link to https://issues.apache.org/jira/browse/JCRVLT-403 --- src/site/markdown/validation.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/site/markdown/validation.md b/src/site/markdown/validation.md index 829c2f73a..94efa8c17 100644 --- a/src/site/markdown/validation.md +++ b/src/site/markdown/validation.md @@ -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 From 9eceee628c3e90de675a9c183fbdce5e1e9ed7be Mon Sep 17 00:00:00 2001 From: Konrad Windszus Date: Mon, 29 Jun 2026 12:54:45 +0200 Subject: [PATCH 35/35] Add missing item to release notes for 4.0.0 (#426) Some have incorrectly been targeted for the skipped version 3.8.6. Fix formatting --- RELEASE-NOTES.txt | 44 +++++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 25b0c9db5..d812210b6 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -17,20 +17,20 @@ 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() +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 +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 +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 @@ -40,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 @@ -69,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 @@ -77,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