You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by kw...@apache.org on 2022/02/14 14:35:45 UTC

[jackrabbit-filevault] 01/01: JCRVLT-605 don't overwrite filtered protected properties during import

This is an automated email from the ASF dual-hosted git repository.

kwin pushed a commit to branch bugfix/exclude-filtered-properties-during-import
in repository https://gitbox.apache.org/repos/asf/jackrabbit-filevault.git

commit f6ef7b79f5712c7de0cf2f60a10f47fdc968f3a7
Author: Konrad Windszus <kw...@apache.org>
AuthorDate: Fri Feb 11 09:14:55 2022 +0100

    JCRVLT-605 don't overwrite filtered protected properties during import
---
 .../vault/fs/impl/io/DocViewImporter.java          | 125 +++++++++++++++------
 .../vault/fs/impl/io/DocViewSAXFormatter.java      |  27 ++---
 .../apache/jackrabbit/vault/fs/io/FileArchive.java |  11 +-
 .../jackrabbit/vault/fs/io/ZipNioArchive.java      |   6 +-
 .../jackrabbit/vault/packaging/PackageManager.java |   4 +-
 .../vault/packaging/impl/PackageManagerImpl.java   |  14 ++-
 .../apache/jackrabbit/vault/fs/io/ArchiveTest.java |   1 +
 .../integration/FilteredPropertiesIT.java          |  51 ++++++++-
 .../jcr_root/testroot3/.content.xml                |  11 ++
 9 files changed, 187 insertions(+), 63 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 88ab639..14461a6 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
@@ -84,6 +84,7 @@ import org.apache.jackrabbit.vault.util.DocViewProperty2;
 import org.apache.jackrabbit.vault.util.EffectiveNodeType;
 import org.apache.jackrabbit.vault.util.JcrConstants;
 import org.apache.jackrabbit.vault.util.MimeTypes;
+import org.apache.jackrabbit.vault.util.PathUtil;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 import org.slf4j.Logger;
@@ -110,23 +111,43 @@ public class DocViewImporter implements DocViewParserHandler {
     static final Logger log = LoggerFactory.getLogger(DocViewImporter.class);
 
     /**
-     * these properties are protected but can be set nevertheless via system view xml import
+     * these properties are protected but are set for new nodes nevertheless via system view xml import
      */
-    static final Set<Name> PROTECTED_PROPERTIES;
+    static final Set<Name> PROTECTED_PROPERTIES_CONSIDERED_FOR_NEW_NODES;
 
+    /**
+     * these properties are protected but are set for updated nodes via special JCR methods
+     */
+    static final Set<Name> PROTECTED_PROPERTIES_CONSIDERED_FOR_UPDATED_NODES;
+
+    /** 
+     * These properties are protected and never set, but they should be silently ignored.
+     * They are always exported by the exporter but never considered for the importer.
+     * (see also {@link DocViewSAXFormatter#IGNORED_PROTECTED_PROPERTIES}).
+     */
+    static final Set<Name> PROTECTED_PROPERTIES_SILENTLY_IGNORED;
+    
+    
     static {
         Set<Name> props = new HashSet<>();
         props.add(NameConstants.JCR_PRIMARYTYPE);
         props.add(NameConstants.JCR_MIXINTYPES);
         props.add(NameConstants.JCR_UUID);
+        PROTECTED_PROPERTIES_CONSIDERED_FOR_UPDATED_NODES = Collections.unmodifiableSet(props);
         props.add(NameConstants.JCR_ISCHECKEDOUT);
         props.add(NameConstants.JCR_BASEVERSION);
         props.add(NameConstants.JCR_PREDECESSORS);
         props.add(NameConstants.JCR_SUCCESSORS);
         props.add(NameConstants.JCR_VERSIONHISTORY);
-        PROTECTED_PROPERTIES = Collections.unmodifiableSet(props);
+        PROTECTED_PROPERTIES_CONSIDERED_FOR_NEW_NODES = Collections.unmodifiableSet(props);
+        
+        props = new HashSet<>();
+        props.add(NameConstants.JCR_LASTMODIFIED);
+        props.add(NameConstants.JCR_LASTMODIFIEDBY);
+        PROTECTED_PROPERTIES_SILENTLY_IGNORED = Collections.unmodifiableSet(props);
     }
 
+
     /**
      * the importing session
      */
@@ -934,7 +955,7 @@ public class DocViewImporter implements DocViewParserHandler {
             // TODO: is this faster than using sysview import?
             // set new primary type (but never set rep:root)
             String primaryType = ni.getPrimaryType().orElseThrow(() -> new IllegalStateException("Mandatory property 'jcr:primaryType' missing from " + ni));
-            if (importMode == ImportMode.REPLACE && !"rep:root".equals(primaryType)) {
+            if (importMode == ImportMode.REPLACE && !"rep:root".equals(primaryType) && wspFilter.includesProperty(node.getPath() + JcrConstants.JCR_PRIMARYTYPE)) {
                 if (!node.getPrimaryNodeType().getName().equals(primaryType)) {
                     vs.ensureCheckedOut();
                     node.setPrimaryType(primaryType);
@@ -943,37 +964,38 @@ public class DocViewImporter implements DocViewParserHandler {
             }
             // calculate mixins to be added
             Set<String> newMixins = new HashSet<>();
-            AccessControlHandling acHandling = getAcHandling(ni.getName());
-            for (String mixin : ni.getMixinTypes()) {
-                // omit name if mix:AccessControllable and CLEAR
-                if (!aclManagement.isAccessControllableMixin(mixin)
-                        || acHandling != AccessControlHandling.CLEAR) {
-                    newMixins.add(mixin);
+            if (wspFilter.includesProperty(node.getPath() + JcrConstants.JCR_MIXINTYPES)) {
+                AccessControlHandling acHandling = getAcHandling(ni.getName());
+                for (String mixin : ni.getMixinTypes()) {
+                    // omit if mix:AccessControllable and CLEAR
+                    if (!aclManagement.isAccessControllableMixin(mixin)
+                            || acHandling != AccessControlHandling.CLEAR) {
+                        newMixins.add(mixin);
+                    }
                 }
-            }
-            // remove mixins not in package (only for mode = replace)
-            if (importMode == ImportMode.REPLACE) {
-                for (NodeType mix : node.getMixinNodeTypes()) {
-                    String name = mix.getName();
-                    if (!newMixins.remove(name)) {
-                        // special check for mix:AccessControllable
-                        if (!aclManagement.isAccessControllableMixin(name)
-                                || acHandling == AccessControlHandling.CLEAR
-                                || acHandling == AccessControlHandling.OVERWRITE) {
-                            vs.ensureCheckedOut();
-                            node.removeMixin(name);
-                            updatedNode = node;
+                // remove mixins not in package (only for mode = replace)
+                if (importMode == ImportMode.REPLACE) {
+                    for (NodeType mix : node.getMixinNodeTypes()) {
+                        String name = mix.getName();
+                        if (!newMixins.remove(name)) {
+                            // special check for mix:AccessControllable
+                            if (!aclManagement.isAccessControllableMixin(name)
+                                    || acHandling == AccessControlHandling.CLEAR
+                                    || acHandling == AccessControlHandling.OVERWRITE) {
+                                vs.ensureCheckedOut();
+                                node.removeMixin(name);
+                                updatedNode = node;
+                            }
                         }
                     }
                 }
+                // add remaining mixins (for all import modes)
+                for (String mixin : newMixins) {
+                    vs.ensureCheckedOut();
+                    node.addMixin(mixin);
+                    updatedNode = node;
+                }
             }
-            // add remaining mixins (for all import modes)
-            for (String mixin : newMixins) {
-                vs.ensureCheckedOut();
-                node.addMixin(mixin);
-                updatedNode = node;
-            }
-    
             // remove unprotected properties not in package (only for mode = replace)
             if (importMode == ImportMode.REPLACE) {
                 PropertyIterator pIter = node.getProperties();
@@ -990,9 +1012,12 @@ public class DocViewImporter implements DocViewParserHandler {
                     }
                 }
             }
+            EffectiveNodeType effectiveNodeType = EffectiveNodeType.ofNode(node);
+            // logging for uncovered protected properties
+            logIgnoredProtectedProperties(effectiveNodeType, node.getPath(), ni.getProperties(), PROTECTED_PROPERTIES_CONSIDERED_FOR_UPDATED_NODES);
             
             // add/modify properties contained in package
-            if (setUnprotectedProperties(node, ni, importMode == ImportMode.REPLACE|| importMode == ImportMode.UPDATE || importMode == ImportMode.UPDATE_PROPERTIES, vs)) {
+            if (setUnprotectedProperties(effectiveNodeType, node, ni, importMode == ImportMode.REPLACE|| importMode == ImportMode.UPDATE || importMode == ImportMode.UPDATE_PROPERTIES, vs)) {
                 updatedNode = node;
             }
         }
@@ -1041,7 +1066,7 @@ public class DocViewImporter implements DocViewParserHandler {
             boolean addMixRef = false;
             
             if (ni.getIndex() > 0 && !ni.getIdentifier().isPresent()) {
-            	Collection<DocViewProperty2> preprocessedProperties = new LinkedList<>(ni.getProperties());
+                Collection<DocViewProperty2> preprocessedProperties = new LinkedList<>(ni.getProperties());
                 preprocessedProperties.add(new DocViewProperty2( NameConstants.JCR_UUID, UUID.randomUUID().toString(), PropertyType.STRING));
                 // check mixins
                 DocViewProperty2 mix = ni.getProperty(NameConstants.JCR_MIXINTYPES).orElse(null);
@@ -1066,11 +1091,14 @@ public class DocViewImporter implements DocViewParserHandler {
                 }
                 ni = ni.cloneWithDifferentProperties(preprocessedProperties);
             }
+
+            String nodePath = PathUtil.append(parentPath, npResolver.getJCRName(ni.getName()));
             // add the protected properties
             for (DocViewProperty2 p : ni.getProperties()) {
-                if (p.getStringValue().isPresent() && PROTECTED_PROPERTIES.contains(p.getName())) {
+                String qualifiedPropertyName = npResolver.getJCRName(p.getName());
+                if (p.getStringValue().isPresent() && PROTECTED_PROPERTIES_CONSIDERED_FOR_NEW_NODES.contains(p.getName()) && wspFilter.includesProperty(nodePath + "/" + qualifiedPropertyName)) {
                     attrs = new AttributesImpl();
-                    attrs.addAttribute(Name.NS_SV_URI, "name", "sv:name", ATTRIBUTE_TYPE_CDATA, npResolver.getJCRName(p.getName()));
+                    attrs.addAttribute(Name.NS_SV_URI, "name", "sv:name", ATTRIBUTE_TYPE_CDATA, qualifiedPropertyName);
                     attrs.addAttribute(Name.NS_SV_URI, "type", "sv:type", ATTRIBUTE_TYPE_CDATA, PropertyType.nameFromValue(p.getType()));
                     handler.startElement(Name.NS_SV_URI, "property", "sv:property", attrs);
                     for (String v : p.getStringValues()) {
@@ -1086,7 +1114,11 @@ public class DocViewImporter implements DocViewParserHandler {
 
             // retrieve newly created node either by uuid, label or name
             Node node = getNodeByIdOrName(parentNode, ni, importUuidBehavior == ImportUUIDBehavior.IMPORT_UUID_CREATE_NEW);
-            setUnprotectedProperties(node, ni, true, null);
+            EffectiveNodeType effectiveNodeType = EffectiveNodeType.ofNode(node);
+
+            // logging for uncovered protected properties
+            logIgnoredProtectedProperties(effectiveNodeType, node.getPath(), ni.getProperties(), PROTECTED_PROPERTIES_CONSIDERED_FOR_NEW_NODES);
+            setUnprotectedProperties(effectiveNodeType, node, ni, true, null);
             // remove mix referenceable if it was temporarily added
             if (addMixRef) {
                 node.removeMixin(JcrConstants.MIX_REFERENCEABLE);
@@ -1114,6 +1146,26 @@ public class DocViewImporter implements DocViewParserHandler {
         }
     }
 
+    private void logIgnoredProtectedProperties(EffectiveNodeType effectiveNodeType, String nodePath, Collection<DocViewProperty2> properties, Set<Name> importedProtectedProperties) {
+        // logging for uncovered protected properties
+        properties.stream()
+            .filter(p -> p.getStringValue().isPresent() 
+                    && !importedProtectedProperties.contains(p.getName()))
+            .forEach(p -> {
+                try {
+                    if (!PROTECTED_PROPERTIES_SILENTLY_IGNORED.contains(p.getName())) {
+                        if (isPropertyProtected(effectiveNodeType, p)) {
+                            log.warn("Ignore protected property '{}' on node '{}'", npResolver.getJCRName(p.getName()), nodePath);
+                        }
+                    } else {
+                        log.trace("Silently ignore protected property '{}' on node '{}'", npResolver.getJCRName(p.getName()), nodePath);
+                    }
+                } catch (RepositoryException e) {
+                    throw new IllegalStateException("Error retrieving protected status of properties", e);
+                }
+            });
+    }
+
     /**
      * Determines if a given property is protected according to the node type.
      * 
@@ -1153,7 +1205,7 @@ public class DocViewImporter implements DocViewParserHandler {
         return node;
     }
 
-    private boolean setUnprotectedProperties(@NotNull Node node, @NotNull DocViewNode2 ni, boolean overwriteExistingProperties, @Nullable VersioningState vs) throws RepositoryException {
+    private boolean setUnprotectedProperties(@NotNull EffectiveNodeType effectiveNodeType, @NotNull Node node, @NotNull DocViewNode2 ni, boolean overwriteExistingProperties, @Nullable VersioningState vs) throws RepositoryException {
         boolean isAtomicCounter = false;
         for (String mixin : ni.getMixinTypes()) {
             if ("mix:atomicCounter".equals(mixin)) {
@@ -1161,7 +1213,6 @@ public class DocViewImporter implements DocViewParserHandler {
             }
         }
 
-        EffectiveNodeType effectiveNodeType = EffectiveNodeType.ofNode(node);
         boolean modified = false;
         // add properties
         for (DocViewProperty2 prop : ni.getProperties()) {
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/DocViewSAXFormatter.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/DocViewSAXFormatter.java
index 6470b0f..b5e40a7 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/DocViewSAXFormatter.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/DocViewSAXFormatter.java
@@ -99,7 +99,7 @@ public class DocViewSAXFormatter implements AggregateWalkListener {
     protected final String jcrRoot;
 
     // used to temporarily store properties of a node
-    private final List<Property> props = new ArrayList<Property>();
+    private final List<Property> props = new ArrayList<>();
 
     /**
      * the export context
@@ -112,9 +112,18 @@ public class DocViewSAXFormatter implements AggregateWalkListener {
     private final boolean useBinaryReferences;
 
     /**
-     * internally ignored properties
+     * Names of properties which should never be contained in the doc view serialization.
      */
-    private final Set<String> ignored = new HashSet<String>();
+    private static final Set<String> IGNORED_PROTECTED_PROPERTIES;
+    static {
+        Set<String> props = new HashSet<>();
+        props.add(JcrConstants.JCR_CREATED);
+        props.add(JcrConstants.JCR_CREATED_BY);
+        props.add(JcrConstants.JCR_BASEVERSION);
+        props.add(JcrConstants.JCR_VERSIONHISTORY);
+        props.add(JcrConstants.JCR_PREDECESSORS);
+        IGNORED_PROTECTED_PROPERTIES = Collections.unmodifiableSet(props);
+    }
 
     private ItemNameComparator2 itemNameComparator;
 
@@ -184,14 +193,6 @@ public class DocViewSAXFormatter implements AggregateWalkListener {
      */
     @Override
     public void onWalkBegin(Node root) throws RepositoryException {
-        // init ignored protected properties
-        ignored.clear();
-        ignored.add(JcrConstants.JCR_CREATED);
-        ignored.add(JcrConstants.JCR_CREATED_BY);
-        ignored.add(JcrConstants.JCR_BASEVERSION);
-        ignored.add(JcrConstants.JCR_VERSIONHISTORY);
-        ignored.add(JcrConstants.JCR_PREDECESSORS);
-
         try {
             writer.writeStartDocument();
         } catch (XMLStreamException e) {
@@ -230,7 +231,7 @@ public class DocViewSAXFormatter implements AggregateWalkListener {
         String label = Text.getName(node.getPath());
         final String elemName;
         if (level == 0) {
-            // root node needs a name
+            // root node needs a special name
             elemName = jcrRoot;
         } else {
             // encode node name to make sure it's a valid xml name
@@ -289,7 +290,7 @@ public class DocViewSAXFormatter implements AggregateWalkListener {
      */
     @Override
     public void onProperty(Property prop, int level) throws RepositoryException {
-        if (ignored.contains(prop.getName()) && prop.getDefinition().isProtected()) {
+        if (IGNORED_PROTECTED_PROPERTIES.contains(prop.getName())) {
             return;
         }
 
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/FileArchive.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/FileArchive.java
index 821dd84..c41f662 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/FileArchive.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/FileArchive.java
@@ -90,7 +90,7 @@ public class FileArchive extends AbstractArchive {
      */
     @Override
     public Entry getRoot() throws IOException {
-        return new OsEntry(eRoot.getRoot());
+        return new OsEntry(eRoot.getRoot(), true);
     }
 
     /**
@@ -128,9 +128,15 @@ public class FileArchive extends AbstractArchive {
     private static class OsEntry implements Entry {
 
         private final File file;
+        private final boolean isRoot;
 
         private OsEntry(File file) {
+            this(file, false);
+        }
+
+        private OsEntry(File file, boolean isRoot) {
             this.file = file;
+            this.isRoot = isRoot;
         }
 
         /**
@@ -138,6 +144,9 @@ public class FileArchive extends AbstractArchive {
          */
         @Override
         public String getName() {
+            if (isRoot) {
+                return "";
+            }
             return file.getName();
         }
 
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 cd9d9e9..f136fd6 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
@@ -266,7 +266,11 @@ public class ZipNioArchive extends AbstractArchive {
         @Override
         @NotNull
         public String getName() {
-            String name = path.getName(path.getNameCount()-1).toString();
+            int numNames = path.getNameCount();
+            if (numNames == 0) {
+                return "";
+            }
+            String name = path.getName(numNames-1).toString();
             // strip trailing slashes (returned by Zip File System provider for directories)
             if (name.endsWith("/")) {
                 name = name.substring(0, name.length()-1);
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/PackageManager.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/PackageManager.java
index ed87954..10664ab 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/PackageManager.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/PackageManager.java
@@ -141,11 +141,11 @@ public interface PackageManager {
 
     /**
      * Re-wraps a package using the given meta information and file to
-     * store to. if file is {@code null} a temp file is generated.
+     * store to.
      *
      * @param opts export options
      * @param src source package
-     * @param file the file to write to
+     * @param file the file to write to (may be {@code null}) to create a new temp file
      * @return the newly created vault package
      *
      * @throws IOException if an I/O error occurs.
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/PackageManagerImpl.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/PackageManagerImpl.java
index 21e8102..2507bd8 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/PackageManagerImpl.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/PackageManagerImpl.java
@@ -218,11 +218,11 @@ public class PackageManagerImpl implements PackageManager {
             }
 
             final Set<String> metaInfIncludes;
-            final Set<String> metaInfExcludes;
+            final Set<String> metaInfExcludes = new HashSet<>();
             if (opts.getPostProcessor() == null) {
                 // no post processor, we keep all metadata files except the properties
                 metaInfIncludes = Collections.emptySet();
-                metaInfExcludes = Collections.singleton(Constants.META_DIR + "/" + Constants.PROPERTIES_XML);
+                metaInfExcludes.add(Constants.META_DIR + "/" + Constants.PROPERTIES_XML);
             } else {
                 
                 metaInfIncludes = new HashSet<>();
@@ -230,9 +230,11 @@ public class PackageManagerImpl implements PackageManager {
                 metaInfIncludes.add(Constants.META_DIR + "/" + Constants.NODETYPES_CND);
                 metaInfIncludes.add(Constants.META_DIR + "/" + Constants.CONFIG_XML);
                 metaInfIncludes.add(Constants.META_DIR + "/" + Constants.FILTER_XML);
-                metaInfExcludes = Collections.emptySet();
             }
 
+            if (inf.getFilter() != null) {
+                metaInfExcludes.add(Constants.META_DIR + "/" + Constants.FILTER_XML);
+            }
             try (Archive archive = src.getArchive()) {
                 archive.open(false);
                 addArchiveEntryToExporter(exporter, archive, "", archive.getRoot(), metaInfIncludes, metaInfExcludes);
@@ -246,6 +248,12 @@ public class PackageManagerImpl implements PackageManager {
                 tracker.track("A", Constants.META_DIR + "/" + Constants.PROPERTIES_XML);
             }
 
+            if (inf.getFilter() != null) {
+                // write updated filter
+                try (InputStream filterStream = inf.getFilter().getSource()) {
+                    exporter.writeFile(filterStream, Constants.META_DIR + "/" + Constants.FILTER_XML);
+                }
+            }
             if (opts.getPostProcessor() != null) {
                 opts.getPostProcessor().process(exporter);
             }
diff --git a/vault-core/src/test/java/org/apache/jackrabbit/vault/fs/io/ArchiveTest.java b/vault-core/src/test/java/org/apache/jackrabbit/vault/fs/io/ArchiveTest.java
index 3936ecf..366c807 100644
--- a/vault-core/src/test/java/org/apache/jackrabbit/vault/fs/io/ArchiveTest.java
+++ b/vault-core/src/test/java/org/apache/jackrabbit/vault/fs/io/ArchiveTest.java
@@ -145,6 +145,7 @@ public class ArchiveTest {
         archive.open(true);
         Entry root = archive.getRoot();
         assertNotNull(root);
+        assertEquals("", root.getName());
         assertTrue(root.isDirectory());
         MatcherAssert.assertThat(root.getChildren().stream().map(Entry::getName).collect(Collectors.toList()), Matchers.containsInAnyOrder("META-INF", "jcr_root"));
 
diff --git a/vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/integration/FilteredPropertiesIT.java b/vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/integration/FilteredPropertiesIT.java
index 7c9f173..614626d 100644
--- a/vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/integration/FilteredPropertiesIT.java
+++ b/vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/integration/FilteredPropertiesIT.java
@@ -17,9 +17,12 @@
 
 package org.apache.jackrabbit.vault.packaging.integration;
 
+import static org.junit.Assert.assertNotNull;
+
 import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.IOException;
+import java.nio.charset.StandardCharsets;
 import java.util.Properties;
 
 import javax.jcr.Node;
@@ -33,16 +36,25 @@ import org.apache.jackrabbit.vault.fs.config.DefaultMetaInf;
 import org.apache.jackrabbit.vault.fs.config.DefaultWorkspaceFilter;
 import org.apache.jackrabbit.vault.fs.filter.DefaultPathFilter;
 import org.apache.jackrabbit.vault.packaging.ExportOptions;
+import org.apache.jackrabbit.vault.packaging.JcrPackage;
 import org.apache.jackrabbit.vault.packaging.PackageException;
+import org.apache.jackrabbit.vault.packaging.PackageManager;
 import org.apache.jackrabbit.vault.packaging.VaultPackage;
+import org.apache.jackrabbit.vault.packaging.impl.PackageManagerImpl;
+import org.jetbrains.annotations.NotNull;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
 
 /**
  * Covers testing the filtering of properties both during export and import
  */
 public class FilteredPropertiesIT extends IntegrationTestBase {
 
+    @Rule
+    public TemporaryFolder folder= new TemporaryFolder();
+
     @Before
     public void setUp() throws Exception {
         super.setUp();
@@ -449,10 +461,35 @@ public class FilteredPropertiesIT extends IntegrationTestBase {
     }
 
     @Test
-    public void importWithDifferentFilterThanUsedForExport() {
-        // the package itself contains more properties than are supposed to be installed during import
-        
+    public void importWithExcludedPropertiesContainedInSerialization() throws IOException, PackageException, RepositoryException, ConfigurationException {
+        clean("/testroot2");
+        clean("/testroot3");
         
+        // the package itself contains more properties than are supposed to be installed during import
+        String filterSrc = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+                "<workspaceFilter version=\"1.0\">\n" +
+                "    <filter root=\"/testroot3\">\n" +
+                "        <exclude pattern=\"/testroot3/jcr:mixinTypes\" matchProperties=\"true\"/>\n" +
+                "        <exclude pattern=\"/testroot3/someUnprotectedStringProperty\" matchProperties=\"true\"/>\n" +
+                "    </filter>\n" +
+                "</workspaceFilter>";
+
+        DefaultWorkspaceFilter filter = new DefaultWorkspaceFilter();
+        filter.load(new ByteArrayInputStream(filterSrc.getBytes(StandardCharsets.UTF_8)));
+        try (VaultPackage pkg = loadVaultPackage("/test-packages/protected_properties.zip")) {
+            File tmpFile = folder.newFile();
+            try (VaultPackage pkgWithAdjustedFilter = packMgr.rewrap(createExportOptions(filter), pkg, tmpFile)) {
+                try (JcrPackage uploadedPackage = packMgr.upload(tmpFile, false, true, null)) {
+                    assertNotNull(uploadedPackage);
+                    uploadedPackage.install(getDefaultOptions());
+                }
+            }
+        }
+        assertProperty("/testroot3/someUnprotectedStringProperty2", "foo");
+        assertPropertyMissing("/testroot3/jcr:mixinTypes");
+        assertPropertyMissing("/testroot3/someUnprotectedStringProperty");
+        // some protected properties are never imported, independent of their excludes
+        // assertProperty("/testroot3/someProtectedBooleanProperty");
     }
 
     /**
@@ -478,7 +515,11 @@ public class FilteredPropertiesIT extends IntegrationTestBase {
             throws IOException, RepositoryException {
 
         File tmpFile = File.createTempFile("vaulttest", ".zip");
+        packMgr.assemble(admin, createExportOptions(filter), tmpFile).close();
+        return tmpFile;
+    }
 
+    private static @NotNull ExportOptions createExportOptions(WorkspaceFilter filter) {
         ExportOptions options = new ExportOptions();
         DefaultMetaInf meta = new DefaultMetaInf();
         meta.setFilter(filter);
@@ -489,9 +530,7 @@ public class FilteredPropertiesIT extends IntegrationTestBase {
         meta.setProperties(props);
 
         options.setMetaInf(meta);
-
-        packMgr.assemble(admin, options, tmpFile).close();
-        return tmpFile;
+        return options;
     }
 
     private void assertPropertiesExist(String rootPath, String... propNames)
diff --git a/vault-core/src/test/resources/test-packages/protected_properties.zip/jcr_root/testroot3/.content.xml b/vault-core/src/test/resources/test-packages/protected_properties.zip/jcr_root/testroot3/.content.xml
new file mode 100644
index 0000000..a7bf23a
--- /dev/null
+++ b/vault-core/src/test/resources/test-packages/protected_properties.zip/jcr_root/testroot3/.content.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:rep="internal" xmlns:my="http://jackrabbit.apache.org/filevault/testing"
+    jcr:primaryType="my:Folder"
+    jcr:mixinTypes="mix:title"
+    jcr:createdBy="myself"
+    someProtectedBooleanProperty="{Boolean}true"
+    someProtectedBooleanProperty2="{Boolean}true"
+    someUnprotectedStringProperty="{String}foo"
+    someUnprotectedStringProperty2="{String}foo"
+    >
+</jcr:root>