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/06/16 20:35:48 UTC

[jackrabbit-filevault] branch master updated: JCRVLT-527 deferred validation of child nodes (#148)

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

kwin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/jackrabbit-filevault.git


The following commit(s) were added to refs/heads/master by this push:
     new fdae7d4  JCRVLT-527 deferred validation of child nodes (#148)
fdae7d4 is described below

commit fdae7d4a5eb71e48e912f629199c2df77bca584c
Author: Konrad Windszus <kw...@apache.org>
AuthorDate: Wed Jun 16 22:35:43 2021 +0200

    JCRVLT-527 deferred validation of child nodes (#148)
---
 vault-validation/pom.xml                           |   5 -
 .../spi/impl/nodetype/JcrNodeTypeMetaData.java     |   9 +-
 .../spi/impl/nodetype/JcrNodeTypeMetaDataImpl.java | 136 ++++++++-------
 .../spi/impl/nodetype/NodeTypeValidator.java       |  33 ++--
 .../impl/nodetype/JcrNodeTypeMetaDataImplTest.java | 182 ++++++++++-----------
 .../spi/impl/nodetype/NodeTypeValidatorTest.java   |  61 +++++--
 6 files changed, 235 insertions(+), 191 deletions(-)

diff --git a/vault-validation/pom.xml b/vault-validation/pom.xml
index ab6f608..195aa16 100644
--- a/vault-validation/pom.xml
+++ b/vault-validation/pom.xml
@@ -91,11 +91,6 @@
         </dependency>
 
         <dependency>
-            <groupId>org.apache.commons</groupId>
-            <artifactId>commons-lang3</artifactId>
-        </dependency>
-
-        <dependency>
             <groupId>org.apache.jackrabbit</groupId>
             <artifactId>jackrabbit-spi-commons</artifactId>
         </dependency>
diff --git a/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/impl/nodetype/JcrNodeTypeMetaData.java b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/impl/nodetype/JcrNodeTypeMetaData.java
index ea41212..d30e8e4 100644
--- a/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/impl/nodetype/JcrNodeTypeMetaData.java
+++ b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/impl/nodetype/JcrNodeTypeMetaData.java
@@ -42,7 +42,7 @@ import org.jetbrains.annotations.Nullable;
 
 public interface JcrNodeTypeMetaData {
 
-    void addProperty(@NotNull NodeContext nodeContext, @NotNull NamePathResolver namePathResolver, @NotNull EffectiveNodeTypeProvider effectiveNodeTypeProvider,
+    Collection<ValidationMessage> addProperty(@NotNull NodeContext nodeContext, @NotNull NamePathResolver namePathResolver, @NotNull EffectiveNodeTypeProvider effectiveNodeTypeProvider,
             @NotNull NodeTypeDefinitionProvider nodeTypeDefinitionProvider, @NotNull ItemDefinitionProvider itemDefinitionProvider,
             @NotNull ValidationMessageSeverity severity, String name, boolean isMultiValue, Value... values) throws RepositoryException;
     @NotNull JcrNodeTypeMetaData addImplicitChildNode(@NotNull NameResolver nameResolver,
@@ -53,17 +53,16 @@ public interface JcrNodeTypeMetaData {
             @NotNull NodeTypeDefinitionProvider nodeTypeDefinitionProvider, @NotNull ItemDefinitionProvider itemDefinitionProvider,
             @NotNull ValidationMessageSeverity severity, @NotNull NodeContext nodeContext, @NotNull String primaryType, String... mixinTypes)
                     throws RepositoryException, NamespaceExceptionInNodeName;
-    @NotNull JcrNodeTypeMetaData addUnknownChildNode(@NotNull NameResolver nameResolver, @NotNull String name) throws IllegalNameException, NamespaceException;
+    @NotNull JcrNodeTypeMetaData addUnknownChildNode(@NotNull NameResolver nameResolver, @NotNull NodeContext nodeContext, @NotNull String name) throws IllegalNameException, NamespaceException;
     
     // navigate
     @NotNull Collection<@NotNull ? extends JcrNodeTypeMetaData> getChildren();
     Optional<JcrNodeTypeMetaData> getNode(NamePathResolver nameResolver, String path)
             throws ItemNotFoundException, RepositoryException;
-    @NotNull JcrNodeTypeMetaData getOrCreateNode(NamePathResolver nameResolver, String path) throws RepositoryException;
+    @NotNull JcrNodeTypeMetaData getOrCreateNode(NamePathResolver nameResolver, @NotNull NodeContext nodeContext, String path) throws RepositoryException;
     
     @NotNull Collection<ValidationMessage> finalizeValidation(@NotNull NamePathResolver nameResolver,
-            @NotNull ValidationMessageSeverity severity, @NotNull WorkspaceFilter filter) throws NamespaceException;
-    void fetchAndClearValidationMessages(Collection<ValidationMessage> messages);
+            @NotNull NodeTypeDefinitionProvider nodeTypeDefinitionProvider, @NotNull ItemDefinitionProvider itemDefinitionProvider, @NotNull ValidationMessageSeverity severity, @NotNull WorkspaceFilter filter) throws NamespaceException;
     @NotNull Name getPrimaryNodeType();
     String getQualifiedPath(NamePathResolver resolver) throws NamespaceException;
     void setNodeTypes(@NotNull NameResolver nameResolver, @NotNull EffectiveNodeTypeProvider effectiveNodeTypeProvider,
diff --git a/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/impl/nodetype/JcrNodeTypeMetaDataImpl.java b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/impl/nodetype/JcrNodeTypeMetaDataImpl.java
index 144bc43..e62d5ca 100644
--- a/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/impl/nodetype/JcrNodeTypeMetaDataImpl.java
+++ b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/impl/nodetype/JcrNodeTypeMetaDataImpl.java
@@ -82,10 +82,10 @@ public class JcrNodeTypeMetaDataImpl implements JcrNodeTypeMetaData {
     static final String MESSAGE_CHILD_NODE_NOT_ALLOWED = "Node '%s [%s]' is not allowed as child of node with types [%s]: %s";
     static final String MESSAGE_PROPERTY_OF_NOT_CONTAINED_PARENT_POTENTIALLY_NOT_ALLOWED = "Property '%s' [%s] is not allowed in node with potential default types [%s]: %s";;
     static final String MESSAGE_PROPERTY_NOT_ALLOWED = "Property '%s' [%s] is not allowed in node with types [%s]: %s";
-    static final String MESSAGE_MANDATORY_CHILD_NODE_MISSING = "Mandatory child node missing: %s inside node with types [%s] at %s";
-    static final String MESSAGE_MANDATORY_UNCONTAINED_CHILD_NODE_MISSING = "Mandatory child node missing: %s inside node with types [%s] at %s (outside of filter rules)";
-    static final String MESSAGE_MANDATORY_PROPERTY_MISSING = "Mandatory property '%s' missing in node with types [%s] at %s";
-    static final String MESSAGE_MANDATORY_PROPERTY_WITH_WRONG_TYPE = "Mandatory property '%s' has type '%s' while it should have '%s' in node with types [%s] at %s";
+    static final String MESSAGE_MANDATORY_CHILD_NODE_MISSING = "Mandatory child node missing: %s inside node with types [%s]";
+    static final String MESSAGE_MANDATORY_UNCONTAINED_CHILD_NODE_MISSING = "Mandatory child node missing: %s inside node with types [%s] (outside of filter rules)";
+    static final String MESSAGE_MANDATORY_PROPERTY_MISSING = "Mandatory property '%s' missing in node with types [%s]";
+    static final String MESSAGE_MANDATORY_PROPERTY_WITH_WRONG_TYPE = "Mandatory property '%s' has type '%s' while it should have '%s' in node with types [%s]";
 
     // do not validate protected JCR system properties that are handled by FileVault specially in https://github.com/apache/jackrabbit-filevault/blob/f785fcb24d4cbd01c734e9273310a925c29ae15b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/DocViewSAXImporter.java#L123 and 
     // https://github.com/apache/jackrabbit-filevault/blob/f785fcb24d4cbd01c734e9273310a925c29ae15b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/DocViewSAXImporter.java#L140
@@ -100,28 +100,28 @@ public class JcrNodeTypeMetaDataImpl implements JcrNodeTypeMetaData {
     private static final QValueFactory QVALUE_FACTORY = QValueFactoryImpl.getInstance();
 
     private final @NotNull Name name;
+    private final @NotNull NodeContext context;
     private @Nullable Name primaryNodeType; // the effectiveNodeType does not remember which one was the primary one!
     private @Nullable EffectiveNodeType effectiveNodeType;
     private final @NotNull Map<Name, Integer> propertyTypesByName;
     private final @NotNull Map<Name, JcrNodeTypeMetaDataImpl> childNodesByName;
     private final @Nullable JcrNodeTypeMetaDataImpl parentNode;
-    private final Collection<ValidationMessage> messages;
     private boolean isAuthenticationOrAuthorizationContext;
     private final boolean isImplicit; // if this is true, the node type is set implicitly (not explicitly set in package, used as is in the
                                       // repository)
     private boolean isValidationDone;
     private final boolean isIncremental;
 
-    private JcrNodeTypeMetaDataImpl(boolean isIncremental, @NotNull Name name, @Nullable Name primaryNodeType, @Nullable EffectiveNodeType effectiveNodeType,
+    private JcrNodeTypeMetaDataImpl(boolean isIncremental, @NotNull NodeContext context, @NotNull Name name, @Nullable Name primaryNodeType, @Nullable EffectiveNodeType effectiveNodeType,
             JcrNodeTypeMetaDataImpl parentNode, boolean isAuthenticationOrAuthorizationContext, boolean isImplicit) {
         super();
+        this.context = context;
         this.name = name; // fully namespaced (taking into account local namespace declaration for Docview XML)
         this.primaryNodeType = primaryNodeType;
         this.effectiveNodeType = effectiveNodeType;
         this.parentNode = parentNode;
         this.propertyTypesByName = new HashMap<>();
         this.childNodesByName = new HashMap<>();
-        this.messages = new ArrayList<>();
         this.isAuthenticationOrAuthorizationContext = isAuthenticationOrAuthorizationContext;
         this.isImplicit = isImplicit;
         this.isValidationDone = false;
@@ -135,7 +135,6 @@ public class JcrNodeTypeMetaDataImpl implements JcrNodeTypeMetaData {
                 + "propertyTypesByName=" + propertyTypesByName + ", "
                 + "childNodes=" + childNodesByName.keySet() + ", "
                 // + "parentNode path="+(parentNode != null ? + parentNode.getPath() + ", " : "")
-                + (messages != null ? "messages=" + messages + ", " : "")
                 + "isAuthenticationOrAuthorizationContext=" + isAuthenticationOrAuthorizationContext + "]";
     }
 
@@ -213,7 +212,7 @@ public class JcrNodeTypeMetaDataImpl implements JcrNodeTypeMetaData {
             @NotNull NodeTypeDefinitionProvider nodeTypeDefinitionProvider, @NotNull ItemDefinitionProvider itemDefinitionProvider,
             @NotNull NodeContext nodeContext, @Nullable Name implicitNodeType) throws RepositoryException {
         JcrNodeTypeMetaDataImpl childNode = addChildNode(nameResolver, effectiveNodeTypeProvider, nodeTypeDefinitionProvider,
-                itemDefinitionProvider, true, Text.getName(nodeContext.getNodePath()), implicitNodeType);
+                itemDefinitionProvider, true, nodeContext, Text.getName(nodeContext.getNodePath()), implicitNodeType);
         // now validate for validity
         Optional<String> constraintViolation = childNode.validateAgainstParentNodeType(effectiveNodeType, nodeTypeDefinitionProvider,
                 itemDefinitionProvider);
@@ -224,13 +223,13 @@ public class JcrNodeTypeMetaDataImpl implements JcrNodeTypeMetaData {
     }
 
     @Override
-    public @NotNull JcrNodeTypeMetaData addUnknownChildNode(@NotNull NameResolver nameResolver, @NotNull String name)
+    public @NotNull JcrNodeTypeMetaData addUnknownChildNode(@NotNull NameResolver nameResolver, @NotNull NodeContext context, @NotNull String name)
             throws IllegalNameException, NamespaceException {
-        return addUnknownChildNode(getQName(nameResolver, name, NameType.NODE_NAME));
+        return addUnknownChildNode(context, getQName(nameResolver, name, NameType.NODE_NAME));
     }
 
-    private @NotNull JcrNodeTypeMetaDataImpl addUnknownChildNode(@NotNull Name name) throws IllegalNameException {
-        JcrNodeTypeMetaDataImpl childNode = new JcrNodeTypeMetaDataImpl(this.isIncremental, name, null, null, this, false, false);
+    private @NotNull JcrNodeTypeMetaDataImpl addUnknownChildNode(@NotNull NodeContext context, @NotNull Name name) throws IllegalNameException {
+        JcrNodeTypeMetaDataImpl childNode = new JcrNodeTypeMetaDataImpl(this.isIncremental, context, name, null, null, this, false, false);
         childNodesByName.put(name, childNode);
         return childNode;
     }
@@ -241,26 +240,13 @@ public class JcrNodeTypeMetaDataImpl implements JcrNodeTypeMetaData {
             @NotNull NodeTypeDefinitionProvider nodeTypeDefinitionProvider, @NotNull ItemDefinitionProvider itemDefinitionProvider,
             @NotNull ValidationMessageSeverity severity, @NotNull NodeContext nodeContext, @NotNull String primaryType,
             String... mixinTypes)
-            throws IllegalNameException, NoSuchNodeTypeException, RepositoryException, NamespaceExceptionInNodeName {
+            throws IllegalNameException, RepositoryException, NamespaceExceptionInNodeName {
 
         List<Name> types = getTypes(nameResolver, primaryType, mixinTypes);
         String nodeName = Text.getName(nodeContext.getNodePath());
         JcrNodeTypeMetaDataImpl childNode = addChildNode(nameResolver, effectiveNodeTypeProvider, nodeTypeDefinitionProvider,
-                itemDefinitionProvider, false, nodeName, types.toArray(new Name[0]));
-        // now validate for validity
-        Optional<String> constraintViolation = childNode.validateAgainstParentNodeType(effectiveNodeType, nodeTypeDefinitionProvider,
-                itemDefinitionProvider);
-        if (constraintViolation.isPresent()) {
-            childNode.messages.add(new ValidationMessage(severity,
-                    String.format(
-                            isImplicit ? MESSAGE_CHILD_NODE_OF_NOT_CONTAINED_PARENT_POTENTIALLY_NOT_ALLOWED
-                                    : MESSAGE_CHILD_NODE_NOT_ALLOWED,
-                            nodeName, primaryType,
-                            getEffectiveNodeTypeLabel(nameResolver, effectiveNodeType),
-                            constraintViolation.get()),
-                    nodeContext));
-
-        }
+                itemDefinitionProvider, false, nodeContext, nodeName, types.toArray(new Name[0]));
+        // defer validation
         return childNode;
     }
 
@@ -279,7 +265,7 @@ public class JcrNodeTypeMetaDataImpl implements JcrNodeTypeMetaData {
     private @NotNull JcrNodeTypeMetaDataImpl addChildNode(@NotNull NameResolver nameResolver,
             @NotNull EffectiveNodeTypeProvider effectiveNodeTypeProvider,
             @NotNull NodeTypeDefinitionProvider nodeTypeDefinitionProvider, @NotNull ItemDefinitionProvider itemDefinitionProvider,
-            boolean isImplicit, @NotNull String name, @Nullable Name... nodeTypes)
+            boolean isImplicit, @NotNull NodeContext context, @NotNull String name, @Nullable Name... nodeTypes)
             throws ConstraintViolationException, NoSuchNodeTypeException, NamespaceExceptionInNodeName, NamespaceException,
             IllegalNameException {
 
@@ -301,7 +287,7 @@ public class JcrNodeTypeMetaDataImpl implements JcrNodeTypeMetaData {
         if (!isAuthenticationOrAuthorizationContext) {
             isAuthenticationOrAuthorizationContext = this.isAuthenticationOrAuthorizationContext;
         }
-        JcrNodeTypeMetaDataImpl newNode = new JcrNodeTypeMetaDataImpl(this.isIncremental, qName, newPrimaryNodeType, newEffectiveNodeType, this,
+        JcrNodeTypeMetaDataImpl newNode = new JcrNodeTypeMetaDataImpl(this.isIncremental, context, qName, newPrimaryNodeType, newEffectiveNodeType, this,
                 isAuthenticationOrAuthorizationContext, isImplicit);
         childNodesByName.put(qName, newNode);
         return newNode;
@@ -348,15 +334,15 @@ public class JcrNodeTypeMetaDataImpl implements JcrNodeTypeMetaData {
     }
 
     @Override
-    public @NotNull Collection<ValidationMessage> finalizeValidation(@NotNull NamePathResolver namePathResolver,
+    public @NotNull Collection<ValidationMessage> finalizeValidation(@NotNull NamePathResolver namePathResolver,  @NotNull NodeTypeDefinitionProvider nodeTypeDefinitionProvider, @NotNull ItemDefinitionProvider itemDefinitionProvider,
             @NotNull ValidationMessageSeverity severity, @NotNull WorkspaceFilter filter) throws NamespaceException {
         if (!isValidationDone) {
             Collection<ValidationMessage> messages = new LinkedList<>();
             // in incremental validations ignore missing mandatory properties and child nodes (as they might not be visible to the validator)
             if (!isIncremental) {
                 messages.add(new ValidationMessage(ValidationMessageSeverity.DEBUG,
-                        "Validate mandatory children and properties of " + getQualifiedPath(namePathResolver)));
-                messages.addAll(validateMandatoryChildNodes(namePathResolver, severity, filter));
+                        "Validate children and mandatory properties of " + getQualifiedPath(namePathResolver)));
+                messages.addAll(validateChildNodes(namePathResolver, nodeTypeDefinitionProvider, itemDefinitionProvider, severity, filter));
                 messages.addAll(validateMandatoryProperties(namePathResolver, severity));
             }
             // only remove child nodes on 2nd level to be able to validate mandatory properties of parent
@@ -371,14 +357,36 @@ public class JcrNodeTypeMetaDataImpl implements JcrNodeTypeMetaData {
         }
     }
 
-    private Collection<ValidationMessage> validateMandatoryChildNodes(@NotNull NamePathResolver namePathResolver,
+    private Collection<ValidationMessage> validateChildNodes(@NotNull NamePathResolver namePathResolver,  @NotNull NodeTypeDefinitionProvider nodeTypeDefinitionProvider, @NotNull ItemDefinitionProvider itemDefinitionProvider,
             @NotNull ValidationMessageSeverity severity, @NotNull WorkspaceFilter filter) {
         if (effectiveNodeType == null) {
             return Collections.emptyList();
         }
 
-        // validate mandatory child nodes of children
         Collection<ValidationMessage> messages = new LinkedList<>();
+        // validate child nodes against parent node type definition
+        for (JcrNodeTypeMetaDataImpl childNode : childNodesByName.values()) {
+            Optional<String> constraintViolation;
+            try {
+                constraintViolation = childNode.validateAgainstParentNodeType(effectiveNodeType, nodeTypeDefinitionProvider,
+                        itemDefinitionProvider);
+                if (constraintViolation.isPresent()) {
+                    messages.add(new ValidationMessage(severity,
+                            String.format(
+                                    isImplicit ? MESSAGE_CHILD_NODE_OF_NOT_CONTAINED_PARENT_POTENTIALLY_NOT_ALLOWED
+                                            : MESSAGE_CHILD_NODE_NOT_ALLOWED,
+                                    namePathResolver.getJCRName(childNode.name), namePathResolver.getJCRName(childNode.primaryNodeType),
+                                    getEffectiveNodeTypeLabel(namePathResolver, effectiveNodeType),
+                                    constraintViolation.get()), childNode.context));
+        
+                }
+            } catch (RepositoryException e) {
+                throw new IllegalStateException("Could not validate child node " + childNode.name + " against parent node definition", e);
+            }
+            
+        }
+
+        // validate mandatory child nodes of children
         for (QNodeDefinition mandatoryNodeType : effectiveNodeType.getMandatoryQNodeDefinitions()) {
             // skip auto created ones
             if (mandatoryNodeType.isAutoCreated()) {
@@ -396,11 +404,12 @@ public class JcrNodeTypeMetaDataImpl implements JcrNodeTypeMetaData {
                     if (filter.contains(namePathResolver.getJCRPath(pathBuilder.getPath()))) {
                         messages.add(new ValidationMessage(severity, String.format(MESSAGE_MANDATORY_CHILD_NODE_MISSING,
                                 getNodeDefinitionLabel(namePathResolver, mandatoryNodeType),
-                                getEffectiveNodeTypeLabel(namePathResolver, effectiveNodeType), getQualifiedPath(namePathResolver))));
+                                getEffectiveNodeTypeLabel(namePathResolver, effectiveNodeType))));
                     } else {
-                        messages.add(new ValidationMessage(severity, String.format(MESSAGE_MANDATORY_UNCONTAINED_CHILD_NODE_MISSING,
+                        // if mandatory child nodes are missing outside filter rules, this is not an issue
+                        messages.add(new ValidationMessage(ValidationMessageSeverity.DEBUG, String.format(MESSAGE_MANDATORY_UNCONTAINED_CHILD_NODE_MISSING,
                                 getNodeDefinitionLabel(namePathResolver, mandatoryNodeType),
-                                getEffectiveNodeTypeLabel(namePathResolver, effectiveNodeType), getQualifiedPath(namePathResolver))));
+                                getEffectiveNodeTypeLabel(namePathResolver, effectiveNodeType)), context));
                     }
                 } catch (NamespaceException | MalformedPathException e) {
                     throw new IllegalStateException("Could not give out node types and name for " + mandatoryNodeType, e);
@@ -436,16 +445,16 @@ public class JcrNodeTypeMetaDataImpl implements JcrNodeTypeMetaData {
     }
 
     @Override
-    public @NotNull JcrNodeTypeMetaData getOrCreateNode(NamePathResolver nameResolver, String path) throws RepositoryException {
-        return getNode(nameResolver, path, true).get();
+    public @NotNull JcrNodeTypeMetaData getOrCreateNode(NamePathResolver nameResolver, @NotNull NodeContext nodeContext, String path) throws RepositoryException {
+        return getNode(nameResolver, nodeContext, path, true).get();
     }
 
     @Override
     public Optional<JcrNodeTypeMetaData> getNode(NamePathResolver nameResolver, String path) throws RepositoryException {
-        return getNode(nameResolver, path, false);
+        return getNode(nameResolver, null, path, false);
     }
 
-    private Optional<JcrNodeTypeMetaData> getNode(NamePathResolver nameResolver, String path, boolean shouldCreateIfMissing)
+    private Optional<JcrNodeTypeMetaData> getNode(NamePathResolver nameResolver, @Nullable NodeContext nodeContext, String path, boolean shouldCreateIfMissing)
             throws RepositoryException {
         // convert to fully namespaced path
         Path qPath = nameResolver.getQPath(path);
@@ -473,7 +482,10 @@ public class JcrNodeTypeMetaDataImpl implements JcrNodeTypeMetaData {
             JcrNodeTypeMetaDataImpl childNode = currentNode.childNodesByName.get(element.getName());
             if (childNode == null) {
                 if (shouldCreateIfMissing) {
-                    childNode = currentNode.addUnknownChildNode(element.getName());
+                    if (nodeContext == null) {
+                        throw new IllegalArgumentException("Node context must be given in case node is created but is null");
+                    }
+                    childNode = currentNode.addUnknownChildNode(nodeContext, element.getName());
                 } else {
                     return Optional.empty();
                 }
@@ -505,7 +517,7 @@ public class JcrNodeTypeMetaDataImpl implements JcrNodeTypeMetaData {
                     messages.add(new ValidationMessage(severity,
                             String.format(MESSAGE_MANDATORY_PROPERTY_MISSING,
                                     nameResolver.getJCRName(mandatoryPropertyDefinition.getName()),
-                                    getEffectiveNodeTypeLabel(nameResolver, effectiveNodeType), getQualifiedPath(nameResolver))));
+                                    getEffectiveNodeTypeLabel(nameResolver, effectiveNodeType)), context));
                 } else {
                     // check type
                     int actualPropertyType = propertyTypesByName.get(mandatoryPropertyDefinition.getName());
@@ -516,7 +528,7 @@ public class JcrNodeTypeMetaDataImpl implements JcrNodeTypeMetaData {
                                         nameResolver.getJCRName(mandatoryPropertyDefinition.getName()),
                                         PropertyType.nameFromValue(actualPropertyType),
                                         PropertyType.nameFromValue(mandatoryPropertyDefinition.getRequiredType()),
-                                        getEffectiveNodeTypeLabel(nameResolver, effectiveNodeType), getQualifiedPath(nameResolver))));
+                                        getEffectiveNodeTypeLabel(nameResolver, effectiveNodeType)), context));
                     }
                 }
             } catch (NamespaceException e) {
@@ -528,10 +540,11 @@ public class JcrNodeTypeMetaDataImpl implements JcrNodeTypeMetaData {
     }
 
     @Override
-    public void addProperty(@NotNull NodeContext nodeContext, @NotNull NamePathResolver namePathResolver,
+    public Collection<ValidationMessage> addProperty(@NotNull NodeContext nodeContext, @NotNull NamePathResolver namePathResolver,
             @NotNull EffectiveNodeTypeProvider effectiveNodeTypeProvider,
             @NotNull NodeTypeDefinitionProvider nodeTypeDefinitionProvider, @NotNull ItemDefinitionProvider itemDefinitionProvider,
             @NotNull ValidationMessageSeverity severity, String name, boolean isMultiValue, Value... values) throws RepositoryException {
+        Collection<ValidationMessage> messages = new ArrayList<>();
         // some sanity checks on multivalue
         if (!isMultiValue && values.length > 1) {
             throw new IllegalArgumentException("isMultiValue is only supposed to be false if exactly one value is passed but "
@@ -540,7 +553,7 @@ public class JcrNodeTypeMetaDataImpl implements JcrNodeTypeMetaData {
 
         if (values.length == 0) {
             // unable to proceed when no value is present
-            return;
+            return messages;
         }
 
         Name qName;
@@ -565,6 +578,7 @@ public class JcrNodeTypeMetaDataImpl implements JcrNodeTypeMetaData {
                             constraintViolation.get()),
                     nodeContext));
         }
+        return messages;
     }
 
     private @NotNull Optional<String> validatePropertyConstraints(@NotNull NamePathResolver namePathResolver,
@@ -631,7 +645,25 @@ public class JcrNodeTypeMetaDataImpl implements JcrNodeTypeMetaData {
 
     public static @NotNull JcrNodeTypeMetaDataImpl createRoot(boolean isIncremental, @NotNull EffectiveNodeTypeProvider effectiveNodeTypeProvider)
             throws ConstraintViolationException, NoSuchNodeTypeException {
-        return new JcrNodeTypeMetaDataImpl(isIncremental, NameConstants.ROOT, NameConstants.REP_ROOT, effectiveNodeTypeProvider.getEffectiveNodeType(
+        return new JcrNodeTypeMetaDataImpl(isIncremental, new NodeContext() {
+
+            @Override
+            public @NotNull String getNodePath() {
+                return null;
+            }
+
+            @Override
+            @NotNull
+            public java.nio.file.@NotNull Path getFilePath() {
+                return null;
+            }
+
+            @Override
+            public java.nio.file.@NotNull Path getBasePath() {
+                return null;
+            }
+            
+        }, NameConstants.ROOT, NameConstants.REP_ROOT, effectiveNodeTypeProvider.getEffectiveNodeType(
                 new Name[] {
                         NameConstants.REP_ROOT,
                         NameConstants.REP_ACCESS_CONTROLLABLE,
@@ -639,12 +671,6 @@ public class JcrNodeTypeMetaDataImpl implements JcrNodeTypeMetaData {
                 null, false, false);
     }
 
-    @Override
-    public void fetchAndClearValidationMessages(Collection<ValidationMessage> messages) {
-        messages.addAll(this.messages);
-        this.messages.clear();
-    }
-
     private Path getPath() {
         if (parentNode == null) {
             return PathFactoryImpl.getInstance().getRootPath();
diff --git a/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/impl/nodetype/NodeTypeValidator.java b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/impl/nodetype/NodeTypeValidator.java
index 2bacdcf..4add3c5 100644
--- a/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/impl/nodetype/NodeTypeValidator.java
+++ b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/impl/nodetype/NodeTypeValidator.java
@@ -121,7 +121,7 @@ public class NodeTypeValidator implements DocumentViewXmlValidator, JcrPathValid
             }
         }
         Collection<ValidationMessage> messages = new LinkedList<>();
-        messages.addAll(getOrCreateNewNode(nodeContext, isImplicit(nodeContext.getNodePath()), node.primary, node.mixins));
+        messages.addAll(getOrCreateNewNode(nodeContext, false, isImplicit(nodeContext.getNodePath()), node.primary, node.mixins));
 
         for (DocViewProperty property : node.props.values()) {
             try {
@@ -131,8 +131,6 @@ public class NodeTypeValidator implements DocumentViewXmlValidator, JcrPathValid
                         String.format(MESSAGE_INVALID_PROPERTY_VALUE, property.name, e.getLocalizedMessage())));
             }
         }
-        // emit messages
-        currentNodeTypeMetaData.fetchAndClearValidationMessages(messages);
 
         // defer checking for missing mandatory properties (as those might be added by some other files)
         return messages;
@@ -145,9 +143,9 @@ public class NodeTypeValidator implements DocumentViewXmlValidator, JcrPathValid
     private Collection<ValidationMessage> addProperty(NodeContext nodeContext, String propertyName, boolean isMultiValue, Value... values) {
         Collection<ValidationMessage> messages = new ArrayList<>();
         try {
-            currentNodeTypeMetaData.addProperty(nodeContext, ntManagerProvider.getNamePathResolver(),
+            messages.addAll(currentNodeTypeMetaData.addProperty(nodeContext, ntManagerProvider.getNamePathResolver(),
                     ntManagerProvider.getEffectiveNodeTypeProvider(), ntManagerProvider.getNodeTypeDefinitionProvider(),
-                    ntManagerProvider.getItemDefinitionProvider(), defaultSeverity, propertyName, isMultiValue, values);
+                    ntManagerProvider.getItemDefinitionProvider(), defaultSeverity, propertyName, isMultiValue, values));
         } catch (NoSuchNodeTypeException | NamespaceException e) {
             // log each unknown node type/namespace only once!
             if (!loggedUnknownNodeTypeMessages.contains(e.getMessage())) {
@@ -171,7 +169,7 @@ public class NodeTypeValidator implements DocumentViewXmlValidator, JcrPathValid
         }
     }
 
-    private @NotNull Collection<ValidationMessage> getOrCreateNewNode(NodeContext nodeContext, boolean isImplicit, String primaryType,
+    private @NotNull Collection<ValidationMessage> getOrCreateNewNode(NodeContext nodeContext, boolean isFolder, boolean isImplicit, String primaryType,
             String... mixinTypes) {
         Optional<JcrNodeTypeMetaData> node = getNode(nodeContext.getNodePath());
         if (node.isPresent()) {
@@ -193,11 +191,11 @@ public class NodeTypeValidator implements DocumentViewXmlValidator, JcrPathValid
             }
             return Collections.emptyList();
         } else {
-            return createNewNode(nodeContext, isImplicit, primaryType, mixinTypes);
+            return createNewNode(nodeContext, isFolder, isImplicit, primaryType, mixinTypes);
         }
     }
 
-    private @NotNull Collection<ValidationMessage> createNewNode(NodeContext nodeContext, boolean isImplicit, String primaryType,
+    private @NotNull Collection<ValidationMessage> createNewNode(NodeContext nodeContext, boolean isFolder, boolean isImplicit, String primaryType,
             String... mixinTypes) {
         Collection<ValidationMessage> messages = new ArrayList<>();
         String nodePath = nodeContext.getNodePath();
@@ -209,7 +207,7 @@ public class NodeTypeValidator implements DocumentViewXmlValidator, JcrPathValid
         try {
             //
             JcrNodeTypeMetaData parentNode = currentNodeTypeMetaData.getOrCreateNode(ntManagerProvider.getNamePathResolver(),
-                    parentNodePath);
+                    nodeContext, parentNodePath);
             try {
                 if (isImplicit) {
                     if (!nodePath.equals("/")) {
@@ -224,6 +222,7 @@ public class NodeTypeValidator implements DocumentViewXmlValidator, JcrPathValid
                     currentNodeTypeMetaData = parentNode.addChildNode(ntManagerProvider.getNameResolver(),
                             ntManagerProvider.getEffectiveNodeTypeProvider(), ntManagerProvider.getNodeTypeDefinitionProvider(),
                             ntManagerProvider.getItemDefinitionProvider(), defaultSeverity, nodeContext, primaryType, mixinTypes);
+                    
                 }
             } catch (NoSuchNodeTypeException | NamespaceException e) {
                 // TODO: NoSuchNodeTypeException might be thrown due to previous registration of the namespace for a node name
@@ -238,9 +237,9 @@ public class NodeTypeValidator implements DocumentViewXmlValidator, JcrPathValid
                     // now register namespace with an arbitrary namespace url
                     NameParser.parse(nodeName, new OnDemandRegisterNamespaceResolverWrapper(ntManagerProvider),
                             NameFactoryImpl.getInstance());
-                    messages.addAll(createNewNode(nodeContext, isImplicit, primaryType, mixinTypes));
+                    messages.addAll(createNewNode(nodeContext, isFolder, isImplicit, primaryType, mixinTypes));
                 } else {
-                    currentNodeTypeMetaData = parentNode.addUnknownChildNode(ntManagerProvider.getNameResolver(), nodeName);
+                    currentNodeTypeMetaData = parentNode.addUnknownChildNode(ntManagerProvider.getNameResolver(),nodeContext, nodeName);
                 }
             }
         } catch (RepositoryException e) {
@@ -285,9 +284,11 @@ public class NodeTypeValidator implements DocumentViewXmlValidator, JcrPathValid
 
     private @Nullable Collection<ValidationMessage> finalizeValidationForSubtree(JcrNodeTypeMetaData node, NodeContext nodeContext) throws NamespaceException {
         Collection<ValidationMessage> messages = new ArrayList<>();
+        messages.add(new ValidationMessage(ValidationMessageSeverity.DEBUG, "Finalize validation for subtree at " + nodeContext));
         for (JcrNodeTypeMetaData child : node.getChildren()) {
             messages.addAll(finalizeValidationForSubtree(child, nodeContext));
-            messages.addAll(child.finalizeValidation(ntManagerProvider.getNamePathResolver(), defaultSeverity, filter));
+            messages.addAll(child.finalizeValidation(ntManagerProvider.getNamePathResolver(), ntManagerProvider.getNodeTypeDefinitionProvider(),
+                    ntManagerProvider.getItemDefinitionProvider(), defaultSeverity, filter));
         }
         return messages;
     }
@@ -299,7 +300,7 @@ public class NodeTypeValidator implements DocumentViewXmlValidator, JcrPathValid
         List<ValidationMessage> messages = new ArrayList<>();
         boolean isImplicit = isImplicit(nodeContext.getNodePath());
         if (isFolder) {
-            messages.addAll(getOrCreateNewNode(nodeContext, isImplicit, JcrConstants.NT_FOLDER));
+            messages.addAll(getOrCreateNewNode(nodeContext, isFolder, isImplicit, JcrConstants.NT_FOLDER));
             //
             if (!nodeContext.getNodePath().equals("/")) {
                 messages.addAll(finalizeValidationForSiblings(nodeContext));
@@ -311,19 +312,19 @@ public class NodeTypeValidator implements DocumentViewXmlValidator, JcrPathValid
                 // https://jackrabbit.apache.org/filevault/vaultfs.html#Binary_Properties
                 if (fileName.endsWith(ValidationExecutor.EXTENSION_BINARY)) {
                     // create parent if it does not exist yet
-                    messages.addAll(getOrCreateNewNode(nodeContext, isImplicit, JcrConstants.NT_FOLDER));
+                    messages.addAll(getOrCreateNewNode(nodeContext, isFolder, isImplicit, JcrConstants.NT_FOLDER));
                     String propertyName = fileName.substring(0, fileName.length() - ValidationExecutor.EXTENSION_BINARY.length());
                     messages.addAll(addProperty(nodeContext, propertyName, false, DUMMY_BINARY_VALUE));
                 } else {
                     // if binary node is not yet there
-                    messages.addAll(getOrCreateNewNode(nodeContext, isImplicit, JcrConstants.NT_FILE));
+                    messages.addAll(getOrCreateNewNode(nodeContext, isFolder, isImplicit, JcrConstants.NT_FILE));
                     // if a NT_FILE create a jcr:content sub node of type NT_RESOURCE
                     if (currentNodeTypeMetaData.getPrimaryNodeType().equals(NameConstants.NT_FILE)) {
                         // create new node context
                         nodeContext = new NodeContextImpl(nodeContext.getNodePath() + "/" + JcrConstants.JCR_CONTENT,
                                 nodeContext.getFilePath(), nodeContext.getBasePath());
                         messages.addAll(
-                                getOrCreateNewNode(nodeContext, isImplicit(nodeContext.getNodePath()), JcrConstants.NT_RESOURCE));
+                                getOrCreateNewNode(nodeContext, isFolder, isImplicit(nodeContext.getNodePath()), JcrConstants.NT_RESOURCE));
                     }
                     messages.addAll(addProperty(nodeContext, JcrConstants.JCR_DATA, false, DUMMY_BINARY_VALUE));
                     messages.addAll(addProperty(nodeContext, JcrConstants.JCR_MIMETYPE, false, DUMMY_STRING_VALUE));
diff --git a/vault-validation/src/test/java/org/apache/jackrabbit/vault/validation/spi/impl/nodetype/JcrNodeTypeMetaDataImplTest.java b/vault-validation/src/test/java/org/apache/jackrabbit/vault/validation/spi/impl/nodetype/JcrNodeTypeMetaDataImplTest.java
index f69d870..0eef044 100644
--- a/vault-validation/src/test/java/org/apache/jackrabbit/vault/validation/spi/impl/nodetype/JcrNodeTypeMetaDataImplTest.java
+++ b/vault-validation/src/test/java/org/apache/jackrabbit/vault/validation/spi/impl/nodetype/JcrNodeTypeMetaDataImplTest.java
@@ -22,7 +22,6 @@ import java.io.InputStreamReader;
 import java.io.Reader;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Paths;
-import java.util.ArrayList;
 import java.util.Collection;
 
 import javax.jcr.NamespaceException;
@@ -48,7 +47,6 @@ import org.apache.jackrabbit.vault.validation.spi.ValidationMessage;
 import org.apache.jackrabbit.vault.validation.spi.ValidationMessageSeverity;
 import org.apache.jackrabbit.vault.validation.spi.util.NodeContextImpl;
 import org.hamcrest.MatcherAssert;
-import org.hamcrest.Matchers;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -79,19 +77,16 @@ public class JcrNodeTypeMetaDataImplTest {
                 ntManagerProvider.getNodeTypeDefinitionProvider(), ntManagerProvider.getItemDefinitionProvider(), severity,
                 createSimpleNodeContext("my"),
                 NodeType.NT_FOLDER);
-        assertNoValidationErrors(child);
         JcrNodeTypeMetaData grandChild = child.addChildNode(ntManagerProvider.getNameResolver(),
                 ntManagerProvider.getEffectiveNodeTypeProvider(),
                 ntManagerProvider.getNodeTypeDefinitionProvider(), ntManagerProvider.getItemDefinitionProvider(), severity,
                 createSimpleNodeContext("test"),
                 NodeType.NT_FOLDER);
-        assertNoValidationErrors(grandChild);
         JcrNodeTypeMetaData child2 = root.addChildNode(ntManagerProvider.getNameResolver(),
                 ntManagerProvider.getEffectiveNodeTypeProvider(),
                 ntManagerProvider.getNodeTypeDefinitionProvider(), ntManagerProvider.getItemDefinitionProvider(), severity,
                 createSimpleNodeContext("test2"),
                 NodeType.NT_FOLDER);
-        assertNoValidationErrors(child2);
         Assert.assertEquals(child2,
                 grandChild.getNode(ntManagerProvider.getNamePathResolver(), "/test2").get());
     }
@@ -142,81 +137,87 @@ public class JcrNodeTypeMetaDataImplTest {
                         StandardCharsets.US_ASCII)) {
             ntManagerProvider.registerNodeTypes(reader);
         }
-
-        // add child node with mixin type as primary
+        DefaultWorkspaceFilter filter = new DefaultWorkspaceFilter();
+        
+        // add node with mixin type as primary
         NodeContext nodeContext = createSimpleNodeContext("name");
+        
         JcrNodeTypeMetaData node = root.addChildNode(ntManagerProvider.getNamePathResolver(),
                 ntManagerProvider.getEffectiveNodeTypeProvider(), ntManagerProvider.getNodeTypeDefinitionProvider(),
                 ntManagerProvider.getItemDefinitionProvider(), ValidationMessageSeverity.ERROR, nodeContext,
                 "mix:mimeType");
-        assertValidationMessage(node, new ValidationMessage(ValidationMessageSeverity.ERROR,
+        ValidationExecutorTest.assertViolation(root.finalizeValidation(ntManagerProvider.getNamePathResolver(), ntManagerProvider.getNodeTypeDefinitionProvider(),
+                ntManagerProvider.getItemDefinitionProvider(),
+                ValidationMessageSeverity.ERROR, filter),
+                new ValidationMessage(ValidationMessageSeverity.ERROR,
                 String.format(JcrNodeTypeMetaDataImpl.MESSAGE_CHILD_NODE_NOT_ALLOWED, "name", "mix:mimeType", ROOT_NODE_TYPES,
-                        JcrNodeTypeMetaDataImpl.CONSTRAINT_MIXIN_TYPE_AS_PRIMARY_TYPE),
-                nodeContext));
+                        JcrNodeTypeMetaDataImpl.CONSTRAINT_MIXIN_TYPE_AS_PRIMARY_TYPE), nodeContext));
 
-        // add child node with abstract type
+        // add node with abstract type
+        root = JcrNodeTypeMetaDataImpl.createRoot(false, ntManagerProvider.getEffectiveNodeTypeProvider());
         node = root.addChildNode(ntManagerProvider.getNamePathResolver(),
                 ntManagerProvider.getEffectiveNodeTypeProvider(), ntManagerProvider.getNodeTypeDefinitionProvider(),
                 ntManagerProvider.getItemDefinitionProvider(), ValidationMessageSeverity.ERROR, nodeContext,
                 "nt:hierarchyNode");
-        assertValidationMessage(node, new ValidationMessage(ValidationMessageSeverity.ERROR,
+        ValidationExecutorTest.assertViolation(root.finalizeValidation(ntManagerProvider.getNamePathResolver(), ntManagerProvider.getNodeTypeDefinitionProvider(),
+                ntManagerProvider.getItemDefinitionProvider(),
+                ValidationMessageSeverity.ERROR, filter),
+                new ValidationMessage(ValidationMessageSeverity.ERROR,
                 String.format(JcrNodeTypeMetaDataImpl.MESSAGE_CHILD_NODE_NOT_ALLOWED, "name", "nt:hierarchyNode", ROOT_NODE_TYPES,
-                        JcrNodeTypeMetaDataImpl.CONSTRAINT_ABSTRACT_TYPE_AS_PRIMARY_TYPE),
-                nodeContext));
+                        JcrNodeTypeMetaDataImpl.CONSTRAINT_ABSTRACT_TYPE_AS_PRIMARY_TYPE), nodeContext));
 
+        // add node for version storage
+        root = JcrNodeTypeMetaDataImpl.createRoot(false, ntManagerProvider.getEffectiveNodeTypeProvider());
         node = root.addChildNode(ntManagerProvider.getNamePathResolver(), ntManagerProvider.getEffectiveNodeTypeProvider(),
                 ntManagerProvider.getNodeTypeDefinitionProvider(), ntManagerProvider.getItemDefinitionProvider(),
                 ValidationMessageSeverity.ERROR,
                 createSimpleNodeContext("versionedNode"), "rep:versionStorage");
-        assertNoValidationErrors(node);
-
         // add child node with protected node which is ACL (i.e. accepted)
-        JcrNodeTypeMetaData childNode = node.addChildNode(ntManagerProvider.getNamePathResolver(),
+        node.addChildNode(ntManagerProvider.getNamePathResolver(),
                 ntManagerProvider.getEffectiveNodeTypeProvider(),
                 ntManagerProvider.getNodeTypeDefinitionProvider(), ntManagerProvider.getItemDefinitionProvider(),
                 ValidationMessageSeverity.ERROR,
                 createSimpleNodeContext("rep:policy"), "rep:Policy");
-        assertNoValidationErrors(childNode);
-
-        // add child node with protected node which is not ACL (i.e. accepted)
-        childNode = node.addChildNode(ntManagerProvider.getNamePathResolver(), ntManagerProvider.getEffectiveNodeTypeProvider(),
+        // add child node with protected node which is not ACL (i.e. not accepted)
+        root = JcrNodeTypeMetaDataImpl.createRoot(false, ntManagerProvider.getEffectiveNodeTypeProvider());
+        node.addChildNode(ntManagerProvider.getNamePathResolver(), ntManagerProvider.getEffectiveNodeTypeProvider(),
                 ntManagerProvider.getNodeTypeDefinitionProvider(), ntManagerProvider.getItemDefinitionProvider(),
                 ValidationMessageSeverity.ERROR,
                 nodeContext, "nt:versionHistory");
-        assertValidationMessage(childNode, new ValidationMessage(ValidationMessageSeverity.ERROR,
+        ValidationExecutorTest.assertViolation(node.finalizeValidation(ntManagerProvider.getNamePathResolver(), ntManagerProvider.getNodeTypeDefinitionProvider(),
+                ntManagerProvider.getItemDefinitionProvider(),
+                ValidationMessageSeverity.ERROR, filter), new ValidationMessage(ValidationMessageSeverity.ERROR,
                 String.format(JcrNodeTypeMetaDataImpl.MESSAGE_CHILD_NODE_NOT_ALLOWED, "name", "nt:versionHistory", "rep:versionStorage",
-                        JcrNodeTypeMetaDataImpl.CONSTRAINT_CHILD_NODE_PROTECTED),
-                nodeContext));
+                        JcrNodeTypeMetaDataImpl.CONSTRAINT_CHILD_NODE_PROTECTED), nodeContext));
 
         // add valid child node
+        root = JcrNodeTypeMetaDataImpl.createRoot(false, ntManagerProvider.getEffectiveNodeTypeProvider());
         node = root.addChildNode(ntManagerProvider.getNamePathResolver(),
                 ntManagerProvider.getEffectiveNodeTypeProvider(), ntManagerProvider.getNodeTypeDefinitionProvider(),
                 ntManagerProvider.getItemDefinitionProvider(), ValidationMessageSeverity.ERROR, createSimpleNodeContext("name"),
                 "my:nodeType1");
-        assertNoValidationErrors(node);
-
-        // add auto-created child node
+        // below that add auto-created child node
         nodeContext = createSimpleNodeContext("my:autoCreatedChild1");
-        childNode = node.addChildNode(ntManagerProvider.getNamePathResolver(),
+        node.addChildNode(ntManagerProvider.getNamePathResolver(),
                 ntManagerProvider.getEffectiveNodeTypeProvider(), ntManagerProvider.getNodeTypeDefinitionProvider(),
                 ntManagerProvider.getItemDefinitionProvider(), ValidationMessageSeverity.ERROR,
                 nodeContext, "my:nodeType2");
-        assertValidationMessage(childNode, new ValidationMessage(ValidationMessageSeverity.ERROR,
-                String.format(JcrNodeTypeMetaDataImpl.MESSAGE_CHILD_NODE_NOT_ALLOWED, "my:autoCreatedChild1", "my:nodeType2",
-                        "my:nodeType1",
-                        JcrNodeTypeMetaDataImpl.CONSTRAINT_CHILD_NODE_AUTO_CREATED),
-                nodeContext));
-
-        // below that add child node which is not allowed
-        nodeContext = createSimpleNodeContext("name2");
-        childNode = node.addChildNode(ntManagerProvider.getNamePathResolver(),
+        // and next to it a child node which is not allowed
+        NodeContext nodeContext2 = createSimpleNodeContext("name2");
+        node.addChildNode(ntManagerProvider.getNamePathResolver(),
                 ntManagerProvider.getEffectiveNodeTypeProvider(), ntManagerProvider.getNodeTypeDefinitionProvider(),
-                ntManagerProvider.getItemDefinitionProvider(), ValidationMessageSeverity.ERROR, nodeContext,
+                ntManagerProvider.getItemDefinitionProvider(), ValidationMessageSeverity.ERROR, nodeContext2,
                 "my:nodeType1");
-        assertValidationMessage(childNode, new ValidationMessage(ValidationMessageSeverity.ERROR,
+        ValidationExecutorTest.assertViolation(node.finalizeValidation(ntManagerProvider.getNamePathResolver(), ntManagerProvider.getNodeTypeDefinitionProvider(),
+                ntManagerProvider.getItemDefinitionProvider(),
+                ValidationMessageSeverity.ERROR, filter), 
+                new ValidationMessage(ValidationMessageSeverity.ERROR,
+                        String.format(JcrNodeTypeMetaDataImpl.MESSAGE_CHILD_NODE_NOT_ALLOWED, "my:autoCreatedChild1", "my:nodeType2",
+                                "my:nodeType1",
+                                JcrNodeTypeMetaDataImpl.CONSTRAINT_CHILD_NODE_AUTO_CREATED), nodeContext),
+                new ValidationMessage(ValidationMessageSeverity.ERROR,
                 String.format(JcrNodeTypeMetaDataImpl.MESSAGE_CHILD_NODE_NOT_ALLOWED, "name2", "my:nodeType1", "my:nodeType1",
-                        JcrNodeTypeMetaDataImpl.CONSTRAINT_CHILD_NODE_NOT_ALLOWED),
-                nodeContext));
+                        JcrNodeTypeMetaDataImpl.CONSTRAINT_CHILD_NODE_NOT_ALLOWED), nodeContext2));
     }
 
     @Test
@@ -232,16 +233,14 @@ public class JcrNodeTypeMetaDataImplTest {
                 ntManagerProvider.getEffectiveNodeTypeProvider(), ntManagerProvider.getNodeTypeDefinitionProvider(),
                 ntManagerProvider.getItemDefinitionProvider(), ValidationMessageSeverity.ERROR, createSimpleNodeContext("name"),
                 "my:nodeType1");
-        assertNoValidationErrors(node);
 
         DefaultWorkspaceFilter filter = new DefaultWorkspaceFilter();
 
         // mandatory child node missing outside filter
-        Collection<ValidationMessage> messages = node.finalizeValidation(ntManagerProvider.getNamePathResolver(),
+        Collection<ValidationMessage> messages = root.finalizeValidation(ntManagerProvider.getNamePathResolver(), ntManagerProvider.getNodeTypeDefinitionProvider(),
+                ntManagerProvider.getItemDefinitionProvider(),
                 ValidationMessageSeverity.ERROR, filter);
-        ValidationExecutorTest.assertViolation(messages, new ValidationMessage(ValidationMessageSeverity.ERROR,
-                String.format(JcrNodeTypeMetaDataImpl.MESSAGE_MANDATORY_UNCONTAINED_CHILD_NODE_MISSING, "my:namedChild1 [my:nodeType1]", "my:nodeType1",
-                        "/name")));
+        MatcherAssert.assertThat(messages, AnyValidationMessageMatcher.noValidationInCollection());
 
         node = root.addChildNode(ntManagerProvider.getNamePathResolver(),
                 ntManagerProvider.getEffectiveNodeTypeProvider(), ntManagerProvider.getNodeTypeDefinitionProvider(),
@@ -250,13 +249,15 @@ public class JcrNodeTypeMetaDataImplTest {
         
         // mandatory child node missing inside filter
         filter.add(new PathFilterSet("/"));
-        messages = node.finalizeValidation(ntManagerProvider.getNamePathResolver(), ValidationMessageSeverity.ERROR, filter);
+        messages = node.finalizeValidation(ntManagerProvider.getNamePathResolver(), ntManagerProvider.getNodeTypeDefinitionProvider(),
+                ntManagerProvider.getItemDefinitionProvider(),ValidationMessageSeverity.ERROR, filter);
         ValidationExecutorTest.assertViolation(messages, new ValidationMessage(ValidationMessageSeverity.ERROR,
                 String.format(JcrNodeTypeMetaDataImpl.MESSAGE_MANDATORY_CHILD_NODE_MISSING, "my:namedChild1 [my:nodeType1]", "my:nodeType1",
                         "/name2")));
 
         // calling a second time will not lead to anything
-        messages = node.finalizeValidation(ntManagerProvider.getNamePathResolver(), ValidationMessageSeverity.ERROR, filter);
+        messages = node.finalizeValidation(ntManagerProvider.getNamePathResolver(), ntManagerProvider.getNodeTypeDefinitionProvider(),
+                ntManagerProvider.getItemDefinitionProvider(), ValidationMessageSeverity.ERROR, filter);
         MatcherAssert.assertThat(messages, AnyValidationMessageMatcher.noValidationInCollection());
         
         // now add mandatory child node
@@ -269,40 +270,40 @@ public class JcrNodeTypeMetaDataImplTest {
                 ntManagerProvider.getEffectiveNodeTypeProvider(), ntManagerProvider.getNodeTypeDefinitionProvider(),
                 ntManagerProvider.getItemDefinitionProvider(), ValidationMessageSeverity.ERROR, createSimpleNodeContext("my:namedChild1"),
                 "my:nodeType1");
-        messages = node.finalizeValidation(ntManagerProvider.getNamePathResolver(), ValidationMessageSeverity.ERROR,
+        messages = node.finalizeValidation(ntManagerProvider.getNamePathResolver(), ntManagerProvider.getNodeTypeDefinitionProvider(),
+                ntManagerProvider.getItemDefinitionProvider(), ValidationMessageSeverity.ERROR,
                 new DefaultWorkspaceFilter());
         MatcherAssert.assertThat(messages, AnyValidationMessageMatcher.noValidationInCollection());
 
         // add arbitrary property to root
-        root.addProperty(createSimpleNodeContext("/"), ntManagerProvider.getNamePathResolver(),
+        MatcherAssert.assertThat(root.addProperty(createSimpleNodeContext("/"), ntManagerProvider.getNamePathResolver(),
                 ntManagerProvider.getEffectiveNodeTypeProvider(), ntManagerProvider.getNodeTypeDefinitionProvider(),
                 ntManagerProvider.getItemDefinitionProvider(), ValidationMessageSeverity.ERROR, "property", false,
-                ValueFactoryImpl.getInstance().createValue("foo"));
-        assertNoValidationErrors(root);
-
+                ValueFactoryImpl.getInstance().createValue("foo")), AnyValidationMessageMatcher.noValidationInCollection());
+        
         NodeContext nodeContext = createSimpleNodeContext("nodeForMandatoryProperties");
         node = root.addChildNode(ntManagerProvider.getNamePathResolver(),
                 ntManagerProvider.getEffectiveNodeTypeProvider(), ntManagerProvider.getNodeTypeDefinitionProvider(),
                 ntManagerProvider.getItemDefinitionProvider(), ValidationMessageSeverity.ERROR, nodeContext, "my:nodeType2");
-        assertNoValidationErrors(node);
-        messages = node.finalizeValidation(ntManagerProvider.getNamePathResolver(), ValidationMessageSeverity.ERROR, filter);
+        messages = node.finalizeValidation(ntManagerProvider.getNamePathResolver(), ntManagerProvider.getNodeTypeDefinitionProvider(),
+                ntManagerProvider.getItemDefinitionProvider(),ValidationMessageSeverity.ERROR, filter);
         ValidationExecutorTest.assertViolation(messages, new ValidationMessage(ValidationMessageSeverity.ERROR,
                 String.format(JcrNodeTypeMetaDataImpl.MESSAGE_MANDATORY_PROPERTY_MISSING, "my:mandatoryProperty", "my:nodeType2",
-                        "/nodeForMandatoryProperties")));
+                        "/nodeForMandatoryProperties"), nodeContext));
 
         nodeContext = createSimpleNodeContext("nodeForMandatoryProperties2");
         node = root.addChildNode(ntManagerProvider.getNamePathResolver(),
                 ntManagerProvider.getEffectiveNodeTypeProvider(), ntManagerProvider.getNodeTypeDefinitionProvider(),
                 ntManagerProvider.getItemDefinitionProvider(), ValidationMessageSeverity.ERROR, nodeContext, "my:nodeType2");
-        assertNoValidationErrors(node);
         node.addProperty(nodeContext, ntManagerProvider.getNamePathResolver(), ntManagerProvider.getEffectiveNodeTypeProvider(),
                 ntManagerProvider.getNodeTypeDefinitionProvider(), ntManagerProvider.getItemDefinitionProvider(),
                 ValidationMessageSeverity.ERROR,
                 "my:mandatoryProperty", false, ValueFactoryImpl.getInstance().createValue("foo"));
-        messages = node.finalizeValidation(ntManagerProvider.getNamePathResolver(), ValidationMessageSeverity.ERROR, filter);
+        messages = node.finalizeValidation(ntManagerProvider.getNamePathResolver(), ntManagerProvider.getNodeTypeDefinitionProvider(),
+                ntManagerProvider.getItemDefinitionProvider(), ValidationMessageSeverity.ERROR, filter);
         ValidationExecutorTest.assertViolation(messages, new ValidationMessage(ValidationMessageSeverity.ERROR,
                 String.format(JcrNodeTypeMetaDataImpl.MESSAGE_MANDATORY_PROPERTY_WITH_WRONG_TYPE, "my:mandatoryProperty", "String", "Date",
-                        "my:nodeType2", "/nodeForMandatoryProperties2")));
+                        "my:nodeType2"), nodeContext));
     }
 
     @Test
@@ -318,7 +319,6 @@ public class JcrNodeTypeMetaDataImplTest {
                 ntManagerProvider.getEffectiveNodeTypeProvider(), ntManagerProvider.getNodeTypeDefinitionProvider(),
                 ntManagerProvider.getItemDefinitionProvider(), ValidationMessageSeverity.ERROR, createSimpleNodeContext("name"),
                 "my:nodeType1");
-        assertNoValidationErrors(node);
 
         DefaultWorkspaceFilter filter = new DefaultWorkspaceFilter();
 
@@ -329,7 +329,8 @@ public class JcrNodeTypeMetaDataImplTest {
         
         // mandatory child node missing but not reported due to incremental validation
         filter.add(new PathFilterSet("/"));
-        Collection<ValidationMessage> messages = node.finalizeValidation(ntManagerProvider.getNamePathResolver(), ValidationMessageSeverity.ERROR, filter);
+        Collection<ValidationMessage> messages = node.finalizeValidation(ntManagerProvider.getNamePathResolver(), ntManagerProvider.getNodeTypeDefinitionProvider(),
+                ntManagerProvider.getItemDefinitionProvider(),  ValidationMessageSeverity.ERROR, filter);
         MatcherAssert.assertThat(messages, AnyValidationMessageMatcher.noValidationInCollection());
 
         // mandatory property missing but not reported due to incremental validation
@@ -337,8 +338,8 @@ public class JcrNodeTypeMetaDataImplTest {
         node = root.addChildNode(ntManagerProvider.getNamePathResolver(),
                 ntManagerProvider.getEffectiveNodeTypeProvider(), ntManagerProvider.getNodeTypeDefinitionProvider(),
                 ntManagerProvider.getItemDefinitionProvider(), ValidationMessageSeverity.ERROR, nodeContext, "my:nodeType2");
-        assertNoValidationErrors(node);
-        messages = node.finalizeValidation(ntManagerProvider.getNamePathResolver(), ValidationMessageSeverity.ERROR, filter);
+        messages = node.finalizeValidation(ntManagerProvider.getNamePathResolver(), ntManagerProvider.getNodeTypeDefinitionProvider(),
+                ntManagerProvider.getItemDefinitionProvider(), ValidationMessageSeverity.ERROR, filter);
         MatcherAssert.assertThat(messages, AnyValidationMessageMatcher.noValidationInCollection());
     }
     
@@ -361,72 +362,65 @@ public class JcrNodeTypeMetaDataImplTest {
 
         NodeContext nodeContext = createSimpleNodeContext("/");
         // add arbitrary property to root
-        root.addProperty(nodeContext, ntManagerProvider.getNamePathResolver(),
+        MatcherAssert.assertThat(root.addProperty(nodeContext, ntManagerProvider.getNamePathResolver(),
                 ntManagerProvider.getEffectiveNodeTypeProvider(), ntManagerProvider.getNodeTypeDefinitionProvider(),
                 ntManagerProvider.getItemDefinitionProvider(), ValidationMessageSeverity.ERROR, "property", false,
-                ValueFactoryImpl.getInstance().createValue("foo"));
-        assertNoValidationErrors(root);
+                ValueFactoryImpl.getInstance().createValue("foo")), AnyValidationMessageMatcher.noValidationInCollection());
 
         JcrNodeTypeMetaData node = root.addChildNode(ntManagerProvider.getNamePathResolver(),
                 ntManagerProvider.getEffectiveNodeTypeProvider(), ntManagerProvider.getNodeTypeDefinitionProvider(),
                 ntManagerProvider.getItemDefinitionProvider(), ValidationMessageSeverity.ERROR, createSimpleNodeContext("child"),
                 "my:nodeType3");
-        assertNoValidationErrors(node);
 
         // not allowed (wrong type)
-        node.addProperty(nodeContext, ntManagerProvider.getNamePathResolver(),
+        ValidationExecutorTest.assertViolation( node.addProperty(nodeContext, ntManagerProvider.getNamePathResolver(),
                 ntManagerProvider.getEffectiveNodeTypeProvider(), ntManagerProvider.getNodeTypeDefinitionProvider(),
                 ntManagerProvider.getItemDefinitionProvider(), ValidationMessageSeverity.ERROR, "property", false,
-                ValueFactoryImpl.getInstance().createValue("foo"));
-        assertValidationMessage(node,
+                ValueFactoryImpl.getInstance().createValue("foo")),
                 new ValidationMessage(ValidationMessageSeverity.ERROR,
                         String.format(JcrNodeTypeMetaDataImpl.MESSAGE_PROPERTY_NOT_ALLOWED, "property",
                                 "String", "my:nodeType3", JcrNodeTypeMetaDataImpl.CONSTRAINT_PROPERTY_NOT_ALLOWED),
                         nodeContext));
 
         // protected but nevertheless allowed
-        node.addProperty(nodeContext, ntManagerProvider.getNamePathResolver(),
+        MatcherAssert.assertThat(node.addProperty(nodeContext, ntManagerProvider.getNamePathResolver(),
                 ntManagerProvider.getEffectiveNodeTypeProvider(), ntManagerProvider.getNodeTypeDefinitionProvider(),
                 ntManagerProvider.getItemDefinitionProvider(), ValidationMessageSeverity.ERROR, "jcr:primaryType", false,
-                ValueFactoryImpl.getInstance().createValue("foo"));
-        assertNoValidationErrors(node);
+                ValueFactoryImpl.getInstance().createValue("foo")), AnyValidationMessageMatcher.noValidationInCollection());
 
         // protected
-        node.addProperty(nodeContext, ntManagerProvider.getNamePathResolver(), ntManagerProvider.getEffectiveNodeTypeProvider(),
+        ValidationExecutorTest.assertViolation(node.addProperty(nodeContext, ntManagerProvider.getNamePathResolver(), ntManagerProvider.getEffectiveNodeTypeProvider(),
                 ntManagerProvider.getNodeTypeDefinitionProvider(), ntManagerProvider.getItemDefinitionProvider(),
                 ValidationMessageSeverity.ERROR,
-                "my:protected", false, ValueFactoryImpl.getInstance().createValue("foo"));
-        assertValidationMessage(node,
+                "my:protected", false, ValueFactoryImpl.getInstance().createValue("foo")),
                 new ValidationMessage(ValidationMessageSeverity.ERROR, String.format(JcrNodeTypeMetaDataImpl.MESSAGE_PROPERTY_NOT_ALLOWED,
                         "my:protected", "String", "my:nodeType3", JcrNodeTypeMetaDataImpl.CONSTRAINT_PROPERTY_PROTECTED), nodeContext));
 
         // multi value where single value is required
-        node.addProperty(nodeContext, ntManagerProvider.getNamePathResolver(),
+        ValidationExecutorTest.assertViolation(node.addProperty(nodeContext, ntManagerProvider.getNamePathResolver(),
                 ntManagerProvider.getEffectiveNodeTypeProvider(), ntManagerProvider.getNodeTypeDefinitionProvider(),
                 ntManagerProvider.getItemDefinitionProvider(), ValidationMessageSeverity.ERROR, "my:property1", true,
-                ValueFactoryImpl.getInstance().createValue("foo"), ValueFactoryImpl.getInstance().createValue("bar"));
-        assertValidationMessage(node,
+                ValueFactoryImpl.getInstance().createValue("foo"), ValueFactoryImpl.getInstance().createValue("bar")),
                 new ValidationMessage(ValidationMessageSeverity.ERROR, String.format(JcrNodeTypeMetaDataImpl.MESSAGE_PROPERTY_NOT_ALLOWED,
                         "my:property1", "String", "my:nodeType3", JcrNodeTypeMetaDataImpl.CONSTRAINT_PROPERTY_NOT_ALLOWED), nodeContext));
 
         // constrained property
-        node.addProperty(nodeContext, ntManagerProvider.getNamePathResolver(), ntManagerProvider.getEffectiveNodeTypeProvider(),
+        MatcherAssert.assertThat(node.addProperty(nodeContext, ntManagerProvider.getNamePathResolver(), ntManagerProvider.getEffectiveNodeTypeProvider(),
                 ntManagerProvider.getNodeTypeDefinitionProvider(), ntManagerProvider.getItemDefinitionProvider(),
                 ValidationMessageSeverity.ERROR,
-                "my:constrainedStringProperty", false, ValueFactoryImpl.getInstance().createValue("prefix1foo"));
-        assertNoValidationErrors(root);
+                "my:constrainedStringProperty", false, ValueFactoryImpl.getInstance().createValue("prefix1foo")),
+                AnyValidationMessageMatcher.noValidationInCollection());
 
-        node.addProperty(nodeContext, ntManagerProvider.getNamePathResolver(), ntManagerProvider.getEffectiveNodeTypeProvider(),
+        MatcherAssert.assertThat(node.addProperty(nodeContext, ntManagerProvider.getNamePathResolver(), ntManagerProvider.getEffectiveNodeTypeProvider(),
                 ntManagerProvider.getNodeTypeDefinitionProvider(), ntManagerProvider.getItemDefinitionProvider(),
                 ValidationMessageSeverity.ERROR,
-                "my:constrainedStringProperty", false, ValueFactoryImpl.getInstance().createValue("foosuffix1"));
-        assertNoValidationErrors(root);
+                "my:constrainedStringProperty", false, ValueFactoryImpl.getInstance().createValue("foosuffix1")),
+                AnyValidationMessageMatcher.noValidationInCollection());
 
-        node.addProperty(nodeContext, ntManagerProvider.getNamePathResolver(), ntManagerProvider.getEffectiveNodeTypeProvider(),
+        ValidationExecutorTest.assertViolation( node.addProperty(nodeContext, ntManagerProvider.getNamePathResolver(), ntManagerProvider.getEffectiveNodeTypeProvider(),
                 ntManagerProvider.getNodeTypeDefinitionProvider(), ntManagerProvider.getItemDefinitionProvider(),
                 ValidationMessageSeverity.ERROR,
-                "my:constrainedStringProperty", false, ValueFactoryImpl.getInstance().createValue("foo"));
-        assertValidationMessage(node,
+                "my:constrainedStringProperty", false, ValueFactoryImpl.getInstance().createValue("foo")),
                 new ValidationMessage(ValidationMessageSeverity.ERROR, String.format(JcrNodeTypeMetaDataImpl.MESSAGE_PROPERTY_NOT_ALLOWED,
                         "my:constrainedStringProperty", "String", "my:nodeType3",
                         String.format(JcrNodeTypeMetaDataImpl.CONSTRAINT_PROPERTY_VALUE,
@@ -434,16 +428,4 @@ public class JcrNodeTypeMetaDataImplTest {
                         nodeContext));
     }
 
-    private static void assertNoValidationErrors(JcrNodeTypeMetaData node) {
-        Collection<ValidationMessage> messages = new ArrayList<>();
-        node.fetchAndClearValidationMessages(messages);
-        MatcherAssert.assertThat(messages, Matchers.empty());
-    }
-
-    private static void assertValidationMessage(JcrNodeTypeMetaData node, ValidationMessage... expectedMessages) {
-        Collection<ValidationMessage> actualMessages = new ArrayList<>();
-        node.fetchAndClearValidationMessages(actualMessages);
-        MatcherAssert.assertThat(actualMessages, Matchers.contains(expectedMessages));
-    }
-
 }
diff --git a/vault-validation/src/test/java/org/apache/jackrabbit/vault/validation/spi/impl/nodetype/NodeTypeValidatorTest.java b/vault-validation/src/test/java/org/apache/jackrabbit/vault/validation/spi/impl/nodetype/NodeTypeValidatorTest.java
index 3790f3c..382649e 100644
--- a/vault-validation/src/test/java/org/apache/jackrabbit/vault/validation/spi/impl/nodetype/NodeTypeValidatorTest.java
+++ b/vault-validation/src/test/java/org/apache/jackrabbit/vault/validation/spi/impl/nodetype/NodeTypeValidatorTest.java
@@ -48,9 +48,7 @@ import org.apache.jackrabbit.vault.validation.spi.ValidationMessageSeverity;
 import org.apache.jackrabbit.vault.validation.spi.util.NodeContextImpl;
 import org.apache.jackrabbit.vault.validation.spi.util.classloaderurl.URLFactory;
 import org.hamcrest.MatcherAssert;
-import org.junit.Assert;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 
 public class NodeTypeValidatorTest {
@@ -85,8 +83,7 @@ public class NodeTypeValidatorTest {
     }
 
     @Test
-    @Ignore
-    public void testValidateComplexUnstructuredNodeTypes() throws IOException, RepositoryException, ParseException, ConfigurationException {
+    public void testValidateNotAllowedProperties() throws IOException, RepositoryException, ParseException, ConfigurationException {
         NodeContext nodeContext = new NodeContextImpl("/apps/test/node4", Paths.get("node4"), Paths.get(""));
 
         Map<String, DocViewProperty> props = new HashMap<>();
@@ -96,16 +93,16 @@ public class NodeTypeValidatorTest {
                 new String[] { "nt:unstructured" }, false, PropertyType.STRING));
 
         // no primary type
-        DocViewNode node = new DocViewNode("jcr:root", "jcr:root", null, props, null, "sling:Folder");
-        Assert.assertNull(validator.validate(node, nodeContext, false));
+        DocViewNode node = new DocViewNode("jcr:root", "jcr:root", null, props, null, "nt:unstructured");
+        MatcherAssert.assertThat(validator.validate(node, nodeContext, false), AnyValidationMessageMatcher.noValidationInCollection());
 
         props.put(NameConstants.JCR_PRIMARYTYPE.toString(),
                 new DocViewProperty(NameConstants.JCR_PRIMARYTYPE.toString(), new String[] { "value1" }, false, PropertyType.STRING));
         node = new DocViewNode("test", "test", null, props, null, "nt:folder");
         ValidationExecutorTest.assertViolation(validator.validate(node, nodeContext, false),
                 new ValidationMessage(ValidationMessageSeverity.ERROR,
-                        String.format(JcrNodeTypeMetaDataImpl.MESSAGE_PROPERTY_NOT_ALLOWED, property, "nt:folder",
-                                "No property definition found for name!")));
+                        String.format(JcrNodeTypeMetaDataImpl.MESSAGE_PROPERTY_NOT_ALLOWED, "prop1", "String", "nt:folder",
+                                "No applicable property definition found for name and type!"), nodeContext));
     }
 
     @Test
@@ -138,15 +135,59 @@ public class NodeTypeValidatorTest {
         
         // nt:unstructured below nt:folder is not allowed
         DocViewNode node = new DocViewNode("jcr:root", "jcr:root", null, props, null, JcrConstants.NT_UNSTRUCTURED);
-        ValidationExecutorTest.assertViolation(validator.validate(node, nodeContext, false),
+        MatcherAssert.assertThat(validator.validate(node, nodeContext, false), AnyValidationMessageMatcher.noValidationInCollection());
+        ValidationExecutorTest.assertViolation(validator.done(),
                 new ValidationMessage(ValidationMessageSeverity.ERROR,
                         String.format(JcrNodeTypeMetaDataImpl.MESSAGE_CHILD_NODE_OF_NOT_CONTAINED_PARENT_POTENTIALLY_NOT_ALLOWED,
                                 "test", "nt:unstructured", JcrConstants.NT_FOLDER,
                                 "Node type does not allow arbitrary child nodes and does not allow this specific name and node type either!"), nodeContext));
+    }
+
+    // https://issues.apache.org/jira/browse/JCRVLT-527
+    @Test
+    public void testChildFolderBelowTypeNotAllowingNtFolder() {
+        NodeContext nodeContext = new NodeContextImpl("/apps/test", Paths.get("apps","test", ".content.xml"), Paths.get(""));
+        Map<String, DocViewProperty> props = new HashMap<>();
+        props.put(NameConstants.JCR_PRIMARYTYPE.toString(), new DocViewProperty(NameConstants.JCR_PRIMARYTYPE.toString(),
+                new String[] { "rep:AuthorizableFolder" }, false, PropertyType.STRING));
+        DocViewNode node = new DocViewNode("jcr:root", "jcr:root", null, props, null, "rep:AuthorizableFolder");
+        
+        MatcherAssert.assertThat(validator.validate(node, nodeContext, true), AnyValidationMessageMatcher.noValidationInCollection());
+        
+        // add child as folder first
+        nodeContext = new NodeContextImpl("/apps/test/child", Paths.get("apps", "test", "child"), Paths.get(""));
+        MatcherAssert.assertThat(validator.validateJcrPath(nodeContext, true, false), AnyValidationMessageMatcher.noValidationInCollection());
+        
+        // now refine type via .content.xml
+        nodeContext = new NodeContextImpl("/apps/test/child", Paths.get("apps", "test", "child", ".content.xml"), Paths.get(""));
+        props.put(NameConstants.JCR_PRIMARYTYPE.toString(), new DocViewProperty(NameConstants.JCR_PRIMARYTYPE.toString(),
+                new String[] { "rep:SystemUser" }, false, PropertyType.STRING));
+        props.put(NameConstants.REP_PRINCIPAL_NAME.toString(), new DocViewProperty(NameConstants.REP_PRINCIPAL_NAME.toString(),
+                new String[] { "mySystemUser" }, false, PropertyType.STRING));
+        node = new DocViewNode("jcr:root", "jcr:root", null, props, null, "rep:SystemUser");
+        
+        MatcherAssert.assertThat(validator.validate(node, nodeContext, true), AnyValidationMessageMatcher.noValidationInCollection());
         MatcherAssert.assertThat(validator.done(), AnyValidationMessageMatcher.noValidationInCollection());
     }
 
     @Test
+    public void testMissingMandatoryProperty() {
+        // now refine type via .content.xml
+        NodeContext nodeContext = new NodeContextImpl("/apps/test/child", Paths.get("apps", "test", "child", ".content.xml"), Paths.get(""));
+        Map<String, DocViewProperty> props = new HashMap<>();
+        props.put(NameConstants.JCR_PRIMARYTYPE.toString(), new DocViewProperty(NameConstants.JCR_PRIMARYTYPE.toString(),
+                new String[] { "rep:SystemUser" }, false, PropertyType.STRING));
+        DocViewNode node = new DocViewNode("jcr:root", "jcr:root", null, props, null, "rep:SystemUser");
+        
+        MatcherAssert.assertThat(validator.validate(node, nodeContext, true), AnyValidationMessageMatcher.noValidationInCollection());
+        
+        ValidationExecutorTest.assertViolation(validator.done(),
+                new ValidationMessage(ValidationMessageSeverity.ERROR,
+                        String.format(JcrNodeTypeMetaDataImpl.MESSAGE_MANDATORY_PROPERTY_MISSING,
+                                "rep:principalName", "rep:SystemUser", nodeContext.getNodePath()), nodeContext));
+    }
+
+    @Test
     public void testMissingMandatoryChildNode() {
         NodeContext nodeContext = new NodeContextImpl("/apps/test/node4", Paths.get("node4"), Paths.get(""));
 
@@ -181,7 +222,7 @@ public class NodeTypeValidatorTest {
     }
 
     @Test
-    public void testPropertyWitInconvertibleValue() {
+    public void testPropertyWithInconvertibleValue() {
         NodeContext nodeContext = new NodeContextImpl("/apps/test/node4", Paths.get("node4"), Paths.get(""));
 
         Map<String, DocViewProperty> props = new HashMap<>();