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 2021/05/07 11:25:11 UTC

[jackrabbit-filevault] branch bugfix/JCRVLT-255-importmodes-for-docview updated (44b8b22 -> d3d1297)

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

kwin pushed a change to branch bugfix/JCRVLT-255-importmodes-for-docview
in repository https://gitbox.apache.org/repos/asf/jackrabbit-filevault.git.


 discard 44b8b22  add new import modes, clarify existing ones.
     new d3d1297  add new import modes, clarify existing ones.

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (44b8b22)
            \
             N -- N -- N   refs/heads/bugfix/JCRVLT-255-importmodes-for-docview (d3d1297)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../apache/jackrabbit/vault/fs/api/ImportMode.java    | 19 ++++++++++++-------
 .../vault/fs/impl/io/FileArtifactHandler.java         |  6 +++---
 .../vault/fs/impl/io/FolderArtifactHandler.java       | 19 ++++++++++++++++++-
 .../vault/fs/impl/io/GenericArtifactHandler.java      | 18 ++++++++++++++++++
 .../vault/packaging/integration/ImportModeIT.java     |  4 ++--
 5 files changed, 53 insertions(+), 13 deletions(-)

[jackrabbit-filevault] 01/01: add new import modes, clarify existing ones.

Posted by kw...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

kwin pushed a commit to branch bugfix/JCRVLT-255-importmodes-for-docview
in repository https://gitbox.apache.org/repos/asf/jackrabbit-filevault.git

commit d3d12978efcf78b9eb1800afdc953c5e17f80440
Author: Konrad Windszus <kw...@apache.org>
AuthorDate: Thu Apr 22 15:34:45 2021 +0200

    add new import modes, clarify existing ones.
---
 .../apache/jackrabbit/vault/fs/api/ImportMode.java |  49 +++-
 .../jackrabbit/vault/fs/api/package-info.java      |   2 +-
 .../vault/fs/impl/io/DocViewSAXImporter.java       | 281 ++++++++++-----------
 .../vault/fs/impl/io/FileArtifactHandler.java      |  62 ++---
 .../vault/fs/impl/io/FolderArtifactHandler.java    |  35 +--
 .../vault/fs/impl/io/JcrSysViewTransformer.java    |  18 +-
 .../io/{ChildNodeStash.java => NodeStash.java}     | 111 +++++---
 .../apache/jackrabbit/vault/util/DocViewNode.java  |  13 +-
 .../vault/packaging/integration/ImportModeIT.java  |  47 +++-
 .../META-INF/vault/filter.xml                      |   2 +
 .../jcr_root/testroot/.content.xml                 |  18 +-
 11 files changed, 372 insertions(+), 266 deletions(-)

diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/api/ImportMode.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/api/ImportMode.java
index e19418b..eab8d65 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/api/ImportMode.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/api/ImportMode.java
@@ -20,6 +20,22 @@ package org.apache.jackrabbit.vault.fs.api;
 /**
  * {@code ImportMode} is used to define how importing content is applied
  * to the existing content in the repository.
+ * 
+ * <table border="1">
+ * <tr><th rowspan="2">Import Mode</th><th colspan="3">Property/Node (at a specific path)</th></tr>
+ * <tr><th>In Package</th><th>In Repository Before Installation</th><th>In Repository After Installation</th></tr>
+ * <tr><td rowspan="3">{@link #REPLACE}</td><td>non-existing</td><td>existing</td><td>removed</td></tr>
+ * <tr><td>existing</td><td>existing</td><td>replaced</td></tr>
+ * <tr><td>existing</td><td>non-existing</td><td>created</td></tr>
+ * <tr><td rowspan="3">{@link #MERGE_PROPERTIES}</td><td>non-existing</td><td>existing</td><td>not touched</td></tr>
+ * <tr><td>existing</td><td>existing</td><td>not touched</td></tr>
+ * <tr><td>existing</td><td>non-existing</td><td>created</td></tr>
+ * <tr><td rowspan="3">{@link #UPDATE_PROPERTIES}</td><td>non-existing</td><td>existing</td><td>not touched</td></tr>
+ * <tr><td>existing</td><td>existing</td><td>replaced</td></tr>
+ * <tr><td>existing</td><td>non-existing</td><td>created</td></tr>
+ * </table>
+ * 
+ * TODO: Handling of {code oak:incrementalCounter}.
  */
 public enum ImportMode {
 
@@ -31,12 +47,39 @@ public enum ImportMode {
 
     /**
      * Existing content is not modified, i.e. only new content is added and
-     * none is deleted or modified.
+     * none is deleted or modified
+     * <p>
+     * <strong>Only considered for 
+     * <ul>
+     * <li>Binaries, i.e. they will never be imported if the parent node has this import mode.</li>
+     * <li>Authorizable nodes: only {@code rep:members} of existing authorizables is updated, no other property.</li>
+     * <li>Simple files: i.e. they will never be imported in case the repo has this file already.
+     * <li>Other docview files: It will ignore them in case the docview's root node does already exist in the repo (both full coverage and .content.xml). It skips non-existing child nodes/properties in the docview as well.</li>
+     * </ul>
+     *  <strong></p>
+     * 
+     * @deprecated As this behaves inconsistently for the different serialization formats, rather use {@link #MERGE_PROPERTIES}.
      */
+    @Deprecated()
     MERGE,
 
     /**
-     * Existing content is updated, new content is added and none is deleted.
+     * Existing properties are replaced (except for {@code jcr:primaryType}), new properties and nodes are added and no existing properties or nodes are deleted. 
+     * <strong>Only affects authorizable nodes (not their child nodes). Other nodes are imported in mode {@link #REPLACE}.</strong>
+     * @deprecated rather use {@link #UPDATE_PROPERTIES}
      */
-    UPDATE
+    @Deprecated()
+    UPDATE,
+
+    /**
+     * Existing properties are not touched, new nodes/properties are added, no existing nodes/properties are deleted
+     * The only existing properties potentially touched is the multi value property {@code jcr:mixinType}.
+     */
+    MERGE_PROPERTIES,
+
+    /**
+     * Existing properties are replaced, new nodes/properties are added, no existing nodes/properties are deleted
+     * Existing multi-value properties are replaced and not extended except for {@code jcr:mixinType} which is extended.
+     */
+    UPDATE_PROPERTIES
 }
\ No newline at end of file
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 3995610..38c4265 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.8.0")
+@Version("2.9.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/impl/io/DocViewSAXImporter.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/DocViewSAXImporter.java
index 383ff90..b3d31ae 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/DocViewSAXImporter.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/DocViewSAXImporter.java
@@ -43,7 +43,12 @@ import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 import javax.jcr.Value;
 import javax.jcr.ValueFactory;
+import javax.jcr.ValueFormatException;
+import javax.jcr.lock.LockException;
+import javax.jcr.nodetype.ConstraintViolationException;
+import javax.jcr.nodetype.NoSuchNodeTypeException;
 import javax.jcr.nodetype.NodeType;
+import javax.jcr.version.VersionException;
 
 import org.apache.jackrabbit.spi.Name;
 import org.apache.jackrabbit.spi.commons.conversion.DefaultNamePathResolver;
@@ -71,6 +76,7 @@ import org.apache.jackrabbit.vault.util.MimeTypes;
 import org.apache.jackrabbit.vault.util.RejectingEntityDefaultHandler;
 import org.apache.jackrabbit.util.Text;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.xml.sax.Attributes;
@@ -642,19 +648,21 @@ public class DocViewSAXImporter extends RejectingEntityDefaultHandler implements
                     log.trace("Skipping ignored element {}", name);
                 }
             } else {
+                final String path = (!node.getPath().equals("/") ? node.getPath() : "")+ "/" + name;
                 if (attributes.getLength() == 0) {
                     // only ordering node. skip
-                    log.trace("Skipping empty node {}", node.getPath() + "/" + name);
+                    log.trace("Skipping empty node {}", path);
                     stack = stack.push();
                 } else if (snsNode) {
                     // skip SNS nodes with index > 1
-                    log.warn("Skipping unsupported SNS node with index > 1. Some content will be missing after import: {}", node.getPath() + "/" + label);
+                    log.warn("Skipping unsupported SNS node with index > 1. Some content will be missing after import: {}", path);
                     stack = stack.push();
                 } else {
                     try {
-                        AccessControlHandling acHandling = getAcHandling(label);
                         DocViewNode ni = new DocViewNode(name, label, attributes, npResolver);
+                        // is policy node?
                         if (aclManagement.isACLNodeType(ni.primary)) {
+                            AccessControlHandling acHandling = getAcHandling(label);
                             if (acHandling != AccessControlHandling.CLEAR && acHandling != AccessControlHandling.IGNORE) {
                                 log.trace("Access control policy element detected. starting special transformation {}/{}", node.getPath(), name);
                                 if (aclManagement.ensureAccessControllable(node, ni.primary)) {
@@ -676,18 +684,15 @@ public class DocViewSAXImporter extends RejectingEntityDefaultHandler implements
                                 stack = stack.push();
                             }
                         } else if (userManagement != null && userManagement.isAuthorizableNodeType(ni.primary)) {
+                            // is authorizable node?
                             handleAuthorizable(node, ni);
                         } else {
+                            // regular node
                             stack = stack.push(addNode(ni));
                         }
                     } catch (RepositoryException e) {
-                        String errPath = node.getPath();
-                        if (errPath.length() > 1) {
-                            errPath += "/";
-                        }
-                        errPath += name;
-                        log.error("Error during processing of {}: {}", errPath, e.toString());
-                        importInfo.onError(errPath, e);
+                        log.error("Error during processing of {}: {}", path, e.toString());
+                        importInfo.onError(path, e);
                         stack = stack.push();
                     }
                 }
@@ -718,7 +723,7 @@ public class DocViewSAXImporter extends RejectingEntityDefaultHandler implements
             // just import the authorizable node
             log.trace("Authorizable element detected. starting sysview transformation {}", newPath);
             stack = stack.push();
-            stack.adapter = new JcrSysViewTransformer(node);
+            stack.adapter = new JcrSysViewTransformer(node, wspFilter.getImportMode(newPath));
             stack.adapter.startNode(ni);
             importInfo.onCreated(newPath);
             return;
@@ -761,7 +766,7 @@ public class DocViewSAXImporter extends RejectingEntityDefaultHandler implements
                 // just replace the entire subtree for now.
                 log.trace("Authorizable element detected. starting sysview transformation {}", newPath);
                 stack = stack.push();
-                stack.adapter = new JcrSysViewTransformer(node);
+                stack.adapter = new JcrSysViewTransformer(node, mode);
                 stack.adapter.startNode(ni);
                 importInfo.onReplaced(newPath);
                 break;
@@ -769,7 +774,7 @@ public class DocViewSAXImporter extends RejectingEntityDefaultHandler implements
             case UPDATE:
                 log.trace("Authorizable element detected. starting sysview transformation {}", newPath);
                 stack = stack.push();
-                stack.adapter = new JcrSysViewTransformer(node, oldPath);
+                stack.adapter = new JcrSysViewTransformer(node, oldPath, mode);
                 // we need to tweak the ni.name so that the sysview import does not
                 // rename the authorizable node name
                 String newName = Text.getName(oldPath);
@@ -847,12 +852,15 @@ public class DocViewSAXImporter extends RejectingEntityDefaultHandler implements
             oldNode = null;
         }
 
+        // TODO: under which condition do we end up here?
         if (oldNode != null) {
             // check versionable
             new VersioningState(stack, oldNode).ensureCheckedOut();
 
-            ChildNodeStash recovery = new ChildNodeStash(session);
-            recovery.stashChildren(oldNode);
+            String oldNodePath = oldNode.getPath();
+            ImportMode importMode = wspFilter.getImportMode(oldNodePath);
+            NodeStash recovery = new NodeStash(session, oldNodePath, importMode);
+            recovery.stash();
 
             // ensure that existing binaries are not sourced from a property
             // that is about to be removed
@@ -865,10 +873,11 @@ public class DocViewSAXImporter extends RejectingEntityDefaultHandler implements
 
             oldNode.remove();
             // now create the new node
-            node = createNode(currentNode, ni);
+            node = createNewNode(currentNode, ni);
 
+            
             // move the children back
-            recovery.recoverChildren(node, importInfo);
+            recovery.recover(importInfo);
 
             importInfo.onReplaced(node.getPath());
             return new StackElement(node, false);
@@ -879,8 +888,8 @@ public class DocViewSAXImporter extends RejectingEntityDefaultHandler implements
         boolean isCheckedIn = coProp != null && "false".equals(coProp.values[0]);
 
         // create or update node
-        boolean isNew = false;
-        if (node == null) {
+        boolean isNew = node == null;
+        if (isNew) {
             // workaround for bug in jcr2spi if mixins are empty
             if (!ni.props.containsKey(JcrConstants.JCR_MIXINTYPES)) {
                 ni.props.put(JcrConstants.JCR_MIXINTYPES,
@@ -888,7 +897,7 @@ public class DocViewSAXImporter extends RejectingEntityDefaultHandler implements
             }
 
             stack.ensureCheckedOut();
-            node = createNode(currentNode, ni);
+            node = createNewNode(currentNode, ni);
             if (node.isNodeType(JcrConstants.NT_RESOURCE)) {
                 if (!node.hasProperty(JcrConstants.JCR_DATA)) {
                     importInfo.onMissing(node.getPath() + "/" + JcrConstants.JCR_DATA);
@@ -898,53 +907,56 @@ public class DocViewSAXImporter extends RejectingEntityDefaultHandler implements
                 importInfo.registerToVersion(node.getPath());
             }
             importInfo.onCreated(node.getPath());
-            isNew = true;
 
         } else if (isIncluded(node, node.getDepth() - rootDepth)) {
-            boolean modified = false;
-
             if (isCheckedIn) {
                 // don't rely on isVersionable here, since SPI might not have this info yet
                 importInfo.registerToVersion(node.getPath());
             }
-            VersioningState vs = new VersioningState(stack, node);
-
-            // set new primary type (but never set rep:root)
-            if (!"rep:root".equals(ni.primary)) {
-                if (!node.getPrimaryNodeType().getName().equals(ni.primary)) {
-                    vs.ensureCheckedOut();
-                    node.setPrimaryType(ni.primary);
-                    modified = true;
+            ImportMode importMode = wspFilter.getImportMode(node.getPath());
+            if (updateExistingNode(node, ni, importMode)) {
+                if (node.isNodeType(JcrConstants.NT_RESOURCE)) {
+                    if (!node.hasProperty(JcrConstants.JCR_DATA)) {
+                        importInfo.onMissing(node.getPath() + "/" + JcrConstants.JCR_DATA);
+                    }
                 }
+                importInfo.onModified(node.getPath());
+            } else {
+                importInfo.onNop(node.getPath());
             }
+        } else {
+            // remove registered binaries outside of the filter (JCR-126)
+            binaries.remove(node.getPath());
+        }
+        return new StackElement(node, isNew);
+    }
 
-            // remove the 'system' properties from the set
-            ni.props.remove(JcrConstants.JCR_PRIMARYTYPE);
-            ni.props.remove(JcrConstants.JCR_MIXINTYPES);
-            ni.props.remove(JcrConstants.JCR_UUID);
-            ni.props.remove(JcrConstants.JCR_BASEVERSION);
-            ni.props.remove(JcrConstants.JCR_PREDECESSORS);
-            ni.props.remove(JcrConstants.JCR_SUCCESSORS);
-            ni.props.remove(JcrConstants.JCR_VERSIONHISTORY);
-
-            // adjust mixins
-            Set<String> newMixins = new HashSet<String>();
-            boolean isAtomicCounter = false;
-            AccessControlHandling acHandling = getAcHandling(ni.name);
-            if (ni.mixins != null) {
-                for (String mixin : ni.mixins) {
-                    // omit name if mix:AccessControllable and CLEAR
-                    if (!aclManagement.isAccessControllableMixin(mixin)
-                            || acHandling != AccessControlHandling.CLEAR) {
-                        newMixins.add(mixin);
-
-                        if ("mix:atomicCounter".equals(mixin)) {
-                            isAtomicCounter = true;
-                        }
-                    }
+    private boolean updateExistingNode(@NotNull Node node, @NotNull DocViewNode ni, @NotNull ImportMode importMode) throws RepositoryException {
+        VersioningState vs = new VersioningState(stack, node);
+        boolean modified = false;
+        // set new primary type (but never set rep:root)
+        if (importMode == ImportMode.REPLACE && !"rep:root".equals(ni.primary)) {
+            if (!node.getPrimaryNodeType().getName().equals(ni.primary)) {
+                vs.ensureCheckedOut();
+                node.setPrimaryType(ni.primary);
+                modified = true;
+            }
+        }
+
+        // calculate mixins
+        Set<String> newMixins = new HashSet<>();
+        AccessControlHandling acHandling = getAcHandling(ni.name);
+        if (ni.mixins != null) {
+            for (String mixin : ni.mixins) {
+                // omit name if mix:AccessControllable and CLEAR
+                if (!aclManagement.isAccessControllableMixin(mixin)
+                        || acHandling != AccessControlHandling.CLEAR) {
+                    newMixins.add(mixin);
                 }
             }
-            // remove mixin not in package
+        }
+        // remove mixin not in package
+        if (importMode == ImportMode.REPLACE) {
             for (NodeType mix : node.getMixinNodeTypes()) {
                 String name = mix.getName();
                 if (!newMixins.remove(name)) {
@@ -958,15 +970,17 @@ public class DocViewSAXImporter extends RejectingEntityDefaultHandler implements
                     }
                 }
             }
+        }
+        
+        // add remaining mixins (for all import modes)
+        for (String mixin : newMixins) {
+            vs.ensureCheckedOut();
+            node.addMixin(mixin);
+            modified = true;
+        }
 
-            // add remaining mixins
-            for (String mixin : newMixins) {
-                vs.ensureCheckedOut();
-                node.addMixin(mixin);
-                modified = true;
-            }
-
-            // remove properties not in the set
+        // remove properties not in package
+        if (importMode == ImportMode.REPLACE) {
             PropertyIterator pIter = node.getProperties();
             while (pIter.hasNext()) {
                 Property p = pIter.nextProperty();
@@ -984,56 +998,19 @@ public class DocViewSAXImporter extends RejectingEntityDefaultHandler implements
                     }
                 }
             }
-            // add properties
-            for (DocViewProperty prop : ni.props.values()) {
-                if (prop != null && !PROTECTED_PROPERTIES.contains(prop.name)) {
-                    try {
-                        modified |= prop.apply(node);
-                    } catch (RepositoryException e) {
-                        try {
-                            // try again with checked out node
-                            vs.ensureCheckedOut();
-                            modified |= prop.apply(node);
-                        } catch (RepositoryException e1) {
-                            log.warn("Error while setting property (ignore): " + e1);
-                        }
-                    }
-                }
-            }
-            // adjust oak atomic counter
-            if (isAtomicCounter) {
-                long previous = 0;
-                if (node.hasProperty("oak:counter")) {
-                    previous = node.getProperty("oak:counter").getLong();
-                }
-                long counter = 0;
-                try {
-                    counter = Long.valueOf(ni.getValue("oak:counter"));
-                } catch (NumberFormatException e) {
-                    // ignore
-                }
-                node.setProperty("oak:increment", counter - previous);
-                modified = true;
-            }
-
-            if (modified) {
-                if (node.isNodeType(JcrConstants.NT_RESOURCE)) {
-                    if (!node.hasProperty(JcrConstants.JCR_DATA)) {
-                        importInfo.onMissing(node.getPath() + "/" + JcrConstants.JCR_DATA);
-                    }
-                }
-                importInfo.onModified(node.getPath());
-            } else {
-                importInfo.onNop(node.getPath());
-            }
-        } else {
-            // remove registered binaries outside of the filter (JCR-126)
-            binaries.remove(node.getPath());
         }
-        return new StackElement(node, isNew);
+        // add properties
+        modified |= setUnprotectedProperties(node, ni, importMode == ImportMode.REPLACE|| importMode == ImportMode.UPDATE || importMode == ImportMode.UPDATE_PROPERTIES, vs);
+        return modified;
     }
-
-    private Node createNode(Node currentNode, DocViewNode ni)
+    /**
+     * Creates a new node via system view XML and {@link Session#importXML(String, InputStream, int)} to be able to set protected properties as well
+     * @param currentNode
+     * @param ni
+     * @return
+     * @throws RepositoryException
+     */
+    private @NotNull Node createNewNode(Node currentNode, DocViewNode ni)
             throws RepositoryException {
         try {
             String parentPath = currentNode.getPath();
@@ -1049,7 +1026,7 @@ public class DocViewSAXImporter extends RejectingEntityDefaultHandler implements
             AttributesImpl attrs = new AttributesImpl();
             attrs.addAttribute(Name.NS_SV_URI, "name", "sv:name", "CDATA", ni.name);
             handler.startElement(Name.NS_SV_URI, "node", "sv:node", attrs);
-
+    
             // check if SNS and a helper uuid if needed
             boolean addMixRef = false;
             if (!ni.label.equals(ni.name) && ni.uuid == null) {
@@ -1098,7 +1075,7 @@ public class DocViewSAXImporter extends RejectingEntityDefaultHandler implements
             }
             handler.endElement(Name.NS_SV_URI, "node", "sv:node");
             handler.endDocument();
-
+    
             // retrieve newly created node either by uuid, label or name
             Node node = null;
             if (ni.uuid != null) {
@@ -1123,39 +1100,13 @@ public class DocViewSAXImporter extends RejectingEntityDefaultHandler implements
                     throw e;
                 }
             }
-
-            // handle non protected properties
-            for (DocViewProperty p : ni.props.values()) {
-                if (p != null && p.values != null) {
-                    if (!PROTECTED_PROPERTIES.contains(p.name)) {
-                        try {
-                            p.apply(node);
-                        } catch (RepositoryException e) {
-                            log.warn("Error while setting property (ignore): " + e);
-                        }
-                    }
-                }
-            }
-
-            // check for atomic counter
-            if (ni.mixins != null) {
-                for (String mixin : ni.mixins) {
-                    if ("mix:atomicCounter".equals(mixin)) {
-                        String counter = ni.getValue("oak:counter");
-                        if (counter != null) {
-                            node.setProperty("oak:increment", counter, PropertyType.LONG);
-                        }
-                        break;
-                    }
-                }
-            }
-
+            setUnprotectedProperties(node, ni, true, null);
             // remove mix referenceable if it was temporarily added
             if (addMixRef) {
                 node.removeMixin(JcrConstants.MIX_REFERENCEABLE);
             }
             return node;
-
+    
         } catch (SAXException e) {
             Exception root = e.getException();
             if (root instanceof RepositoryException) {
@@ -1168,6 +1119,52 @@ public class DocViewSAXImporter extends RejectingEntityDefaultHandler implements
         }
     }
 
+    private boolean setUnprotectedProperties(@NotNull Node node, @NotNull DocViewNode ni, boolean overwriteExistingProperties, @Nullable VersioningState vs) throws RepositoryException {
+        boolean isAtomicCounter = false;
+        for (String mixin : ni.mixins) {
+            if ("mix:atomicCounter".equals(mixin)) {
+                isAtomicCounter = true;
+            }
+        }
+        boolean modified = false;
+        // add properties
+        for (DocViewProperty prop : ni.props.values()) {
+            if (prop != null && !PROTECTED_PROPERTIES.contains(prop.name) && (overwriteExistingProperties || !node.hasProperty(prop.name))) {
+                try {
+                    modified |= prop.apply(node);
+                } catch (RepositoryException e) {
+                    if (vs == null) {
+                        throw e;
+                    }
+                    try {
+                        // try again with checked out node
+                        vs.ensureCheckedOut();
+                        modified |= prop.apply(node);
+                    } catch (RepositoryException e1) {
+                        log.warn("Error while setting property (ignore): " + e1);
+                    }
+                }
+            }
+        }
+
+        // adjust oak atomic counter
+        if (isAtomicCounter) {
+            long previous = 0;
+            if (node.hasProperty("oak:counter")) {
+                previous = node.getProperty("oak:counter").getLong();
+            }
+            long counter = 0;
+            try {
+                counter = Long.valueOf(ni.getValue("oak:counter"));
+            } catch (NumberFormatException e) {
+                // ignore
+            }
+            node.setProperty("oak:increment", counter - previous);
+            modified = true;
+        }
+        return modified;
+    }
+
     /**
      * {@inheritDoc}
      */
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/FileArtifactHandler.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/FileArtifactHandler.java
index 516b08d..7ef37bd 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/FileArtifactHandler.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/FileArtifactHandler.java
@@ -32,6 +32,7 @@ import javax.xml.parsers.ParserConfigurationException;
 import javax.xml.parsers.SAXParser;
 import javax.xml.parsers.SAXParserFactory;
 
+import org.apache.jackrabbit.util.Text;
 import org.apache.jackrabbit.vault.fs.api.Artifact;
 import org.apache.jackrabbit.vault.fs.api.ArtifactType;
 import org.apache.jackrabbit.vault.fs.api.ImportArtifact;
@@ -43,14 +44,13 @@ import org.apache.jackrabbit.vault.fs.api.WorkspaceFilter;
 import org.apache.jackrabbit.vault.fs.impl.ArtifactSetImpl;
 import org.apache.jackrabbit.vault.util.JcrConstants;
 import org.apache.jackrabbit.vault.util.MimeTypes;
-import org.apache.jackrabbit.vault.util.PathUtil;
-import org.apache.jackrabbit.util.Text;
 import org.xml.sax.InputSource;
 import org.xml.sax.SAXException;
 
 /**
  * Creates nt:file structures from  {@link SerializationType#XML_GENERIC} or
- * {@link SerializationType#GENERIC} artifacts.
+ * {@link SerializationType#GENERIC} artifacts as well complex node structure for full-coverage artifacts 
+ * with {@link SerializationType#XML_DOCVIEW}.
  *
  */
 public class FileArtifactHandler extends AbstractArtifactHandler  {
@@ -146,20 +146,9 @@ public class FileArtifactHandler extends AbstractArtifactHandler  {
                 if (info == null) {
                     info = new ImportInfoImpl();
                 }
-                // check import mode
-                ImportMode mode = ImportMode.REPLACE;
-                String path = PathUtil.getPath(parent, primary.getRelativePath());
-                if (primary.getRelativePath().length() == 0 || parent.hasNode(primary.getRelativePath())) {
-                    mode = wspFilter.getImportMode(path);
-                }
-                // only update if not MERGE (i.e. is REPLACE or UPDATE)
-                if (mode != ImportMode.MERGE) {
-                    InputSource source = primary.getInputSource();
-                    if (source != null) {
-                        info.merge(importDocView(parent, source, artifacts, wspFilter));
-                    }
-                } else {
-                    info.onNop(path);
+                InputSource source = primary.getInputSource();
+                if (source != null) {
+                    info.merge(importDocView(parent, source, artifacts, wspFilter));
                 }
             }
             // handle files
@@ -216,31 +205,20 @@ public class FileArtifactHandler extends AbstractArtifactHandler  {
                     }
                     ArtifactSetImpl newSet = new ArtifactSetImpl();
                     newSet.setCoverage(ItemFilterSet.INCLUDE_ALL);
-
-                    // check import mode
-                    ImportMode mode = ImportMode.REPLACE;
-                    String path = PathUtil.getPath(newParent, newName);
-                    if (newName.length() == 0 || newParent.hasNode(newName)) {
-                        mode = wspFilter.getImportMode(path);
-                    }
-                    if (mode != ImportMode.MERGE) {
-                        try {
-                            DocViewSAXImporter handler = new DocViewSAXImporter(newParent, newName, newSet, wspFilter);
-                            handler.setAclHandling(getAcHandling());
-                            handler.setCugHandling(getCugHandling());
-                            SAXParserFactory factory = SAXParserFactory.newInstance();
-                            factory.setNamespaceAware(true);
-                            factory.setFeature("http://xml.org/sax/features/namespace-prefixes", false);
-                            SAXParser parser = factory.newSAXParser();
-                            parser.parse(file.getInputSource(), handler);
-                            info.merge(handler.getInfo());
-                        } catch (ParserConfigurationException e) {
-                            throw new RepositoryException(e);
-                        } catch (SAXException e) {
-                            throw new RepositoryException(e);
-                        }
-                    } else {
-                        info.onNop(path);
+                    try {
+                        DocViewSAXImporter handler = new DocViewSAXImporter(newParent, newName, newSet, wspFilter);
+                        handler.setAclHandling(getAcHandling());
+                        handler.setCugHandling(getCugHandling());
+                        SAXParserFactory factory = SAXParserFactory.newInstance();
+                        factory.setNamespaceAware(true);
+                        factory.setFeature("http://xml.org/sax/features/namespace-prefixes", false);
+                        SAXParser parser = factory.newSAXParser();
+                        parser.parse(file.getInputSource(), handler);
+                        info.merge(handler.getInfo());
+                    } catch (ParserConfigurationException e) {
+                        throw new RepositoryException(e);
+                    } catch (SAXException e) {
+                        throw new RepositoryException(e);
                     }
                 } else {
                     throw new IllegalArgumentException("Files of type " + file.getSerializationType() + " can't be handled by this handler " + this);
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 1e637e7..9dd27d0 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
@@ -23,6 +23,8 @@ import java.util.Set;
 
 import javax.jcr.Node;
 import javax.jcr.NodeIterator;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
 import javax.jcr.RepositoryException;
 
 import org.apache.jackrabbit.vault.fs.api.Artifact;
@@ -104,7 +106,7 @@ public class FolderArtifactHandler extends AbstractArtifactHandler {
 
             Node node = parent.getNode(dir.getRelativePath());
             if (wspFilter.contains(node.getPath()) && !nodeType.equals(node.getPrimaryNodeType().getName())) {
-                node = modifyPrimaryType(node, info);
+                modifyPrimaryType(node, info);
             }
             NodeIterator iter = node.getNodes();
             while (iter.hasNext()) {
@@ -138,23 +140,26 @@ public class FolderArtifactHandler extends AbstractArtifactHandler {
         return info;
     }
 
-    private Node modifyPrimaryType(Node node, ImportInfoImpl info) throws RepositoryException {
-        String name = node.getName();
-        Node parent = node.getParent();
-
+    /**
+     * This is potentially a destructive operation as it will remove all (non-protected) properties before doing the conversion
+     * @param node
+     * @param info
+     * @throws RepositoryException
+     */
+    private void modifyPrimaryType(Node node, ImportInfoImpl info) throws RepositoryException {
         // check versionable
         ensureCheckedOut(node, info);
 
-        ChildNodeStash recovery = new ChildNodeStash(node.getSession());
-        recovery.stashChildren(node);
-        node.remove();
-        
-        // now create the new node
-        Node newNode = parent.addNode(name, nodeType);
-        info.onReplaced(newNode.getPath());
-        // move the children back
-        recovery.recoverChildren(newNode, info);
-        return newNode;
+        // remove all non-allowed properties
+        PropertyIterator propertyIterator = node.getProperties();
+        while (propertyIterator.hasNext()) {
+            Property property = propertyIterator.nextProperty();
+            if (!property.getDefinition().isProtected()) {
+                property.remove();
+            }
+        }
+        node.setPrimaryType(nodeType);
+       
     }
 
     private void ensureCheckedOut(Node node, ImportInfoImpl info) throws RepositoryException {
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/JcrSysViewTransformer.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/JcrSysViewTransformer.java
index cee7bd9..0fce44e 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/JcrSysViewTransformer.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/JcrSysViewTransformer.java
@@ -20,7 +20,6 @@ import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
-import java.util.Stack;
 
 import javax.jcr.ImportUUIDBehavior;
 import javax.jcr.Node;
@@ -30,8 +29,11 @@ import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 
 import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.vault.fs.api.ImportMode;
 import org.apache.jackrabbit.vault.util.DocViewNode;
 import org.apache.jackrabbit.vault.util.DocViewProperty;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.xml.sax.ContentHandler;
@@ -59,7 +61,7 @@ public class JcrSysViewTransformer implements DocViewAdapter {
     /**
      * temporary recovery helper when 'rescuing' the child nodes
      */
-    private ChildNodeStash recovery;
+    private NodeStash recovery;
 
     private String rootName;
 
@@ -71,11 +73,11 @@ public class JcrSysViewTransformer implements DocViewAdapter {
 
     private long ignoreLevel = 0;
 
-    public JcrSysViewTransformer(Node node) throws SAXException, RepositoryException {
-        this(node, null);
+    public JcrSysViewTransformer(@NotNull Node node, @NotNull ImportMode importMode) throws SAXException, RepositoryException {
+        this(node, null, importMode);
     }
 
-    JcrSysViewTransformer(Node node, String existingPath) throws RepositoryException, SAXException {
+    JcrSysViewTransformer(@NotNull Node node, @Nullable String existingPath, @NotNull ImportMode importMode) throws RepositoryException, SAXException {
         Session session = node.getSession();
         parent = node;
         handler = session.getImportContentHandler(
@@ -94,8 +96,8 @@ public class JcrSysViewTransformer implements DocViewAdapter {
         this.existingPath = existingPath;
         if (existingPath != null) {
             // check if there is an existing node with the name
-            recovery = new ChildNodeStash(session).excludeName("rep:cache");
-            recovery.stashChildren(existingPath);
+            recovery = new NodeStash(session, existingPath, importMode).excludeName("rep:cache");
+            recovery.stash();
         }
         excludeNode("rep:cache");
     }
@@ -118,7 +120,7 @@ public class JcrSysViewTransformer implements DocViewAdapter {
         // check for rescued child nodes
         if (recovery != null) {
             try {
-                recovery.recoverChildren(existingPath);
+                recovery.recover(null);
             } catch (RepositoryException e) {
                 log.error("Error while processing rescued child nodes");
             } finally {
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/ChildNodeStash.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/NodeStash.java
similarity index 56%
rename from vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/ChildNodeStash.java
rename to vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/NodeStash.java
index bf19c69..b44c7c0 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/ChildNodeStash.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/NodeStash.java
@@ -21,28 +21,37 @@ import java.util.Set;
 
 import javax.jcr.Node;
 import javax.jcr.NodeIterator;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 
 import org.apache.jackrabbit.vault.fs.api.ImportInfo;
+import org.apache.jackrabbit.vault.fs.api.ImportMode;
 import org.apache.jackrabbit.vault.util.JcrConstants;
+import org.jetbrains.annotations.Nullable;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
- * Helper class isolating the task of temporarily moving child nodes to a
+ * Helper class isolating the task of temporarily moving child nodes and properties to a
  * different location in order to be able to recover (and properly merge) them
  * later on.
+ * This is useful when sysview xml is about to be imported, as that clears everything not explicitly mentioned
  */
-public class ChildNodeStash {
+public class NodeStash {
 
-    static final Logger log = LoggerFactory.getLogger(ChildNodeStash.class);
+    static final Logger log = LoggerFactory.getLogger(NodeStash.class);
 
     private final Session session;
 
+    private final String path;
+
+    private final ImportMode importMode;
+
     private Node tmpNode;
 
-    private final Set<String> excludedNodeName = new HashSet<String>();
+    private final Set<String> excludedNodeName = new HashSet<>();
 
     /**
      * List of potential roots where the transient temporary node will be created.
@@ -52,11 +61,13 @@ public class ChildNodeStash {
 
 
     /**
-     * Creates a new child node stash utility class
+     * Creates a new stash utility class which takes care of child nodes and properties in {@code path}
      * @param session session to operate on
      */
-    public ChildNodeStash(Session session) {
+    public NodeStash(Session session, String path, ImportMode importMode) {
         this.session = session;
+        this.path = path;
+        this.importMode  = importMode;
     }
 
     /**
@@ -85,64 +96,61 @@ public class ChildNodeStash {
      * @param name The name of the node to exclude
      * @return "this" suitable for chaining.
      */
-    public ChildNodeStash excludeName(String name) {
+    public NodeStash excludeName(String name) {
         excludedNodeName.add(name);
         return this;
     }
 
     /**
-     * Moves the nodes below the given parent path to a temporary location.
-     * @param parentPath the path of the parent node.
-     * @throws RepositoryException if an error occurrs
-     */
-    public void stashChildren(String parentPath) throws RepositoryException {
-        stashChildren(session.getNode(parentPath));
-    }
-
-    /**
-     * Moves the nodes below the given parent to a temporary location.
-     * @param parent the parent node.
+     * Moves the child nodes and optionally properties of the path to a temporary location.
      */
-    public void stashChildren(Node parent) {
+    public void stash() {
+        if (importMode == ImportMode.REPLACE) {
+            return;
+        }
         try {
-            NodeIterator iter = parent.getNodes();
-            while (iter.hasNext()) {
-                Node child = iter.nextNode();
+            Node parent = session.getNode(path);
+            Node tmp = getOrCreateTemporaryNode();
+            NodeIterator nodeIterator = parent.getNodes();
+            while (nodeIterator.hasNext()) {
+                Node child = nodeIterator.nextNode();
                 String name = child.getName();
                 if (excludedNodeName.contains(name)) {
                     log.debug("skipping excluded child node from stash: {}", child.getPath());
                     continue;
                 }
-                Node tmp = getOrCreateTemporaryNode();
                 try {
                     session.move(child.getPath(), tmp.getPath() + "/" + name);
                 } catch (RepositoryException e) {
                     log.error("Error while moving child node to temporary location. Child will be removed.", e);
                 }
             }
+            // save properties
+            PropertyIterator propIterator = parent.getProperties();
+            while (propIterator.hasNext()) {
+                Property property = propIterator.nextProperty();
+                if (!property.getDefinition().isProtected()) {
+                    if (property.isMultiple()) {
+                        tmp.setProperty(property.getName(), property.getValues(), property.getType());
+                    } else {
+                        tmp.setProperty(property.getName(), property.getValue(), property.getType());
+                    }
+                }
+            }
         } catch (RepositoryException e) {
             log.warn("error while moving child nodes (ignored)", e);
         }
     }
 
     /**
-     * Moves the stashed nodes back below the given parent path.
-     * @param parentPath the path of the new parent node
-     * @throws RepositoryException if an error occurrs
-     */
-    public void recoverChildren(String parentPath) throws RepositoryException {
-        recoverChildren(session.getNode(parentPath), null);
-    }
-
-    /**
-     * Moves the stashed nodes back below the given parent path.
-     * @param parent the new parent node
+     * Moves the stashed nodes/properties back below the original path.
      * @param importInfo the import info to record the changes
-     * @throws RepositoryException if an error occurrs
+     * @throws RepositoryException if an error occurs
      */
-    public void recoverChildren(Node parent, ImportInfo importInfo) throws RepositoryException {
+    public void recover(@Nullable ImportInfo importInfo) throws RepositoryException {
         // move the old child nodes back
         if (tmpNode != null) {
+            Node parent = session.getNode(path);
             NodeIterator iter = tmpNode.getNodes();
             boolean hasErrors = false;
             while (iter.hasNext()) {
@@ -156,16 +164,45 @@ public class ChildNodeStash {
                     }
                 } catch (RepositoryException e) {
                     log.warn("Unable to move child back to new location at {} due to: {}. Node will remain in temporary location: {}",
-                            new Object[]{newPath, e.getMessage(), child.getPath()});
+                            newPath, e.getMessage(), child.getPath());
                     if (importInfo != null) {
                         importInfo.onError(newPath, e);
                         hasErrors = true;
                     }
                 }
             }
+            try {
+                recoverProperties(importMode==ImportMode.MERGE || importMode == ImportMode.MERGE_PROPERTIES);
+            } catch (RepositoryException e) {
+                log.warn("Unable to restore properties at {} due to: {}. Properties will remain in temporary location: {}",
+                        path, e.getMessage(), tmpNode.getPath());
+                if (importInfo != null) {
+                    importInfo.onError(path, e);
+                    hasErrors = true;
+                }
+            }
             if (!hasErrors) {
                 tmpNode.remove();
             }
         }
     }
+    
+    private void recoverProperties(boolean overwriteNewOnes) throws RepositoryException {
+        PropertyIterator propIterator = tmpNode.getProperties();
+        Node destNode = session.getNode(path);
+        while (propIterator.hasNext()) {
+            Property property = propIterator.nextProperty();
+            if (!property.getDefinition().isProtected()) {
+                if (!overwriteNewOnes && destNode.hasProperty(property.getName())) {
+                    log.debug("Skipping restore property {} as it has been updated", property.getPath());
+                    continue;
+                }
+                if (property.isMultiple()) {
+                    destNode.setProperty(property.getName(), property.getValues(), property.getType());
+                } else {
+                    destNode.setProperty(property.getName(), property.getValue(), property.getType());
+                }
+            }
+        }
+    }
 }
\ No newline at end of file
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/util/DocViewNode.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/util/DocViewNode.java
index fa2adb0..3da5eb9 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/util/DocViewNode.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/util/DocViewNode.java
@@ -27,6 +27,7 @@ import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
 import org.apache.jackrabbit.spi.commons.name.NameConstants;
 import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl;
 import org.apache.jackrabbit.util.ISO9075;
+import org.jetbrains.annotations.NotNull;
 import org.xml.sax.Attributes;
 
 /**
@@ -37,16 +38,20 @@ public class DocViewNode {
 
     public final String name;
     public final String label;
-    public final Map<String, DocViewProperty> props = new HashMap<String, DocViewProperty>();
+    public final @NotNull Map<String, DocViewProperty> props = new HashMap<String, DocViewProperty>();
     public String uuid;
-    public final String[] mixins;
+    public final @NotNull String[] mixins;
     public final String primary;
 
     public DocViewNode(String name, String label, String uuid, Map<String, DocViewProperty> props, String[] mixins, String primary) {
         this.name = name;
         this.label = label;
         this.uuid = uuid;
-        this.mixins = mixins;
+        if (mixins == null) {
+            this.mixins = new String[0];
+        } else {
+            this.mixins = mixins;
+        }
         this.primary = primary;
         this.props.putAll(props);
     }
@@ -79,7 +84,7 @@ public class DocViewNode {
             }
         }
         this.uuid = uuid;
-        this.mixins = mixins;
+        this.mixins = (mixins != null) ? mixins : new String[0];
         this.primary = primary;
     }
 
diff --git a/vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/integration/ImportModeIT.java b/vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/integration/ImportModeIT.java
index 0a8f59f..f3fe7e0 100644
--- a/vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/integration/ImportModeIT.java
+++ b/vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/integration/ImportModeIT.java
@@ -24,6 +24,7 @@ import javax.jcr.RepositoryException;
 
 import org.apache.jackrabbit.vault.packaging.JcrPackage;
 import org.apache.jackrabbit.vault.packaging.PackageException;
+import org.junit.Ignore;
 import org.junit.Test;
 
 /**
@@ -104,9 +105,14 @@ public class ImportModeIT extends IntegrationTestBase {
         setUpNode(parent, "replace");
         setUpNode(parent, "merge");
         setUpNode(parent, "update");
+        setUpNode(parent, "merge_properties");
+        setUpNode(parent, "update_properties");
         admin.save();
-        assertProperty("/testroot/merge/propertyold", "old");
-        assertProperty("/testroot/update/propertyold", "old");
+        
+        assertProperty("/testroot/replace/propertyold", "old");
+        assertProperty("/testroot/replace/propertyupdate", "old");
+        assertNodeExists("/testroot/replace/old");
+        assertProperty("/testroot/replace/existing/propertyold", "old");
         
         extractVaultPackage("/test-packages/import_modes_test_a.zip");
         
@@ -116,28 +122,47 @@ public class ImportModeIT extends IntegrationTestBase {
         assertProperty("/testroot/replace/propertyupdate", "new");
         assertProperty("/testroot/replace/propertynew", "new");
         assertPropertyMissing("/testroot/replace/propertyold");
-        assertProperty("/testroot/replace/update/propertynew", "new");
-        assertPropertyMissing("/testroot/replace/update/propertyold");
+        assertProperty("/testroot/replace/existing/propertynew", "new");
+        assertPropertyMissing("/testroot/replace/existing/propertyold");
         assertNodeExists("/testroot/replace/new");
         assertNodeMissing("/testroot/replace/old");
         
+        /*
         // Update (neither delete existing nodes nor properties)
         assertProperty("/testroot/update/propertyupdate", "new");
         assertProperty("/testroot/update/propertynew", "new");
         assertProperty("/testroot/update/propertyold", "old");
-        assertProperty("/testroot/update/update/propertynew", "new");
-        assertProperty("/testroot/update/update/propertyold", "old");
+        assertProperty("/testroot/update/existing/propertynew", "new");
+        assertProperty("/testroot/update/existing/propertyold", "old");
         assertNodeExists("/testroot/update/new");
         assertNodeExists("/testroot/update/old");
         
         // Merge (don't touch existing nodes, except for adding new children)
         assertProperty("/testroot/merge/propertyupdate", "old");
-        assertPropertyMissing("/testroot/merge/propertynew");
+        assertProperty("/testroot/merge/propertynew", "new");
         assertProperty("/testroot/merge/propertyold", "old");
-        assertPropertyMissing("/testroot/merge/update/propertynew");
-        assertProperty("/testroot/merge/update/propertyold", "old");
+        assertProperty("/testroot/merge/existing/propertynew", "new");
+        assertProperty("/testroot/merge/existing/propertyold", "old");
         assertNodeMissing("/testroot/merge/new");
         assertNodeExists("/testroot/merge/old");
+        */
+        // Property Update (neither delete existing nodes nor properties, but update them and add new properties/nodes)
+        assertProperty("/testroot/update_properties/propertyupdate", "new");
+        assertProperty("/testroot/update_properties/propertynew", "new");
+        assertProperty("/testroot/update_properties/propertyold", "old");
+        assertProperty("/testroot/update_properties/existing/propertynew", "new");
+        assertProperty("/testroot/update_properties/existing/propertyold", "old");
+        assertNodeExists("/testroot/update_properties/new");
+        assertNodeExists("/testroot/update_properties/old");
+        
+        // Property Merge (don't touch existing nodes nor properties, only add new properties/nodes)
+        assertProperty("/testroot/merge_properties/propertyupdate", "old");
+        assertProperty("/testroot/merge_properties/propertynew", "new");
+        assertProperty("/testroot/merge_properties/propertyold", "old");
+        assertProperty("/testroot/merge_properties/existing/propertynew", "new");
+        assertProperty("/testroot/merge_properties/existing/propertyold", "old");
+        assertNodeExists("/testroot/merge_properties/new");
+        assertNodeExists("/testroot/merge_properties/old");
     }
 
     private void setUpNode(Node parent, String name) throws RepositoryException {
@@ -145,8 +170,8 @@ public class ImportModeIT extends IntegrationTestBase {
         node.setProperty("propertyold", "old");
         node.setProperty("propertyupdate", "old");
         node.addNode("old");
-        Node update = node.addNode("update");
-        update.setProperty("propertyold", "old");
+        Node existing = node.addNode("existing");
+        existing.setProperty("propertyold", "old");
     }
 
 }
\ No newline at end of file
diff --git a/vault-core/src/test/resources/test-packages/import_modes_test_a.zip/META-INF/vault/filter.xml b/vault-core/src/test/resources/test-packages/import_modes_test_a.zip/META-INF/vault/filter.xml
index bcd93e6..572c30b 100644
--- a/vault-core/src/test/resources/test-packages/import_modes_test_a.zip/META-INF/vault/filter.xml
+++ b/vault-core/src/test/resources/test-packages/import_modes_test_a.zip/META-INF/vault/filter.xml
@@ -3,4 +3,6 @@
     <filter root="/testroot/replace" mode="replace"/>
     <filter root="/testroot/merge" mode="merge"/>
     <filter root="/testroot/update" mode="update"/>
+    <filter root="/testroot/merge_properties" mode="merge_properties"/>
+    <filter root="/testroot/update_properties" mode="update_properties"/>
 </workspaceFilter>
diff --git a/vault-core/src/test/resources/test-packages/import_modes_test_a.zip/jcr_root/testroot/.content.xml b/vault-core/src/test/resources/test-packages/import_modes_test_a.zip/jcr_root/testroot/.content.xml
index aea4bc7..da0fa1c 100644
--- a/vault-core/src/test/resources/test-packages/import_modes_test_a.zip/jcr_root/testroot/.content.xml
+++ b/vault-core/src/test/resources/test-packages/import_modes_test_a.zip/jcr_root/testroot/.content.xml
@@ -4,19 +4,31 @@
     <replace jcr:primaryType="sling:Folder"
         propertyupdate="new"
         propertynew="new">
-        <update jcr:primaryType="sling:Folder" propertynew="new" />
+        <existing jcr:primaryType="sling:Folder" propertynew="new" />
         <new jcr:primaryType="sling:Folder" />
     </replace>
     <merge jcr:primaryType="sling:Folder"
         propertyupdate="new"
         propertynew="new">
-        <update jcr:primaryType="sling:Folder" propertynew="new" />
+        <existing jcr:primaryType="sling:Folder" propertynew="new" />
         <new jcr:primaryType="sling:Folder" />
     </merge>
     <update jcr:primaryType="sling:Folder"
         propertyupdate="new"
         propertynew="new">
-        <update jcr:primaryType="sling:Folder" propertynew="new"/>
+        <existing jcr:primaryType="sling:Folder" propertynew="new"/>
         <new jcr:primaryType="sling:Folder" />
     </update>
+    <merge_properties jcr:primaryType="sling:Folder"
+        propertyupdate="new"
+        propertynew="new">
+        <existing jcr:primaryType="sling:Folder" propertynew="new" />
+        <new jcr:primaryType="sling:Folder" />
+    </merge_properties>
+    <update_properties jcr:primaryType="sling:Folder"
+        propertyupdate="new"
+        propertynew="new">
+        <existing jcr:primaryType="sling:Folder" propertynew="new"/>
+        <new jcr:primaryType="sling:Folder" />
+    </update_properties>
 </jcr:root>