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/09/03 09:45:25 UTC

[jackrabbit-filevault] 01/01: JCRVLT-556 evaluate primary type for non-file type nodes

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

kwin pushed a commit to branch bugfix/JCRVLT-556-false-positive-for-non-osgiconfig-xml-files
in repository https://gitbox.apache.org/repos/asf/jackrabbit-filevault.git

commit e703e9c7221018bb277ea095a7053001731b2fb1
Author: Konrad Windszus <kw...@apache.org>
AuthorDate: Fri Sep 3 11:45:14 2021 +0200

    JCRVLT-556 evaluate primary type for non-file type nodes
    
    add integration test
---
 src/site/markdown/validation.md                    |   2 +-
 vault-validation/pom.xml                           |  12 ++
 .../vault/validation/ValidationExecutor.java       |   6 +-
 .../validation/spi/impl/PackageTypeValidator.java  |  63 ++++++---
 .../spi/impl/PackageTypeValidatorFactory.java      |  46 +++----
 .../vault/validation/it/AbstractValidationIT.java  | 148 +++++++++++++++++++++
 .../it/PackageFolderValidationContext.java         |  79 +++++++++++
 .../vault/validation/it/ValidationIT.java          |  34 +++++
 .../spi/impl/PackageTypeValidatorFactoryTest.java  |  17 ++-
 .../spi/impl/PackageTypeValidatorTest.java         |  93 +++++++++----
 .../META-INF/vault/filter.xml                      |  21 +++
 .../META-INF/vault/genericfile.txt                 |   1 +
 .../META-INF/vault/properties.xml                  |  36 +++++
 .../jcr_root/apps/.content.xml                     |  25 ++++
 .../jcr_root/apps/child1.xml                       |  24 ++++
 .../jcr_root/apps/genericfile.xml                  |   2 +
 .../jcr_root/apps/test/.content.xml                |  20 +++
 .../jcr_root/apps/test/config/.content.xml         |  20 +++
 .../jcr_root/apps/test/config/child1.xml           |  24 ++++
 19 files changed, 593 insertions(+), 80 deletions(-)

diff --git a/src/site/markdown/validation.md b/src/site/markdown/validation.md
index b8e1271..6b2d554 100644
--- a/src/site/markdown/validation.md
+++ b/src/site/markdown/validation.md
@@ -58,7 +58,7 @@ ID  |  Description | Options | Incremental Execution Limitations
 `jackrabbit-emptyelements` | Check for empty elements within DocView files (used for ordering purposes, compare with  [(extended) Document View Format](docview.html)) which are included in the filter with import=replace as those are actually not replaced! | none | none
 `jackrabbit-mergelimitations` | Checks for the limitation of import mode=merge outlined at [JCRVLT-255][jcrvlt-255]. | none | none
 `jackrabbit-oakindex` |  Checks if the package (potentially) modifies/creates an OakIndexDefinition. This is done by evaluating both the filter.xml for potential matches as well as the actual content for nodes with jcr:primaryType  `oak:indexDefinition`. | none | none
-`jackrabbit-packagetype` | Checks if the package type is correctly set for this package, i.e. is compliant with all rules outlined at [Package Types](packagetypes.html). | *jcrInstallerPathRegex*:  the regex of the paths which all OSGi bundles and configurations within packages must match (default=`/([^/]*/){0,4}?(install|config)[\\./].*\\.(xml|config|cfg|cfg\\.json|jar)`). This should match the paths being picked up by [JCR Installer](https://sling.apache.org/documentation/bundles/jcr-i [...]
+`jackrabbit-packagetype` | Checks if the package type is correctly set for this package, i.e. is compliant with all rules outlined at [Package Types](packagetypes.html). | *jcrInstallerNodePathRegex*:  the regex of the JCR paths which all OSGi bundles and configurations within packages must match (default=`/([^/]*/){0,4}?(install|config)[\\./].*`). This should match the paths being picked up by [JCR Installer](https://sling.apache.org/documentation/bundles/jcr-installer-provider.html).<b [...]
 `jackrabbit-nodetypes` | Checks if all non empty elements within [DocView files](docview.html) have the mandatory property `jcr:primaryType` set and follow the [node type definition of their given type](https://jackrabbit.apache.org/jcr/node-types.html). | *cnds*: A URI pointing to one or multiple [CNDs](https://jackrabbit.apache.org/jcr/node-type-notation.html) (separated by `,`) which define the additional namespaces and nodetypes used apart from the [default ones defined in JCR 2.0](h [...]
 `jackrabbit-accesscontrol` | Checks that [access control list nodes (primary type `rep:ACL`, `rep:CugPolicy` and `rep:PrincipalPolicy`)](https://jackrabbit.apache.org/oak/docs/security/accesscontrol/default.html#Representation_in_the_Repository) are only used when the [package property's](./properties.html) `acHandling` is set to something but `ignore` or `clear` and also that there is at least one access control list node otherwise | none | Validation message in case no access control l [...]
 
diff --git a/vault-validation/pom.xml b/vault-validation/pom.xml
index 61254ad..df5777b 100644
--- a/vault-validation/pom.xml
+++ b/vault-validation/pom.xml
@@ -77,6 +77,18 @@
                     </archive>
                 </configuration>
             </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-failsafe-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>integration-test</goal>
+                            <goal>verify</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
         </plugins>
     </build>
 
diff --git a/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/ValidationExecutor.java b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/ValidationExecutor.java
index 933013a..2195566 100644
--- a/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/ValidationExecutor.java
+++ b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/ValidationExecutor.java
@@ -139,7 +139,7 @@ public final class ValidationExecutor {
     }
 
     /** 
-     * Validates a package META-INF input stream  with all relevant validators.
+     * Validates a package's META-INF input stream  with all relevant validators.
      * 
      * @param input the input stream if it is a file or {@code null} in case it is called for a folder. It is not closed during processing, this is obligation of the caller. Should not be buffered as buffering is done internally! 
      * @param filePath should be relative to the META-INF directory (i.e. should not start with {@code META-INF})
@@ -160,7 +160,9 @@ public final class ValidationExecutor {
     }
 
     /** 
-     * Validates a package jcr_root input stream  with all relevant validators.
+     * Validates a package's input stream (below jcr_root, no metadata) with all relevant validators.
+     * <p>
+     * As some validators rely on the order of validated files, make sure to first call for streams representing the parent nodes (i.e. folders and .content.xml streams), before calling it for streams representing the children.
      * 
      * @param input the input stream if it is a file or {@code null} in case it is called for a folder. It is not closed during processing, this is obligation of the caller. Should not be buffered as buffering is done internally! 
      * @param filePath file path relative to the content package jcr root (i.e. the folder named "jcr_root")
diff --git a/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/impl/PackageTypeValidator.java b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/impl/PackageTypeValidator.java
index 75c8a99..5dd1841 100644
--- a/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/impl/PackageTypeValidator.java
+++ b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/impl/PackageTypeValidator.java
@@ -32,7 +32,8 @@ import org.apache.jackrabbit.vault.fs.api.WorkspaceFilter;
 import org.apache.jackrabbit.vault.packaging.PackageProperties;
 import org.apache.jackrabbit.vault.packaging.PackageType;
 import org.apache.jackrabbit.vault.util.Constants;
-import org.apache.jackrabbit.vault.validation.ValidationExecutor;
+import org.apache.jackrabbit.vault.util.DocViewNode;
+import org.apache.jackrabbit.vault.validation.spi.DocumentViewXmlValidator;
 import org.apache.jackrabbit.vault.validation.spi.FilterValidator;
 import org.apache.jackrabbit.vault.validation.spi.MetaInfPathValidator;
 import org.apache.jackrabbit.vault.validation.spi.NodeContext;
@@ -47,7 +48,7 @@ import org.jetbrains.annotations.Nullable;
 /** Checks if the package type is correctly set for this package
  * 
  * @see <a href="https://issues.apache.org/jira/browse/JCRVLT-170">JCRVLT-170</a> */
-public final class PackageTypeValidator implements NodePathValidator, FilterValidator, PropertiesValidator, MetaInfPathValidator {
+public final class PackageTypeValidator implements NodePathValidator, DocumentViewXmlValidator, FilterValidator, PropertiesValidator, MetaInfPathValidator {
 
     protected static final String MESSAGE_FILTER_HAS_INCLUDE_EXCLUDES = "Package of type '%s' is not supposed to contain includes/excludes below any of its filters!";
     protected static final String MESSAGE_UNSUPPORTED_SUB_PACKAGE_OF_TYPE = "Package of type '%s' must only contain sub packages of type '%s' but found subpackage of type '%s'!";
@@ -67,7 +68,8 @@ public final class PackageTypeValidator implements NodePathValidator, FilterVali
     private final @NotNull PackageType type;
     private final @NotNull ValidationMessageSeverity severity;
     private final @NotNull ValidationMessageSeverity severityForLegacyType;
-    private final @NotNull Pattern jcrInstallerPathRegex;
+    private final @NotNull Pattern jcrInstallerNodePathRegex;
+    private final @NotNull Pattern jcrInstallerAdditionalFileNodePathRegex;
     private final @Nullable ValidationContext containerValidationContext;
     private final ValidationMessageSeverity severityForNoPackageType;
     private final boolean prohibitMutableContent;
@@ -82,8 +84,8 @@ public final class PackageTypeValidator implements NodePathValidator, FilterVali
     public PackageTypeValidator(@NotNull WorkspaceFilter workspaceFilter, @NotNull ValidationMessageSeverity severity,
             @NotNull ValidationMessageSeverity severityForNoPackageType, @NotNull ValidationMessageSeverity severityForLegacyType,
             boolean prohibitMutableContent, boolean prohibitImmutableContent, boolean allowComplexFilterRulesInApplicationPackages,
-            boolean allowInstallHooksInApplicationPackages, @NotNull PackageType type, @NotNull Pattern jcrInstallerPathRegex, 
-            @NotNull Set<String> immutableRootNodeNames, @Nullable ValidationContext containerValidationContext) {
+            boolean allowInstallHooksInApplicationPackages, @NotNull PackageType type, @NotNull Pattern jcrInstallerNodePathRegex, 
+            Pattern jcrInstallerAdditionalFileNodePathRegex, @NotNull Set<String> immutableRootNodeNames, @Nullable ValidationContext containerValidationContext) {
         this.type = type;
         this.severity = severity;
         this.severityForNoPackageType = severityForNoPackageType;
@@ -92,7 +94,8 @@ public final class PackageTypeValidator implements NodePathValidator, FilterVali
         this.prohibitImmutableContent = prohibitImmutableContent;
         this.allowComplexFilterRulesInApplicationPackages = allowComplexFilterRulesInApplicationPackages;
         this.allowInstallHooksInApplicationPackages = allowInstallHooksInApplicationPackages;
-        this.jcrInstallerPathRegex = jcrInstallerPathRegex;
+        this.jcrInstallerNodePathRegex = jcrInstallerNodePathRegex;
+        this.jcrInstallerAdditionalFileNodePathRegex = jcrInstallerAdditionalFileNodePathRegex;
         this.immutableRootNodeNames = immutableRootNodeNames;
         this.containerValidationContext = containerValidationContext;
         this.filter = workspaceFilter;
@@ -100,15 +103,14 @@ public final class PackageTypeValidator implements NodePathValidator, FilterVali
         this.potentiallyDisallowedContainerNodes = new LinkedList<>();
     }
 
-    /**
-     * 
-     * @param filePath
-     * @return {@code true} in case the given file path is specifying 
-     */
-    boolean isOsgiBundleOrConfiguration(Path filePath) {
-        // to properly detect sling:OsgiConfig we need the extension from the file path (.xml)
-        String nodePath = ValidationExecutor.filePathToNodePath(filePath);
-        return jcrInstallerPathRegex.matcher(nodePath).matches();
+    private boolean isOsgiBundleOrConfigurationNode(String nodePath, boolean isFileNode) {
+        if (!jcrInstallerNodePathRegex.matcher(nodePath).matches()) {
+            return false;
+        }
+        if (isFileNode) {
+            return jcrInstallerAdditionalFileNodePathRegex.matcher(nodePath).matches();
+        }
+        return true;
     }
 
     static boolean isSubPackage(String nodePath) {
@@ -147,7 +149,7 @@ public final class PackageTypeValidator implements NodePathValidator, FilterVali
             if (isImmutableContent(nodeContext.getNodePath())) {
                 messages.add(new ValidationMessage(severity, String.format(MESSAGE_APP_CONTENT, type, immutableRootNodeNames.stream().collect(Collectors.joining("' or '", "'", "'")))));
             }
-            if (isOsgiBundleOrConfiguration(nodeContext.getFilePath())) {
+            if (isOsgiBundleOrConfigurationNode(nodeContext.getNodePath(), true)) {
                 messages.add(new ValidationMessage(severity, String.format(MESSAGE_NO_OSGI_BUNDLE_OR_CONFIG_ALLOWED, type)));
             }
             break;
@@ -155,14 +157,14 @@ public final class PackageTypeValidator implements NodePathValidator, FilterVali
             if (!isImmutableContent(nodeContext.getNodePath())) {
                 messages.add(new ValidationMessage(severity, String.format(MESSAGE_NO_APP_CONTENT_FOUND, type, immutableRootNodeNames.stream().collect(Collectors.joining("' or '", "'", "'")))));
             }
-            if (isOsgiBundleOrConfiguration(nodeContext.getFilePath())) {
+            if (isOsgiBundleOrConfigurationNode(nodeContext.getNodePath(), true)) {
                 messages.add(new ValidationMessage(severity, String.format(MESSAGE_NO_OSGI_BUNDLE_OR_CONFIG_ALLOWED, type)));
             }
             // sub packages are detected via validate(Properties) on the sub package
             break;
         case CONTAINER:
             // sling:OsgiConfig
-            if (isOsgiBundleOrConfiguration(nodeContext.getFilePath())) {
+            if (isOsgiBundleOrConfigurationNode(nodeContext.getNodePath(), true)) {
                 validContainerNodePaths.add(nodeContext.getNodePath());
             }
             else if (isSubPackage(nodeContext.getNodePath())) {
@@ -258,6 +260,31 @@ public final class PackageTypeValidator implements NodePathValidator, FilterVali
         return messages;
     }
 
+    
+    @Override
+    public @Nullable Collection<ValidationMessage> validate(@NotNull DocViewNode node, @NotNull NodeContext nodeContext,
+            boolean isRoot) {
+        Collection<ValidationMessage> messages = new LinkedList<>();
+        switch (type) {
+        case CONTENT:
+        case APPLICATION:
+            // is it sling:OsgiConfig node?
+            if ("sling:OsgiConfig".equals(node.primary) && isOsgiBundleOrConfigurationNode(nodeContext.getNodePath(), false)) {
+                messages.add(new ValidationMessage(severity, String.format(MESSAGE_NO_OSGI_BUNDLE_OR_CONFIG_ALLOWED, type)));
+            }
+            break;
+        case CONTAINER:
+            if ("sling:OsgiConfig".equals(node.primary) && isOsgiBundleOrConfigurationNode(nodeContext.getNodePath(), false)) {
+                validContainerNodePaths.add(nodeContext.getNodePath());
+            }
+            break;
+        case MIXED:
+            // no validations currently as most relaxed type
+            break;
+        }
+        return messages;
+    }
+
     static boolean hasIncludesOrExcludes(WorkspaceFilter filter) {
         for (PathFilterSet set : filter.getFilterSets()) {
             if (!set.getEntries().isEmpty()) {
diff --git a/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/impl/PackageTypeValidatorFactory.java b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/impl/PackageTypeValidatorFactory.java
index a5daff6..ef188e3 100644
--- a/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/impl/PackageTypeValidatorFactory.java
+++ b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/impl/PackageTypeValidatorFactory.java
@@ -41,26 +41,17 @@ public final class PackageTypeValidatorFactory implements ValidatorFactory {
      * The option to specify the regex of the node paths which all OSGi bundles and configuration within packages must match
      * @see <a href="https://sling.apache.org/documentation/bundles/jcr-installer-provider.html">JCR Installer</a>
      */
-    @Deprecated
     public static final String OPTION_JCR_INSTALLER_NODE_PATH_REGEX = "jcrInstallerNodePathRegex";
 
     /**
-     * The option to specify the regex of the file node paths which all OSGi bundles and configuration within packages must match
+     * The option to specify the regex of the <i>file</i> node paths which all OSGi bundles and configuration within packages 
+     * must match in addition to {@code jcrInstallerPathRegex}.
      * @see <a href="https://sling.apache.org/documentation/bundles/jcr-installer-provider.html">JCR Installer</a>
      * 
-     * Some artifacts are not based on file nodes (e.g. sling:OsgiConfig nodes).
+     * Some artifacts are not based on file nodes (e.g. sling:OsgiConfig nodes), those do not need to match this regex.
      */
-    @Deprecated
-    public static final String OPTION_ADDITIONAL_JCR_INSTALLER_FILE_NODE_PATH_REGEX = "additionalJcrInstallerFileNodePathRegex";
-    
-    /**
-     * The option to specify the regex of the paths which all OSGi bundles and configurations within packages must match
-     * 
-     * The path is a normalized absolute file path with "/" as separator and any <a href="https://jackrabbit.apache.org/filevault/vaultfs.html#Filename_escaping">filename escaping</a> unescaped
-     * to be able to also match against {@code sling:OsgiConfig} resources.
-     * @see <a href="https://sling.apache.org/documentation/bundles/jcr-installer-provider.html">JCR Installer</a>
-     */
-    public static final String OPTION_JCR_INSTALLER_PATH_REGEX = "jcrInstallerPathRegex";
+    public static final String OPTION_JCR_INSTALLER_ADDITIONAL_FILE_NODE_PATH_REGEX = "additionalJcrInstallerFileNodePathRegex";
+
 
     public static final String OPTION_SEVERITY_FOR_LEGACY_TYPE = "legacyTypeSeverity";
 
@@ -76,7 +67,9 @@ public final class PackageTypeValidatorFactory implements ValidatorFactory {
 
     private static final String OPTION_IMMUTABLE_ROOT_NODE_NAMES = "immutableRootNodeNames";
 
-    static final Pattern DEFAULT_JCR_INSTALLER_PATH_REGEX = Pattern.compile("/([^/]*/){0,4}?(install|config)[\\./].*\\.(xml|config|cfg|cfg\\.json|jar)");
+    static final Pattern DEFAULT_JCR_INSTALLER_NODE_PATH_REGEX = Pattern.compile("/([^/]*/){0,4}?(install|config)[\\./].*");
+
+    static final Pattern DEFAULT_JCR_INSTALLER_ADITIONAL_FILE_NODE_PATH_REGEX = Pattern.compile(".*\\.(config|cfg|cfg\\\\.json|jar)");
 
     private static final ValidationMessageSeverity DEFAULT_SEVERITY_FOR_LEGACY_TYPE = ValidationMessageSeverity.WARN;
     private static final ValidationMessageSeverity DEFAULT_SEVERITY_FOR_NO_TYPE = ValidationMessageSeverity.WARN;
@@ -91,18 +84,19 @@ public final class PackageTypeValidatorFactory implements ValidatorFactory {
     @Override
     public @Nullable Validator createValidator(@NotNull ValidationContext context, @NotNull ValidatorSettings settings) {
         // evaluate options
-        if (settings.getOptions().containsKey(OPTION_JCR_INSTALLER_NODE_PATH_REGEX)) {
-            log.warn("Using no longer supported option {}. Switch to option {} instead (slightly different semantics) as the old option is no longer evaluated!", OPTION_JCR_INSTALLER_NODE_PATH_REGEX, OPTION_JCR_INSTALLER_PATH_REGEX);
-        }
-        if (settings.getOptions().containsKey(OPTION_ADDITIONAL_JCR_INSTALLER_FILE_NODE_PATH_REGEX)) {
-            log.warn("Using no longer supported option {}. Switch to option {} instead (slightly different semantics) as the old option is no longer evaluated!", OPTION_ADDITIONAL_JCR_INSTALLER_FILE_NODE_PATH_REGEX, OPTION_JCR_INSTALLER_PATH_REGEX);
+        final Pattern jcrInstallerAdditionalFileNodePathRegex;
+        if (settings.getOptions().containsKey(OPTION_JCR_INSTALLER_ADDITIONAL_FILE_NODE_PATH_REGEX)) {
+            String optionValue = settings.getOptions().get(OPTION_JCR_INSTALLER_ADDITIONAL_FILE_NODE_PATH_REGEX);
+            jcrInstallerAdditionalFileNodePathRegex = Pattern.compile(optionValue);
+        } else {
+            jcrInstallerAdditionalFileNodePathRegex = DEFAULT_JCR_INSTALLER_ADITIONAL_FILE_NODE_PATH_REGEX;
         }
-        final Pattern jcrInstallerPathRegex;
-        if (settings.getOptions().containsKey(OPTION_JCR_INSTALLER_PATH_REGEX)) {
-            String optionValue = settings.getOptions().get(OPTION_JCR_INSTALLER_PATH_REGEX);
-            jcrInstallerPathRegex = Pattern.compile(optionValue);
+        final Pattern jcrInstallerNodePathRegex;
+        if (settings.getOptions().containsKey(OPTION_JCR_INSTALLER_NODE_PATH_REGEX)) {
+            String optionValue = settings.getOptions().get(OPTION_JCR_INSTALLER_NODE_PATH_REGEX);
+            jcrInstallerNodePathRegex = Pattern.compile(optionValue);
         } else {
-            jcrInstallerPathRegex = DEFAULT_JCR_INSTALLER_PATH_REGEX;
+            jcrInstallerNodePathRegex = DEFAULT_JCR_INSTALLER_NODE_PATH_REGEX;
         }
         final ValidationMessageSeverity severityForNoType;
         if (settings.getOptions().containsKey(OPTION_SEVERITY_FOR_NO_TYPE)) {
@@ -150,7 +144,7 @@ public final class PackageTypeValidatorFactory implements ValidatorFactory {
             immutableRootNodeNames = DEFAULT_IMMUTABLE_ROOT_NODE_NAMES;
         }
         @NotNull PackageType packageType = (context.getProperties().getPackageType() != null) ? context.getProperties().getPackageType() : PackageType.MIXED;
-        return new PackageTypeValidator(context.getFilter(), settings.getDefaultSeverity(), severityForNoType, severityForLegacyType, prohibitMutableContent, prohibitImmutableContent, allowComplexFilterRulesInApplicationPackages, allowInstallHooksInApplicationPackages, packageType, jcrInstallerPathRegex, immutableRootNodeNames, context.getContainerValidationContext());
+        return new PackageTypeValidator(context.getFilter(), settings.getDefaultSeverity(), severityForNoType, severityForLegacyType, prohibitMutableContent, prohibitImmutableContent, allowComplexFilterRulesInApplicationPackages, allowInstallHooksInApplicationPackages, packageType, jcrInstallerNodePathRegex, jcrInstallerAdditionalFileNodePathRegex, immutableRootNodeNames, context.getContainerValidationContext());
     }
 
     @Override
diff --git a/vault-validation/src/test/java/org/apache/jackrabbit/vault/validation/it/AbstractValidationIT.java b/vault-validation/src/test/java/org/apache/jackrabbit/vault/validation/it/AbstractValidationIT.java
new file mode 100644
index 0000000..ec7f882
--- /dev/null
+++ b/vault-validation/src/test/java/org/apache/jackrabbit/vault/validation/it/AbstractValidationIT.java
@@ -0,0 +1,148 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.vault.validation.it;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UncheckedIOException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedList;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.apache.jackrabbit.vault.fs.config.ConfigurationException;
+import org.apache.jackrabbit.vault.util.Constants;
+import org.apache.jackrabbit.vault.validation.ValidationExecutor;
+import org.apache.jackrabbit.vault.validation.ValidationExecutorFactory;
+import org.apache.jackrabbit.vault.validation.ValidationViolation;
+import org.apache.jackrabbit.vault.validation.spi.ValidationContext;
+import org.apache.jackrabbit.vault.validation.spi.ValidationMessageSeverity;
+import org.jetbrains.annotations.NotNull;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/** Base class for integration tests */
+public abstract class AbstractValidationIT {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(AbstractValidationIT.class);
+
+    public @NotNull Collection<ValidationViolation> validatePackageFolder(String resourceName) throws URISyntaxException, IOException, ConfigurationException {
+        URL resourceUrl = AbstractValidationIT.class.getResource(resourceName);
+        if (resourceUrl == null) {
+            throw new IllegalArgumentException("Could not find resource with name" + resourceName);
+        }
+        Path path = Paths.get(resourceUrl.toURI());
+        Collection<ValidationViolation> violations = validatePackageFolder(path);
+        violations = violations.stream().filter(v -> v.getSeverity().ordinal() > ValidationMessageSeverity.INFO.ordinal()).collect(Collectors.toList());
+        violations.forEach(v -> LOGGER.info(v.toString()));
+        return violations;
+    }
+
+    public @NotNull Collection<ValidationViolation> validatePackageFolder(Path rootPath) throws IOException, ConfigurationException {
+        ValidationExecutorFactory executorFactory = new ValidationExecutorFactory(Thread.currentThread().getContextClassLoader());
+        ValidationContext context = new PackageFolderValidationContext(rootPath);
+        ValidationExecutor executor = executorFactory.createValidationExecutor(context, false, false, Collections.emptyMap());
+        return validatePackageFolder(executor, rootPath);
+    }
+
+    private static @NotNull Collection<ValidationViolation> validatePackageFolder(ValidationExecutor executor, Path rootPath) throws IOException {
+        Collection<ValidationViolation> violations = new LinkedList<>();
+        try (Stream<Path> files = Files.walk(rootPath).skip(1).sorted(new ParentAndDotContentXmlFirstComparator())) {
+            files.forEach(file -> {
+                try {
+                    violations.addAll(validateFile(executor, rootPath, file));
+                } catch (IOException e) {
+                    throw new UncheckedIOException(e);
+                }
+            });
+        } catch (UncheckedIOException e) {
+            throw e.getCause();
+        }
+        return violations;
+    }
+
+    private static Collection<ValidationViolation> validateFile(ValidationExecutor executor, Path rootPath, Path file) throws IOException {
+        int rootPathLevel = rootPath.getNameCount();
+        if (file.getNameCount() < rootPathLevel + 2) {
+            return Collections.emptyList();
+        }
+        String topLevelFolderName = file.getName(rootPathLevel).toString();
+        final boolean isMetaInf;
+        final Path basePath = file.subpath(0, rootPathLevel+1);
+        final Path relativeFilePath = file.subpath(rootPathLevel + 1, file.getNameCount());
+        if (topLevelFolderName.equals(Constants.META_INF)) {
+            isMetaInf = true;
+        } else if (topLevelFolderName.equals(Constants.ROOT_DIR)) {
+            isMetaInf = false;
+        } else {
+            throw new IllegalArgumentException("Unexpected folder with name " + topLevelFolderName + " below " + rootPath);
+        }
+        if (Files.isDirectory(file)) {
+            return validateStream(executor, isMetaInf, null, relativeFilePath, basePath);
+        } else {
+            try (InputStream inputStream = Files.newInputStream(file)) {
+                return validateStream(executor, isMetaInf, inputStream, relativeFilePath, basePath);
+            }
+        }
+    }
+
+    private static @NotNull Collection<ValidationViolation> validateStream(ValidationExecutor executor, boolean isMetaInf, InputStream input, Path relativeFilePath, Path basePath) throws IOException {
+        if (isMetaInf) {
+            return executor.validateMetaInf(input, relativeFilePath, basePath);
+        } else {
+            return executor.validateJcrRoot(input, relativeFilePath, basePath);
+        }
+    }
+
+    /** 
+     * Comparator on file paths which makes sure that parent folders and files named {@code .content.xml} come first.
+     */
+    static final class ParentAndDotContentXmlFirstComparator implements Comparator<Path> {
+        
+        @Override
+        public int compare(Path path1, Path path2) {
+            if (path2.startsWith(path1)) {
+                return -1;
+            } else if (path1.startsWith(path2)) {
+                return 1;
+            } else if (path1.getParent().equals(path2.getParent())) {
+                // in same branch?
+                String s1 = path1.getFileName().toString();
+                String s2 = path2.getFileName().toString();
+                if (Constants.DOT_CONTENT_XML.equals(s1) && Constants.DOT_CONTENT_XML.equals(s2)) {
+                    return 0;
+                } else if (Constants.DOT_CONTENT_XML.equals(s1)) {
+                    return -1;
+                } else if (Constants.DOT_CONTENT_XML.equals(s2)) {
+                    return 1;
+                }
+                // lexicographical order for sibling files
+                return s1.compareTo(s2);
+            } else {
+                // in different branches of the folder tree
+                return -1;
+            }
+        }
+    }
+}
diff --git a/vault-validation/src/test/java/org/apache/jackrabbit/vault/validation/it/PackageFolderValidationContext.java b/vault-validation/src/test/java/org/apache/jackrabbit/vault/validation/it/PackageFolderValidationContext.java
new file mode 100644
index 0000000..43267b1
--- /dev/null
+++ b/vault-validation/src/test/java/org/apache/jackrabbit/vault/validation/it/PackageFolderValidationContext.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.vault.validation.it;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Collection;
+import java.util.Collections;
+
+import org.apache.jackrabbit.vault.fs.api.WorkspaceFilter;
+import org.apache.jackrabbit.vault.fs.config.ConfigurationException;
+import org.apache.jackrabbit.vault.fs.config.DefaultWorkspaceFilter;
+import org.apache.jackrabbit.vault.packaging.PackageInfo;
+import org.apache.jackrabbit.vault.packaging.PackageProperties;
+import org.apache.jackrabbit.vault.packaging.impl.DefaultPackageProperties;
+import org.apache.jackrabbit.vault.util.Constants;
+import org.apache.jackrabbit.vault.validation.spi.ValidationContext;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public class PackageFolderValidationContext implements ValidationContext {
+
+    private final Path rootFolder;
+    private final DefaultWorkspaceFilter filter;
+    private final PackageProperties properties;
+
+    public PackageFolderValidationContext(Path rootFolder) throws IOException, ConfigurationException {
+        this.rootFolder = rootFolder;
+        filter = new DefaultWorkspaceFilter();
+        try (InputStream input = Files.newInputStream(rootFolder.resolve(Paths.get(Constants.META_DIR,Constants.FILTER_XML)))) {
+            filter.load(input);
+        }
+        properties = DefaultPackageProperties.fromFile(rootFolder.resolve(Paths.get(Constants.META_DIR,Constants.PROPERTIES_XML)));
+    }
+
+    @Override
+    public @NotNull WorkspaceFilter getFilter() {
+        return filter;
+    }
+
+    @Override
+    public @NotNull PackageProperties getProperties() {
+        return properties;
+    }
+
+    @Override
+    public @Nullable ValidationContext getContainerValidationContext() {
+        return null;
+    }
+
+    @Override
+    public @NotNull Path getPackageRootPath() {
+        return rootFolder;
+    }
+
+    @Override
+    public @NotNull Collection<PackageInfo> getDependenciesPackageInfo() {
+        // TODO Auto-generated method stub
+        return Collections.emptyList();
+    }
+
+}
diff --git a/vault-validation/src/test/java/org/apache/jackrabbit/vault/validation/it/ValidationIT.java b/vault-validation/src/test/java/org/apache/jackrabbit/vault/validation/it/ValidationIT.java
new file mode 100644
index 0000000..fff9eda
--- /dev/null
+++ b/vault-validation/src/test/java/org/apache/jackrabbit/vault/validation/it/ValidationIT.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.vault.validation.it;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+
+import org.apache.jackrabbit.vault.fs.config.ConfigurationException;
+import org.apache.jackrabbit.vault.validation.ValidationViolation;
+import org.hamcrest.MatcherAssert;
+import org.hamcrest.collection.IsEmptyCollection;
+import org.junit.Test;
+
+public class ValidationIT extends AbstractValidationIT {
+
+    @Test
+    public void testPackageWithNoValidationIssues() throws URISyntaxException, IOException, ConfigurationException {
+        MatcherAssert.assertThat(validatePackageFolder("/no-validation-issue-package"), new IsEmptyCollection<ValidationViolation>());
+    }
+}
diff --git a/vault-validation/src/test/java/org/apache/jackrabbit/vault/validation/spi/impl/PackageTypeValidatorFactoryTest.java b/vault-validation/src/test/java/org/apache/jackrabbit/vault/validation/spi/impl/PackageTypeValidatorFactoryTest.java
index 8aac76c..42d3991 100644
--- a/vault-validation/src/test/java/org/apache/jackrabbit/vault/validation/spi/impl/PackageTypeValidatorFactoryTest.java
+++ b/vault-validation/src/test/java/org/apache/jackrabbit/vault/validation/spi/impl/PackageTypeValidatorFactoryTest.java
@@ -23,10 +23,17 @@ public class PackageTypeValidatorFactoryTest {
 
     @Test
     public void testDefaultJcrInstallerNodePathRegex() {
-        Assert.assertTrue(PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_PATH_REGEX.matcher("/apps/test/install/mypackage.jar").matches());
-        Assert.assertFalse(PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_PATH_REGEX.matcher("/apps/child/grandchild/grandgrandchild/install.runmode1.runmode2/30/mypackage.zip").matches()); // subpackages are detected separately
-        Assert.assertTrue(PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_PATH_REGEX.matcher("/apps/test/install/15/mypackage.jar").matches());
-        Assert.assertTrue(PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_PATH_REGEX.matcher("/apps/test/install/bla.xml").matches()); // assume sling:OsgiConfig  property
-        Assert.assertFalse(PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_PATH_REGEX.matcher("/apps/test/installother").matches());
+        Assert.assertTrue(PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_NODE_PATH_REGEX.matcher("/apps/test/install/mypackage.jar").matches());
+        Assert.assertTrue(PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_NODE_PATH_REGEX.matcher("/apps/child/grandchild/grandgrandchild/install.runmode1.runmode2/30/mypackage.zip").matches());
+        Assert.assertTrue(PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_NODE_PATH_REGEX.matcher("/apps/test/install/15/mypackage.jar").matches());
+        Assert.assertTrue(PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_NODE_PATH_REGEX.matcher("/apps/test/install/bla.xml").matches()); // assume sling:OsgiConfig  property
+        Assert.assertFalse(PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_NODE_PATH_REGEX.matcher("/apps/test/installother").matches());
+    }
+
+    @Test
+    public void testDefaultJcrInstallerAdditionalNodePathRegex() {
+        Assert.assertFalse(PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_ADITIONAL_FILE_NODE_PATH_REGEX.matcher("/apps/test/install/myconfig.xml").matches());
+        Assert.assertFalse(PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_ADITIONAL_FILE_NODE_PATH_REGEX.matcher("/apps/test/install/.content.xml").matches());
+        Assert.assertTrue(PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_ADITIONAL_FILE_NODE_PATH_REGEX.matcher("/apps/test/install/15/mypackage.jar").matches());
     }
 }
diff --git a/vault-validation/src/test/java/org/apache/jackrabbit/vault/validation/spi/impl/PackageTypeValidatorTest.java b/vault-validation/src/test/java/org/apache/jackrabbit/vault/validation/spi/impl/PackageTypeValidatorTest.java
index 4fe0eeb..4c618f7 100644
--- a/vault-validation/src/test/java/org/apache/jackrabbit/vault/validation/spi/impl/PackageTypeValidatorTest.java
+++ b/vault-validation/src/test/java/org/apache/jackrabbit/vault/validation/spi/impl/PackageTypeValidatorTest.java
@@ -32,8 +32,10 @@ import org.apache.jackrabbit.vault.fs.config.DefaultWorkspaceFilter;
 import org.apache.jackrabbit.vault.packaging.Dependency;
 import org.apache.jackrabbit.vault.packaging.PackageProperties;
 import org.apache.jackrabbit.vault.packaging.PackageType;
+import org.apache.jackrabbit.vault.util.DocViewNode;
 import org.apache.jackrabbit.vault.validation.AnyValidationMessageMatcher;
 import org.apache.jackrabbit.vault.validation.ValidationExecutorTest;
+import org.apache.jackrabbit.vault.validation.spi.NodeContext;
 import org.apache.jackrabbit.vault.validation.spi.ValidationContext;
 import org.apache.jackrabbit.vault.validation.spi.ValidationMessage;
 import org.apache.jackrabbit.vault.validation.spi.ValidationMessageSeverity;
@@ -75,7 +77,7 @@ public class PackageTypeValidatorTest {
 
     @Test
     public void testMixedPackageType() {
-        validator = new PackageTypeValidator(filter, ValidationMessageSeverity.ERROR, ValidationMessageSeverity.WARN, ValidationMessageSeverity.ERROR, false, false, false, false, PackageType.MIXED, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_IMMUTABLE_ROOT_NODE_NAMES, null);
+        validator = new PackageTypeValidator(filter, ValidationMessageSeverity.ERROR, ValidationMessageSeverity.WARN, ValidationMessageSeverity.ERROR, false, false, false, false, PackageType.MIXED, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_NODE_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_ADITIONAL_FILE_NODE_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_IMMUTABLE_ROOT_NODE_NAMES, null);
         MatcherAssert.assertThat(validator.validate(new NodeContextImpl("/apps/some/node", Paths.get(""), Paths.get(""))), AnyValidationMessageMatcher.noValidationInCollection());
         MatcherAssert.assertThat(validator.validate(filter), AnyValidationMessageMatcher.noValidationInCollection());
         ValidationExecutorTest.assertViolation(validator.validate(properties), new ValidationMessage(ValidationMessageSeverity.WARN, PackageTypeValidator.MESSAGE_NO_PACKAGE_TYPE_SET));
@@ -87,18 +89,18 @@ public class PackageTypeValidatorTest {
         
         // validate sub packages of type Content
         Mockito.when(parentContainerProperties.getPackageType()).thenReturn(PackageType.MIXED);
-        PackageTypeValidator subPackageValidator = new PackageTypeValidator(filter, ValidationMessageSeverity.ERROR, ValidationMessageSeverity.WARN, ValidationMessageSeverity.INFO, false, false, false, false, PackageType.CONTENT, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_IMMUTABLE_ROOT_NODE_NAMES, parentContainerContext);
+        PackageTypeValidator subPackageValidator = new PackageTypeValidator(filter, ValidationMessageSeverity.ERROR, ValidationMessageSeverity.WARN, ValidationMessageSeverity.INFO, false, false, false, false, PackageType.CONTENT, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_NODE_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_ADITIONAL_FILE_NODE_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_IMMUTABLE_ROOT_NODE_NAMES, parentContainerContext);
         Mockito.when(properties.getPackageType()).thenReturn(PackageType.CONTENT);
         MatcherAssert.assertThat(subPackageValidator.validate(properties), AnyValidationMessageMatcher.noValidationInCollection());
         // validate sub packages of type Application
-        subPackageValidator = new PackageTypeValidator(filter, ValidationMessageSeverity.ERROR, ValidationMessageSeverity.WARN, ValidationMessageSeverity.INFO, false, false, false, false, PackageType.APPLICATION, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_IMMUTABLE_ROOT_NODE_NAMES, parentContainerContext);
+        subPackageValidator = new PackageTypeValidator(filter, ValidationMessageSeverity.ERROR, ValidationMessageSeverity.WARN, ValidationMessageSeverity.INFO, false, false, false, false, PackageType.APPLICATION, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_NODE_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_ADITIONAL_FILE_NODE_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_IMMUTABLE_ROOT_NODE_NAMES, parentContainerContext);
         Mockito.when(properties.getPackageType()).thenReturn(PackageType.APPLICATION);
         MatcherAssert.assertThat(subPackageValidator.validate(properties), AnyValidationMessageMatcher.noValidationInCollection());
     }
 
     @Test
     public void testContentPackageType() {
-        validator = new PackageTypeValidator(filter, ValidationMessageSeverity.ERROR, ValidationMessageSeverity.WARN, ValidationMessageSeverity.INFO, false, false, false, false, PackageType.CONTENT, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_IMMUTABLE_ROOT_NODE_NAMES, null);
+        validator = new PackageTypeValidator(filter, ValidationMessageSeverity.ERROR, ValidationMessageSeverity.WARN, ValidationMessageSeverity.INFO, false, false, false, false, PackageType.CONTENT, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_NODE_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_ADITIONAL_FILE_NODE_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_IMMUTABLE_ROOT_NODE_NAMES, null);
         ValidationExecutorTest.assertViolation(validator.validate(new NodeContextImpl("/apps/some/node", Paths.get(""), Paths.get(""))), new ValidationMessage(ValidationMessageSeverity.ERROR, String.format(PackageTypeValidator.MESSAGE_APP_CONTENT, PackageType.CONTENT, "'apps' or 'libs'")));
         ValidationExecutorTest.assertViolation(validator.validate(new NodeContextImpl("/apps", Paths.get(""), Paths.get(""))), new ValidationMessage(ValidationMessageSeverity.ERROR, String.format(PackageTypeValidator.MESSAGE_APP_CONTENT, PackageType.CONTENT, "'apps' or 'libs'")));
         ValidationExecutorTest.assertViolation(validator.validate(new NodeContextImpl("/libs/some/node", Paths.get(""), Paths.get(""))), new ValidationMessage(ValidationMessageSeverity.ERROR, String.format(PackageTypeValidator.MESSAGE_APP_CONTENT, PackageType.CONTENT, "'apps' or 'libs'")));
@@ -115,14 +117,26 @@ public class PackageTypeValidatorTest {
         MatcherAssert.assertThat(validator.validate(properties), AnyValidationMessageMatcher.noValidationInCollection());
         Mockito.when(parentContainerProperties.getPackageType()).thenReturn(PackageType.CONTENT);
         // validate sub packages of type Content
-        PackageTypeValidator subPackageValidator = new PackageTypeValidator(filter, ValidationMessageSeverity.ERROR, ValidationMessageSeverity.WARN, ValidationMessageSeverity.INFO, false, false, false, false, PackageType.CONTENT, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_IMMUTABLE_ROOT_NODE_NAMES, parentContainerContext);
+        PackageTypeValidator subPackageValidator = new PackageTypeValidator(filter, ValidationMessageSeverity.ERROR, ValidationMessageSeverity.WARN, ValidationMessageSeverity.INFO, false, false, false, false, PackageType.CONTENT, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_NODE_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_ADITIONAL_FILE_NODE_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_IMMUTABLE_ROOT_NODE_NAMES, parentContainerContext);
         MatcherAssert.assertThat(subPackageValidator.validate(properties), AnyValidationMessageMatcher.noValidationInCollection());
+
         // validate sling:OsgiConfig node
-        ValidationExecutorTest.assertViolation(validator.validate(new NodeContextImpl("/content/config/someconfigpid", Paths.get("apps", "config", "someconfigpid.xml"), Paths.get(""))), 
-                new ValidationMessage(ValidationMessageSeverity.ERROR, String.format(PackageTypeValidator.MESSAGE_NO_OSGI_BUNDLE_OR_CONFIG_ALLOWED, PackageType.CONTENT, "/content/config/someconfigpid")));
+        NodeContext context = new NodeContextImpl("/content/config/someconfigpid", Paths.get("content", "config", "someconfigpid.xml"), Paths.get(""));
+        MatcherAssert.assertThat(validator.validate(context), AnyValidationMessageMatcher.noValidationInCollection());
+        ValidationExecutorTest.assertViolation(validator.validate(
+            new DocViewNode("jcr:root", "jcr:root", null, Collections.emptyMap(), null, "sling:OsgiConfig"),
+            context,
+            true),
+            new ValidationMessage(ValidationMessageSeverity.ERROR, String.format(PackageTypeValidator.MESSAGE_NO_OSGI_BUNDLE_OR_CONFIG_ALLOWED, PackageType.CONTENT)));
+        // validate other type docview node below config folder
+        MatcherAssert.assertThat(validator.validate(
+                new DocViewNode("jcr:root", "jcr:root", null, Collections.emptyMap(), null, "nt:unstructured"),
+                context,
+                true),
+                AnyValidationMessageMatcher.noValidationInCollection());
         
         // validate sub packages of type Application
-        subPackageValidator = new PackageTypeValidator(filter, ValidationMessageSeverity.ERROR, ValidationMessageSeverity.WARN, ValidationMessageSeverity.INFO, false, false, false, false, PackageType.APPLICATION, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_IMMUTABLE_ROOT_NODE_NAMES, parentContainerContext);
+        subPackageValidator = new PackageTypeValidator(filter, ValidationMessageSeverity.ERROR, ValidationMessageSeverity.WARN, ValidationMessageSeverity.INFO, false, false, false, false, PackageType.APPLICATION, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_NODE_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_ADITIONAL_FILE_NODE_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_IMMUTABLE_ROOT_NODE_NAMES, parentContainerContext);
         Mockito.when(properties.getPackageType()).thenReturn(PackageType.APPLICATION);
         ValidationExecutorTest.assertViolation(subPackageValidator.validate(properties), new ValidationMessage(ValidationMessageSeverity.ERROR, String.format(PackageTypeValidator.MESSAGE_UNSUPPORTED_SUB_PACKAGE_OF_TYPE, PackageType.CONTENT, PackageType.CONTENT, PackageType.APPLICATION)));
     }
@@ -130,7 +144,7 @@ public class PackageTypeValidatorTest {
     @Test
     public void testContainerPackageType() {
         // regular nodes not allowed!
-        validator = new PackageTypeValidator(filter, ValidationMessageSeverity.ERROR, ValidationMessageSeverity.WARN, ValidationMessageSeverity.INFO, false, false, false, false, PackageType.CONTAINER, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_IMMUTABLE_ROOT_NODE_NAMES, null);
+        validator = new PackageTypeValidator(filter, ValidationMessageSeverity.ERROR, ValidationMessageSeverity.WARN, ValidationMessageSeverity.INFO, false, false, false, false, PackageType.CONTAINER, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_NODE_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_ADITIONAL_FILE_NODE_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_IMMUTABLE_ROOT_NODE_NAMES, null);
         MatcherAssert.assertThat(validator.validate(new NodeContextImpl("/apps/some/node", Paths.get("some", "file1"), Paths.get("base"))), AnyValidationMessageMatcher.noValidationInCollection());
         MatcherAssert.assertThat(validator.validate(new NodeContextImpl("/libs/some/node", Paths.get("some", "file2"), Paths.get("base"))), AnyValidationMessageMatcher.noValidationInCollection());
         MatcherAssert.assertThat(validator.validate(new NodeContextImpl("/content/some/node", Paths.get("some", "file3"), Paths.get("base"))), AnyValidationMessageMatcher.noValidationInCollection());
@@ -141,7 +155,7 @@ public class PackageTypeValidatorTest {
                 );
 
         // empty folder should lead to validation error
-        validator = new PackageTypeValidator(filter, ValidationMessageSeverity.ERROR, ValidationMessageSeverity.WARN, ValidationMessageSeverity.INFO, false, false, false, false, PackageType.CONTAINER, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_IMMUTABLE_ROOT_NODE_NAMES, null);
+        validator = new PackageTypeValidator(filter, ValidationMessageSeverity.ERROR, ValidationMessageSeverity.WARN, ValidationMessageSeverity.INFO, false, false, false, false, PackageType.CONTAINER, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_NODE_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_ADITIONAL_FILE_NODE_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_IMMUTABLE_ROOT_NODE_NAMES, null);
         MatcherAssert.assertThat(validator.validate(new NodeContextImpl("/apps/install.runmode", Paths.get("runmode"), Paths.get("base"))), AnyValidationMessageMatcher.noValidationInCollection());
         MatcherAssert.assertThat(validator.validate(new NodeContextImpl("/apps/install.runmode/somebundle.jar", Paths.get("apps", "install.runmode", "somebundle.jar"), Paths.get("base"))), AnyValidationMessageMatcher.noValidationInCollection());
         MatcherAssert.assertThat(validator.validate(new NodeContextImpl("/etc/packages/some/sub/package.zip", Paths.get(""), Paths.get(""))), AnyValidationMessageMatcher.noValidationInCollection());
@@ -152,7 +166,7 @@ public class PackageTypeValidatorTest {
                 new ValidationMessage(ValidationMessageSeverity.ERROR, String.format(PackageTypeValidator.MESSAGE_ONLY_OSGI_BUNDLE_OR_CONFIG_OR_SUBPACKAGE_ALLOWED, PackageType.CONTAINER), "/apps/install.runmode2", Paths.get("apps", "install", "runmode2"), Paths.get("base"), null)
         );
 
-        validator = new PackageTypeValidator(filter, ValidationMessageSeverity.ERROR, ValidationMessageSeverity.WARN, ValidationMessageSeverity.INFO, false, false, false, false, PackageType.CONTAINER, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_IMMUTABLE_ROOT_NODE_NAMES, null);
+        validator = new PackageTypeValidator(filter, ValidationMessageSeverity.ERROR, ValidationMessageSeverity.WARN, ValidationMessageSeverity.INFO, false, false, false, false, PackageType.CONTAINER, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_NODE_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_ADITIONAL_FILE_NODE_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_IMMUTABLE_ROOT_NODE_NAMES, null);
         Mockito.when(properties.getPackageType()).thenReturn(PackageType.CONTAINER);
         MatcherAssert.assertThat(validator.validate(properties), AnyValidationMessageMatcher.noValidationInCollection());
 
@@ -160,26 +174,38 @@ public class PackageTypeValidatorTest {
         
         // validate sub packages of type Mixed
         Mockito.when(properties.getPackageType()).thenReturn(PackageType.MIXED);
-        PackageTypeValidator subPackageValidator = new PackageTypeValidator(filter, ValidationMessageSeverity.ERROR, ValidationMessageSeverity.WARN, ValidationMessageSeverity.INFO, false, false, false, false, PackageType.MIXED, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_IMMUTABLE_ROOT_NODE_NAMES, parentContainerContext);
+        PackageTypeValidator subPackageValidator = new PackageTypeValidator(filter, ValidationMessageSeverity.ERROR, ValidationMessageSeverity.WARN, ValidationMessageSeverity.INFO, false, false, false, false, PackageType.MIXED, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_NODE_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_ADITIONAL_FILE_NODE_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_IMMUTABLE_ROOT_NODE_NAMES, parentContainerContext);
         ValidationExecutorTest.assertViolation(subPackageValidator.validate(properties), new ValidationMessage(ValidationMessageSeverity.ERROR, String.format(PackageTypeValidator.MESSAGE_UNSUPPORTED_SUB_PACKAGE_OF_TYPE, PackageType.CONTAINER, StringUtils.join(new String[] {PackageType.APPLICATION.toString(),PackageType.CONTENT.toString(),PackageType.CONTAINER.toString()}, ", "),  PackageType.MIXED)));
 
         // validate sub packages of type Content
-        subPackageValidator = new PackageTypeValidator(filter, ValidationMessageSeverity.ERROR, ValidationMessageSeverity.WARN, ValidationMessageSeverity.INFO, false, false, false, false,  PackageType.CONTENT, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_IMMUTABLE_ROOT_NODE_NAMES, parentContainerContext);
+        subPackageValidator = new PackageTypeValidator(filter, ValidationMessageSeverity.ERROR, ValidationMessageSeverity.WARN, ValidationMessageSeverity.INFO, false, false, false, false,  PackageType.CONTENT, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_NODE_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_ADITIONAL_FILE_NODE_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_IMMUTABLE_ROOT_NODE_NAMES, parentContainerContext);
         Mockito.when(properties.getPackageType()).thenReturn(PackageType.CONTENT);
         MatcherAssert.assertThat(subPackageValidator.validate(properties), AnyValidationMessageMatcher.noValidationInCollection());
 
         // validate sub packages of type Container
-        subPackageValidator = new PackageTypeValidator(filter, ValidationMessageSeverity.ERROR, ValidationMessageSeverity.WARN, ValidationMessageSeverity.INFO, false, false, false, false, PackageType.CONTAINER, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_IMMUTABLE_ROOT_NODE_NAMES, parentContainerContext);
+        subPackageValidator = new PackageTypeValidator(filter, ValidationMessageSeverity.ERROR, ValidationMessageSeverity.WARN, ValidationMessageSeverity.INFO, false, false, false, false, PackageType.CONTAINER, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_NODE_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_ADITIONAL_FILE_NODE_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_IMMUTABLE_ROOT_NODE_NAMES, parentContainerContext);
         Mockito.when(properties.getPackageType()).thenReturn(PackageType.APPLICATION);
         MatcherAssert.assertThat(subPackageValidator.validate(properties), AnyValidationMessageMatcher.noValidationInCollection());
 
         // validate sub packages of type Application
-        subPackageValidator = new PackageTypeValidator(filter, ValidationMessageSeverity.ERROR, ValidationMessageSeverity.WARN, ValidationMessageSeverity.INFO, false, false, false, false, PackageType.APPLICATION, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_IMMUTABLE_ROOT_NODE_NAMES, parentContainerContext);
+        subPackageValidator = new PackageTypeValidator(filter, ValidationMessageSeverity.ERROR, ValidationMessageSeverity.WARN, ValidationMessageSeverity.INFO, false, false, false, false, PackageType.APPLICATION, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_NODE_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_ADITIONAL_FILE_NODE_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_IMMUTABLE_ROOT_NODE_NAMES, parentContainerContext);
         Mockito.when(properties.getPackageType()).thenReturn(PackageType.APPLICATION);
         MatcherAssert.assertThat(subPackageValidator.validate(properties), AnyValidationMessageMatcher.noValidationInCollection());
 
         // validate sling:OsgiConfig node
-        MatcherAssert.assertThat(validator.validate(new NodeContextImpl("/apps/config/someconfigpid", Paths.get("apps", "config", "someconfigpid.xml"), Paths.get(""))), AnyValidationMessageMatcher.noValidationInCollection());
+        NodeContext context = new NodeContextImpl("/apps/config/someconfigpid", Paths.get("apps", "config", "someconfigpid.xml"), Paths.get(""));
+        MatcherAssert.assertThat(validator.validate(context), AnyValidationMessageMatcher.noValidationInCollection());
+        MatcherAssert.assertThat(validator.validate(
+            new DocViewNode("jcr:root", "jcr:root", null, Collections.emptyMap(), null, "sling:OsgiConfig"),
+            context,
+            true),
+            AnyValidationMessageMatcher.noValidationInCollection());
+       // validate other type docview node below config folder
+        MatcherAssert.assertThat(validator.validate(
+                new DocViewNode("jcr:root", "jcr:root", null, Collections.emptyMap(), null, "nt:unstructured"),
+                context,
+                true),
+                AnyValidationMessageMatcher.noValidationInCollection());
 
         // make sure no dependencies
         Mockito.when(properties.getPackageType()).thenReturn(PackageType.CONTAINER);
@@ -189,7 +215,7 @@ public class PackageTypeValidatorTest {
 
     @Test
     public void testApplicationPackageType() throws IOException, ConfigurationException {
-        validator = new PackageTypeValidator(filter, ValidationMessageSeverity.ERROR, ValidationMessageSeverity.WARN, ValidationMessageSeverity.INFO, false, false, false, false, PackageType.APPLICATION, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_IMMUTABLE_ROOT_NODE_NAMES, null);
+        validator = new PackageTypeValidator(filter, ValidationMessageSeverity.ERROR, ValidationMessageSeverity.WARN, ValidationMessageSeverity.INFO, false, false, false, false, PackageType.APPLICATION, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_NODE_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_ADITIONAL_FILE_NODE_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_IMMUTABLE_ROOT_NODE_NAMES, null);
         MatcherAssert.assertThat(validator.validate(new NodeContextImpl("/apps/some/script", Paths.get(""), Paths.get(""))), AnyValidationMessageMatcher.noValidationInCollection());
         MatcherAssert.assertThat(validator.validate(new NodeContextImpl("/libs",  Paths.get(""), Paths.get(""))), AnyValidationMessageMatcher.noValidationInCollection());
         
@@ -197,7 +223,7 @@ public class PackageTypeValidatorTest {
         ValidationExecutorTest.assertViolation(validator.validate(new NodeContextImpl("/etc/something",  Paths.get(""), Paths.get(""))), new ValidationMessage(ValidationMessageSeverity.ERROR, String.format(PackageTypeValidator.MESSAGE_NO_APP_CONTENT_FOUND, PackageType.APPLICATION, "'apps' or 'libs'")));
         ValidationExecutorTest.assertViolation(validator.validate(new NodeContextImpl("/conf/something",  Paths.get(""), Paths.get(""))), new ValidationMessage(ValidationMessageSeverity.ERROR, String.format(PackageTypeValidator.MESSAGE_NO_APP_CONTENT_FOUND, PackageType.APPLICATION, "'apps' or 'libs'")));
         
-        // no bundles/sub packages
+        // no bundles/configurations
         ValidationExecutorTest.assertViolation(validator.validate(new NodeContextImpl("/apps/install/mybundle.jar", Paths.get("apps", "install", "mybundle.jar"), Paths.get(""))), new ValidationMessage(ValidationMessageSeverity.ERROR, String.format(PackageTypeValidator.MESSAGE_NO_OSGI_BUNDLE_OR_CONFIG_ALLOWED, PackageType.APPLICATION, "/apps/install/mybundle.jar")));
         ValidationExecutorTest.assertViolation(validator.validate(new NodeContextImpl("/apps/install/config.cfg", Paths.get("apps", "install", "config.cfg"), Paths.get(""))), new ValidationMessage(ValidationMessageSeverity.ERROR, String.format(PackageTypeValidator.MESSAGE_NO_OSGI_BUNDLE_OR_CONFIG_ALLOWED, PackageType.APPLICATION, "/apps/install/config.cfg")));
         
@@ -228,18 +254,29 @@ public class PackageTypeValidatorTest {
             filter.load(input);
         }
         ValidationExecutorTest.assertViolation(validator.validate(filter), new ValidationMessage(ValidationMessageSeverity.ERROR, String.format(PackageTypeValidator.MESSAGE_FILTER_HAS_INCLUDE_EXCLUDES, PackageType.APPLICATION)));
-        
+
         // validate sling:OsgiConfig node
-        ValidationExecutorTest.assertViolation(validator.validate(new NodeContextImpl("/apps/config/someconfigpid", Paths.get("apps", "config", "someconfigpid.xml"), Paths.get(""))), 
-                new ValidationMessage(ValidationMessageSeverity.ERROR, String.format(PackageTypeValidator.MESSAGE_NO_OSGI_BUNDLE_OR_CONFIG_ALLOWED, PackageType.APPLICATION, "/apps/config/someconfigpid")));
+        NodeContext context = new NodeContextImpl("/apps/config/someconfigpid", Paths.get("apps", "config", "someconfigpid.xml"), Paths.get(""));
+        MatcherAssert.assertThat(validator.validate(context), AnyValidationMessageMatcher.noValidationInCollection());
+        ValidationExecutorTest.assertViolation(validator.validate(
+            new DocViewNode("jcr:root", "jcr:root", null, Collections.emptyMap(), null, "sling:OsgiConfig"),
+            context,
+            true),
+            new ValidationMessage(ValidationMessageSeverity.ERROR, String.format(PackageTypeValidator.MESSAGE_NO_OSGI_BUNDLE_OR_CONFIG_ALLOWED, PackageType.APPLICATION)));
+        // validate other type docview node below config folder
+        MatcherAssert.assertThat(validator.validate(
+                new DocViewNode("jcr:root", "jcr:root", null, Collections.emptyMap(), null, "nt:unstructured"),
+                context,
+                true),
+                AnyValidationMessageMatcher.noValidationInCollection());
 
         // validate sub packages of type Content
         Mockito.when(parentContainerProperties.getPackageType()).thenReturn(PackageType.APPLICATION);
         Mockito.when(properties.getPackageType()).thenReturn(PackageType.CONTENT);
-        PackageTypeValidator subPackageValidator = new PackageTypeValidator(filter, ValidationMessageSeverity.ERROR, ValidationMessageSeverity.WARN, ValidationMessageSeverity.INFO, false, false, false, false, PackageType.CONTENT, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_IMMUTABLE_ROOT_NODE_NAMES, parentContainerContext);
+        PackageTypeValidator subPackageValidator = new PackageTypeValidator(filter, ValidationMessageSeverity.ERROR, ValidationMessageSeverity.WARN, ValidationMessageSeverity.INFO, false, false, false, false, PackageType.CONTENT, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_NODE_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_ADITIONAL_FILE_NODE_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_IMMUTABLE_ROOT_NODE_NAMES, parentContainerContext);
         ValidationExecutorTest.assertViolation(subPackageValidator.validate(properties), new ValidationMessage(ValidationMessageSeverity.ERROR, String.format(PackageTypeValidator.MESSAGE_UNSUPPORTED_SUB_PACKAGE, PackageType.APPLICATION)));
         // validate sub packages of type Application
-        subPackageValidator = new PackageTypeValidator(filter, ValidationMessageSeverity.ERROR, ValidationMessageSeverity.WARN, ValidationMessageSeverity.INFO, false, false, false, false, PackageType.CONTENT, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_IMMUTABLE_ROOT_NODE_NAMES, parentContainerContext);
+        subPackageValidator = new PackageTypeValidator(filter, ValidationMessageSeverity.ERROR, ValidationMessageSeverity.WARN, ValidationMessageSeverity.INFO, false, false, false, false, PackageType.CONTENT, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_NODE_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_ADITIONAL_FILE_NODE_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_IMMUTABLE_ROOT_NODE_NAMES, parentContainerContext);
         Mockito.when(properties.getPackageType()).thenReturn(PackageType.APPLICATION);
         Mockito.when(properties.getExternalHooks()).thenReturn(Collections.emptyMap());
         ValidationExecutorTest.assertViolation(subPackageValidator.validate(properties), new ValidationMessage(ValidationMessageSeverity.ERROR, String.format(PackageTypeValidator.MESSAGE_UNSUPPORTED_SUB_PACKAGE, PackageType.APPLICATION)));
@@ -248,13 +285,13 @@ public class PackageTypeValidatorTest {
     @Test
     public void testApplicationPackageTypeWithAllowedOakIndex() throws IOException, ConfigurationException {
         Set<String> immutableRootNodeNames = new HashSet<>(Arrays.asList("oak:index"));
-        validator = new PackageTypeValidator(filter, ValidationMessageSeverity.ERROR, ValidationMessageSeverity.WARN, ValidationMessageSeverity.INFO, false, false, false, false, PackageType.APPLICATION, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_PATH_REGEX, immutableRootNodeNames, null);
+        validator = new PackageTypeValidator(filter, ValidationMessageSeverity.ERROR, ValidationMessageSeverity.WARN, ValidationMessageSeverity.INFO, false, false, false, false, PackageType.APPLICATION, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_NODE_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_ADITIONAL_FILE_NODE_PATH_REGEX, immutableRootNodeNames, null);
         MatcherAssert.assertThat(validator.validate(new NodeContextImpl("/oak:index/myindex", Paths.get(""), Paths.get(""))), AnyValidationMessageMatcher.noValidationInCollection());
     }
 
     @Test
     public void testApplicationPackageTypeWithAllowedHook() throws IOException, ConfigurationException {
-        validator = new PackageTypeValidator(filter, ValidationMessageSeverity.ERROR, ValidationMessageSeverity.WARN, ValidationMessageSeverity.INFO, false, false, false, true, PackageType.APPLICATION, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_IMMUTABLE_ROOT_NODE_NAMES, null);
+        validator = new PackageTypeValidator(filter, ValidationMessageSeverity.ERROR, ValidationMessageSeverity.WARN, ValidationMessageSeverity.INFO, false, false, false, true, PackageType.APPLICATION, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_NODE_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_ADITIONAL_FILE_NODE_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_IMMUTABLE_ROOT_NODE_NAMES, null);
         // with hooks
         Map<String, String> hooks = Collections.singletonMap("key", "com.example.ExternalHook");
         Mockito.when(properties.getExternalHooks()).thenReturn(hooks);
@@ -265,7 +302,7 @@ public class PackageTypeValidatorTest {
 
     @Test
     public void testApplicationPackageTypeWithAllowedComplexFilters() throws IOException, ConfigurationException {
-        validator = new PackageTypeValidator(filter, ValidationMessageSeverity.ERROR, ValidationMessageSeverity.WARN, ValidationMessageSeverity.INFO, false, false, true, false, PackageType.APPLICATION, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_IMMUTABLE_ROOT_NODE_NAMES, null);
+        validator = new PackageTypeValidator(filter, ValidationMessageSeverity.ERROR, ValidationMessageSeverity.WARN, ValidationMessageSeverity.INFO, false, false, true, false, PackageType.APPLICATION, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_NODE_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_ADITIONAL_FILE_NODE_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_IMMUTABLE_ROOT_NODE_NAMES, null);
         DefaultWorkspaceFilter filter = new DefaultWorkspaceFilter();
         // with filters with include/exclude
         try (InputStream input = this.getClass().getResourceAsStream("/filter.xml")) {
@@ -276,7 +313,7 @@ public class PackageTypeValidatorTest {
 
     @Test
     public void testMutableContentProhibited() {
-        validator = new PackageTypeValidator(filter, ValidationMessageSeverity.ERROR, ValidationMessageSeverity.INFO, ValidationMessageSeverity.INFO, true, false, false, false, PackageType.APPLICATION, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_IMMUTABLE_ROOT_NODE_NAMES, null);
+        validator = new PackageTypeValidator(filter, ValidationMessageSeverity.ERROR, ValidationMessageSeverity.INFO, ValidationMessageSeverity.INFO, true, false, false, false, PackageType.APPLICATION, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_NODE_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_ADITIONAL_FILE_NODE_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_IMMUTABLE_ROOT_NODE_NAMES, null);
         Mockito.when(properties.getPackageType()).thenReturn(PackageType.MIXED);
         ValidationExecutorTest.assertViolation(validator.validate(properties), new ValidationMessage(ValidationMessageSeverity.ERROR, String.format(PackageTypeValidator.MESSAGE_PROHIBITED_MUTABLE_PACKAGE_TYPE, PackageType.MIXED)));
         Mockito.when(properties.getPackageType()).thenReturn(PackageType.CONTENT);
@@ -289,7 +326,7 @@ public class PackageTypeValidatorTest {
 
     @Test
     public void testImmutableContentProhibited() {
-        validator = new PackageTypeValidator(filter, ValidationMessageSeverity.ERROR, ValidationMessageSeverity.INFO, ValidationMessageSeverity.INFO, false, true, false, false, PackageType.APPLICATION, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_IMMUTABLE_ROOT_NODE_NAMES, null);
+        validator = new PackageTypeValidator(filter, ValidationMessageSeverity.ERROR, ValidationMessageSeverity.INFO, ValidationMessageSeverity.INFO, false, true, false, false, PackageType.APPLICATION, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_NODE_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_JCR_INSTALLER_ADITIONAL_FILE_NODE_PATH_REGEX, PackageTypeValidatorFactory.DEFAULT_IMMUTABLE_ROOT_NODE_NAMES, null);
         Mockito.when(properties.getPackageType()).thenReturn(PackageType.MIXED);
         ValidationExecutorTest.assertViolation(validator.validate(properties), new ValidationMessage(ValidationMessageSeverity.ERROR, String.format(PackageTypeValidator.MESSAGE_PROHIBITED_IMMUTABLE_PACKAGE_TYPE, PackageType.MIXED)));
         Mockito.when(properties.getPackageType()).thenReturn(PackageType.CONTENT);
diff --git a/vault-validation/src/test/resources/no-validation-issue-package/META-INF/vault/filter.xml b/vault-validation/src/test/resources/no-validation-issue-package/META-INF/vault/filter.xml
new file mode 100644
index 0000000..8c0ece1
--- /dev/null
+++ b/vault-validation/src/test/resources/no-validation-issue-package/META-INF/vault/filter.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8" standalone="no"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+<workspaceFilter version="1.0">
+    <filter root="/apps">
+    </filter>
+</workspaceFilter>
\ No newline at end of file
diff --git a/vault-validation/src/test/resources/no-validation-issue-package/META-INF/vault/genericfile.txt b/vault-validation/src/test/resources/no-validation-issue-package/META-INF/vault/genericfile.txt
new file mode 100644
index 0000000..8318c86
--- /dev/null
+++ b/vault-validation/src/test/resources/no-validation-issue-package/META-INF/vault/genericfile.txt
@@ -0,0 +1 @@
+Test
\ No newline at end of file
diff --git a/vault-validation/src/test/resources/no-validation-issue-package/META-INF/vault/properties.xml b/vault-validation/src/test/resources/no-validation-issue-package/META-INF/vault/properties.xml
new file mode 100644
index 0000000..e364192
--- /dev/null
+++ b/vault-validation/src/test/resources/no-validation-issue-package/META-INF/vault/properties.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8" standalone="no"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements.  See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You under the Apache License, Version 2.0
+  ~ (the "License"); you may not use this file except in compliance with
+  ~ the License.  You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
+<properties>
+<comment>FileVault Package Properties</comment>
+<entry key="createdBy">admin</entry>
+<entry key="name">mode_ac_test_a</entry>
+<entry key="lastModified">2011-11-15T09:43:22.972+01:00</entry>
+<entry key="lastModifiedBy">admin</entry>
+<entry key="created">2011-11-15T09:43:22.993+01:00</entry>
+<entry key="buildCount">1</entry>
+<entry key="version"/>
+<entry key="dependencies"/>
+<entry key="packageFormatVersion">2</entry>
+<entry key="description"/>
+<entry key="lastWrapped">2011-11-15T09:43:22.972+01:00</entry>
+<entry key="group"/>
+<entry key="lastWrappedBy">admin</entry>
+<entry key="acHandling">overwrite</entry>
+<entry key="packageType">application</entry>
+</properties>
diff --git a/vault-validation/src/test/resources/no-validation-issue-package/jcr_root/apps/.content.xml b/vault-validation/src/test/resources/no-validation-issue-package/jcr_root/apps/.content.xml
new file mode 100755
index 0000000..27d5961
--- /dev/null
+++ b/vault-validation/src/test/resources/no-validation-issue-package/jcr_root/apps/.content.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+<jc:root xmlns:jc="http://www.jcp.org/jcr/1.0"
+    jc:primaryType="nt:unstructured">
+    <somepath jc:primaryType="nt:unstructured"
+        attribute1="value1">
+    </somepath>
+    <_x0030_123_sample.jpg/>
+    <_x0030_1234_sample.jpg jc:primaryType="nt:file"/>
+</jc:root>
diff --git a/vault-validation/src/test/resources/no-validation-issue-package/jcr_root/apps/child1.xml b/vault-validation/src/test/resources/no-validation-issue-package/jcr_root/apps/child1.xml
new file mode 100755
index 0000000..ab027ad
--- /dev/null
+++ b/vault-validation/src/test/resources/no-validation-issue-package/jcr_root/apps/child1.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0"
+    jcr:primaryType="nt:unstructured">
+    <somepath jcr:primaryType="nt:unstructured"
+        attribute1="value1"
+    ></somepath>
+</jcr:root>
diff --git a/vault-validation/src/test/resources/no-validation-issue-package/jcr_root/apps/genericfile.xml b/vault-validation/src/test/resources/no-validation-issue-package/jcr_root/apps/genericfile.xml
new file mode 100644
index 0000000..ff1b84c
--- /dev/null
+++ b/vault-validation/src/test/resources/no-validation-issue-package/jcr_root/apps/genericfile.xml
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="utf-8" standalone="no"?>
+<somelement attr="value" />
diff --git a/vault-validation/src/test/resources/no-validation-issue-package/jcr_root/apps/test/.content.xml b/vault-validation/src/test/resources/no-validation-issue-package/jcr_root/apps/test/.content.xml
new file mode 100755
index 0000000..2b2fab8
--- /dev/null
+++ b/vault-validation/src/test/resources/no-validation-issue-package/jcr_root/apps/test/.content.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+<jc:root xmlns:jc="http://www.jcp.org/jcr/1.0"
+    jc:primaryType="nt:unstructured">
+</jc:root>
diff --git a/vault-validation/src/test/resources/no-validation-issue-package/jcr_root/apps/test/config/.content.xml b/vault-validation/src/test/resources/no-validation-issue-package/jcr_root/apps/test/config/.content.xml
new file mode 100755
index 0000000..2b2fab8
--- /dev/null
+++ b/vault-validation/src/test/resources/no-validation-issue-package/jcr_root/apps/test/config/.content.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+<jc:root xmlns:jc="http://www.jcp.org/jcr/1.0"
+    jc:primaryType="nt:unstructured">
+</jc:root>
diff --git a/vault-validation/src/test/resources/no-validation-issue-package/jcr_root/apps/test/config/child1.xml b/vault-validation/src/test/resources/no-validation-issue-package/jcr_root/apps/test/config/child1.xml
new file mode 100755
index 0000000..ab027ad
--- /dev/null
+++ b/vault-validation/src/test/resources/no-validation-issue-package/jcr_root/apps/test/config/child1.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0"
+    jcr:primaryType="nt:unstructured">
+    <somepath jcr:primaryType="nt:unstructured"
+        attribute1="value1"
+    ></somepath>
+</jcr:root>