SLING-10808 - Generate scripting capabilities from Jackrabbit FileVault content packages

* added support to generate provided and required capabilities based on FileVault content
packages by reading the .content.xml files
diff --git a/pom.xml b/pom.xml
index 8ffdb54..211a764 100644
--- a/pom.xml
+++ b/pom.xml
@@ -54,7 +54,7 @@
     </properties>
 
     <prerequisites>
-        <maven>${mavenVersion}</maven>
+        <maven>${maven.version}</maven>
     </prerequisites>
 
     <build>
@@ -217,7 +217,37 @@
             <version>4.3.0</version>
             <scope>compile</scope>
         </dependency>
-
+        <!-- FileVault -->
+        <dependency>
+            <groupId>org.apache.jackrabbit.vault</groupId>
+            <artifactId>org.apache.jackrabbit.vault</artifactId>
+            <version>3.5.0</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>javax.jcr</groupId>
+            <artifactId>jcr</artifactId>
+            <version>2.0</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.jackrabbit</groupId>
+            <artifactId>oak-jackrabbit-api</artifactId>
+            <version>1.40.0</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.jackrabbit</groupId>
+            <artifactId>jackrabbit-spi-commons</artifactId>
+            <version>2.20.2</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.jcr.resource</artifactId>
+            <version>3.0.22</version>
+            <scope>compile</scope>
+        </dependency>
         <dependency>
             <groupId>org.apache.maven.plugin-testing</groupId>
             <artifactId>maven-plugin-testing-harness</artifactId>
diff --git a/src/main/java/org/apache/sling/scriptingbundle/plugin/bnd/BundledScriptsScannerPlugin.java b/src/main/java/org/apache/sling/scriptingbundle/plugin/bnd/BundledScriptsScannerPlugin.java
index caa3293..d7893cc 100644
--- a/src/main/java/org/apache/sling/scriptingbundle/plugin/bnd/BundledScriptsScannerPlugin.java
+++ b/src/main/java/org/apache/sling/scriptingbundle/plugin/bnd/BundledScriptsScannerPlugin.java
@@ -61,6 +61,7 @@
     @Override
     public boolean analyzeJar(Analyzer analyzer) throws Exception {
         logger = new BndLogger(reporter);
+        boolean inContentPackage = "content-package".equals(analyzer.get("project.packaging"));
         Path workDirectory = Paths.get(analyzer.get(PROJECT_BUILD_FOLDER), "scriptingbundle-maven-plugin");
         Files.createDirectories(workDirectory);
         Set<PathMatcher> includes = getConfiguredIncludes();
@@ -93,7 +94,7 @@
         scriptEngineMappings = getConfiguredScriptEngineMappings();
         capabilities = Capabilities
                 .fromFileSystemTree(workDirectory, walkPath(workDirectory, includes, excludes), logger,
-                getConfiguredSearchPaths(), scriptEngineMappings, getMissingRequirementsOptional());
+                getConfiguredSearchPaths(), scriptEngineMappings, getMissingRequirementsOptional(), inContentPackage);
         String providedCapabilitiesDefinition = capabilities.getProvidedCapabilitiesString();
         String requiredCapabilitiesDefinition = capabilities.getRequiredCapabilitiesString();
 
@@ -199,10 +200,7 @@
 
     private Stream<Path> walkPath(Path path, Set<PathMatcher> includes, Set<PathMatcher> excludes) throws IOException {
         return Files.walk(path).filter(file -> {
-            boolean include = false;
-            if (includes.isEmpty()) {
-                include = true;
-            }
+            boolean include = includes.isEmpty();
             Optional<PathMatcher> includeOptions = includes.stream().filter(pathMatcher -> pathMatcher.matches(file)).findFirst();
             if (includeOptions.isPresent()) {
                 include = true;
diff --git a/src/main/java/org/apache/sling/scriptingbundle/plugin/capability/Capabilities.java b/src/main/java/org/apache/sling/scriptingbundle/plugin/capability/Capabilities.java
index bd8e4de..4cf9179 100644
--- a/src/main/java/org/apache/sling/scriptingbundle/plugin/capability/Capabilities.java
+++ b/src/main/java/org/apache/sling/scriptingbundle/plugin/capability/Capabilities.java
@@ -132,14 +132,15 @@
     public static @NotNull Capabilities fromFileSystemTree(@NotNull Path root, @NotNull Stream<Path> files, @NotNull Logger logger,
                                                            @NotNull Set<String> searchPaths,
                                                            @NotNull Map<String, String> scriptEngineMappings,
-                                                           boolean missingRequirementsOptional) {
+                                                           boolean missingRequirementsOptional,
+                                                           boolean inContentPackage) {
         Set<ProvidedResourceTypeCapability> providedResourceTypeCapabilities = new LinkedHashSet<>();
         Set<ProvidedScriptCapability> providedScriptCapabilities = new LinkedHashSet<>();
         Set<RequiredResourceTypeCapability> requiredResourceTypeCapabilities = new LinkedHashSet<>();
         FileProcessor fileProcessor = new FileProcessor(logger, searchPaths, scriptEngineMappings);
-        ResourceTypeFolderAnalyser resourceTypeFolderAnalyser = new ResourceTypeFolderAnalyser(logger, root, fileProcessor);
+        ResourceTypeFolderAnalyser resourceTypeFolderAnalyser = new ResourceTypeFolderAnalyser(logger, root, fileProcessor, inContentPackage);
         PathOnlyScriptAnalyser
-                pathOnlyScriptAnalyser = new PathOnlyScriptAnalyser(logger, root, scriptEngineMappings, fileProcessor);
+                pathOnlyScriptAnalyser = new PathOnlyScriptAnalyser(logger, root, scriptEngineMappings, fileProcessor, inContentPackage);
         files.forEach(path -> {
             if (Files.isDirectory(path)) {
                 Capabilities resourceTypeCapabilities = resourceTypeFolderAnalyser.getCapabilities(path);
diff --git a/src/main/java/org/apache/sling/scriptingbundle/plugin/maven/MetadataMojo.java b/src/main/java/org/apache/sling/scriptingbundle/plugin/maven/MetadataMojo.java
index 32d0f66..1a562e4 100644
--- a/src/main/java/org/apache/sling/scriptingbundle/plugin/maven/MetadataMojo.java
+++ b/src/main/java/org/apache/sling/scriptingbundle/plugin/maven/MetadataMojo.java
@@ -178,6 +178,7 @@
     private Capabilities capabilities;
 
     public void execute() {
+        boolean inContentPackage = "content-package".equals(project.getPackaging());
         Logger logger = new MavenLogger(getLog());
         Path workDirectory = Paths.get(project.getBuild().getDirectory(), "scriptingbundle-maven-plugin");
         try {
@@ -226,7 +227,8 @@
                 logger,
                 searchPaths,
                 scriptEngineMappings,
-                missingRequirementsOptional
+                missingRequirementsOptional,
+                inContentPackage
             );
             String providedCapabilitiesDefinition = capabilities.getProvidedCapabilitiesString();
             String requiredCapabilitiesDefinition = capabilities.getRequiredCapabilitiesString();
diff --git a/src/main/java/org/apache/sling/scriptingbundle/plugin/processor/Constants.java b/src/main/java/org/apache/sling/scriptingbundle/plugin/processor/Constants.java
index 55cbc89..0c38ba8 100644
--- a/src/main/java/org/apache/sling/scriptingbundle/plugin/processor/Constants.java
+++ b/src/main/java/org/apache/sling/scriptingbundle/plugin/processor/Constants.java
@@ -54,6 +54,10 @@
     public static final String BND_SEARCH_PATHS = "searchPaths";
     public static final String BND_MISSING_REQUIREMENTS_OPTIONAL = "missingRequirementsOptional";
 
+    public static final String SLING_RESOURCE_SUPER_TYPE_XML_LOCAL_NAME = "resourceSuperType";
+    public static final String SLING_REQUIRED_RESOURCE_TYPES = "sling:requiredResourceTypes";
+    public static final String SLING_REQUIRED_RESOURCE_TYPES_XML_LOCAL_NAME = "requiredResourceTypes";
+
     public static final Map<String, String> DEFAULT_EXTENSION_TO_SCRIPT_ENGINE_MAPPING;
     public static final Set<String> DEFAULT_SEARCH_PATHS;
     public static final Set<String> DEFAULT_SOURCE_DIRECTORIES;
diff --git a/src/main/java/org/apache/sling/scriptingbundle/plugin/processor/FileProcessor.java b/src/main/java/org/apache/sling/scriptingbundle/plugin/processor/FileProcessor.java
index 2ddc24b..8a5f81d 100644
--- a/src/main/java/org/apache/sling/scriptingbundle/plugin/processor/FileProcessor.java
+++ b/src/main/java/org/apache/sling/scriptingbundle/plugin/processor/FileProcessor.java
@@ -35,9 +35,11 @@
 
 import org.apache.commons.io.FilenameUtils;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.jackrabbit.vault.util.PlatformNameFormat;
 import org.apache.sling.api.resource.type.ResourceType;
 import org.apache.sling.scriptingbundle.plugin.capability.ProvidedResourceTypeCapability;
 import org.apache.sling.scriptingbundle.plugin.capability.RequiredResourceTypeCapability;
+import org.apache.sling.scriptingbundle.plugin.processor.filevault.VaultContentXmlReader;
 import org.jetbrains.annotations.NotNull;
 import org.osgi.framework.VersionRange;
 
@@ -61,93 +63,108 @@
         this.scriptEngineMappings = scriptEngineMappings;
     }
 
-    public void processExtendsFile(@NotNull ResourceType resourceType, @NotNull Path extendsFile,
+    public void processExtendsFile(@NotNull ResourceType resourceType, @NotNull Path file,
                             @NotNull Set<ProvidedResourceTypeCapability> providedCapabilities,
                             @NotNull Set<RequiredResourceTypeCapability> requiredCapabilities) throws IllegalArgumentException {
         try {
-            List<String> extendResources = Files.readAllLines(extendsFile, StandardCharsets.UTF_8);
-            if (extendResources.size() == 1) {
-                String extend = extendResources.get(0);
-                Parameters parameters = OSGiHeader.parseHeader(extend);
-                if (parameters.size() < 1 || parameters.size() > 1) {
-                    throw new IllegalArgumentException(String.format("The file '%s' must contain one clause only (not multiple ones separated by ',')", extendsFile));
-                }
-                Entry<String, Attrs> extendsParameter = parameters.entrySet().iterator().next();
-                for (String attributeName : extendsParameter.getValue().keySet()) {
-                    if (!EXTENDS_ALLOWED_ATTRIBUTE_NAMES.contains(attributeName)) {
-                        throw new IllegalArgumentException(String.format("Found unsupported attribute/directive '%s' in file '%s'. Only the following attributes or directives may be used in the extends file: %s", attributeName, extendsFile, String.join(",", EXTENDS_ALLOWED_ATTRIBUTE_NAMES)));
-                    }
-                }
-                String extendedResourceType = FilenameUtils.normalize(extendsParameter.getKey(), true);
-                boolean isOptional = aQute.bnd.osgi.Constants.OPTIONAL.equals(extendsParameter.getValue().get(aQute.bnd.osgi.Constants.RESOLUTION_DIRECTIVE));
-                Set<String> searchPathResourceTypes = processSearchPathResourceTypes(resourceType);
-                Optional<ProvidedResourceTypeCapability> rootCapability = providedCapabilities.stream().filter(capability ->
-                    capability.getResourceTypes().equals(searchPathResourceTypes) && capability.getSelectors().isEmpty() &&
-                            StringUtils.isEmpty(capability.getRequestMethod()) && StringUtils.isEmpty(capability.getRequestExtension())
-                ).findFirst();
-                rootCapability.ifPresent(capability -> {
-                    providedCapabilities.remove(capability);
-                    ProvidedResourceTypeCapability replacement =
-                            ProvidedResourceTypeCapability.builder().fromCapability(capability)
-                                    .withExtendsResourceType(extendedResourceType).build();
-                    providedCapabilities.add(replacement);
-
-                });
-                if (!rootCapability.isPresent()) {
-                    providedCapabilities.add(
-                            ProvidedResourceTypeCapability.builder()
-                                    .withResourceTypes(processSearchPathResourceTypes(resourceType))
-                                    .withVersion(resourceType.getVersion())
-                                    .withExtendsResourceType(extendedResourceType)
-                                    .build());
-                }
-                RequiredResourceTypeCapability.Builder requiredBuilder =
-                        RequiredResourceTypeCapability.builder().withResourceType(extendedResourceType);
-                if (isOptional) {
-                    requiredBuilder.withIsOptional();
-                }
-                extractVersionRange(extendsFile, requiredBuilder, extendsParameter.getValue().getVersion());
-                requiredCapabilities.add(requiredBuilder.build());
+            List<String> extendedResources = Files.readAllLines(file, StandardCharsets.UTF_8);
+            if (extendedResources.size() == 1) {
+                processExtendedResourceType(resourceType, file, providedCapabilities, requiredCapabilities, extendedResources.get(0));
             } else {
-                throw new IllegalArgumentException(String.format("The file '%s' must contain one line only (not multiple ones)", extendsFile));
+                throw new IllegalArgumentException(String.format("The file '%s' must contain one line only (not multiple ones)", file));
             }
         } catch (IOException e) {
-            throw new UncheckedIOException(String.format("Unable to read file %s.", extendsFile.toString()), e);
+            throw new UncheckedIOException(String.format("Unable to read file %s.", file.toString()), e);
         }
     }
 
+    private void processExtendedResourceType(@NotNull ResourceType resourceType, @NotNull Path extendsFile,
+                           @NotNull Set<ProvidedResourceTypeCapability> providedCapabilities,
+                           @NotNull Set<RequiredResourceTypeCapability> requiredCapabilities, @NotNull String extendedResource) {
+        Parameters parameters = OSGiHeader.parseHeader(extendedResource);
+        if (parameters.size() != 1) {
+            throw new IllegalArgumentException(String.format("The file '%s' must contain one clause only (not multiple ones separated by ',')",
+                    extendsFile));
+        }
+        Entry<String, Attrs> extendsParameter = parameters.entrySet().iterator().next();
+        for (String attributeName : extendsParameter.getValue().keySet()) {
+            if (!EXTENDS_ALLOWED_ATTRIBUTE_NAMES.contains(attributeName)) {
+                throw new IllegalArgumentException(String.format("Found unsupported attribute/directive '%s' in file '%s'. Only the following attributes or directives may be used in the extends file: %s", attributeName,
+                        extendsFile, String.join(",", EXTENDS_ALLOWED_ATTRIBUTE_NAMES)));
+            }
+        }
+        String extendedResourceType = FilenameUtils.normalize(extendsParameter.getKey(), true);
+        boolean isOptional = aQute.bnd.osgi.Constants.OPTIONAL.equals(extendsParameter.getValue().get(aQute.bnd.osgi.Constants.RESOLUTION_DIRECTIVE));
+        Set<String> searchPathResourceTypes = processSearchPathResourceTypes(resourceType);
+        Optional<ProvidedResourceTypeCapability> rootCapability = providedCapabilities.stream().filter(capability ->
+            capability.getResourceTypes().equals(searchPathResourceTypes) && capability.getSelectors().isEmpty() &&
+                    StringUtils.isEmpty(capability.getRequestMethod()) && StringUtils.isEmpty(capability.getRequestExtension())
+        ).findFirst();
+        rootCapability.ifPresent(capability -> {
+            providedCapabilities.remove(capability);
+            ProvidedResourceTypeCapability replacement =
+                    ProvidedResourceTypeCapability.builder().fromCapability(capability)
+                            .withExtendsResourceType(extendedResourceType).build();
+            providedCapabilities.add(replacement);
+
+        });
+        if (!rootCapability.isPresent()) {
+            providedCapabilities.add(
+                    ProvidedResourceTypeCapability.builder()
+                            .withResourceTypes(processSearchPathResourceTypes(resourceType))
+                            .withVersion(resourceType.getVersion())
+                            .withExtendsResourceType(extendedResourceType)
+                            .build());
+        }
+        RequiredResourceTypeCapability.Builder requiredBuilder =
+                RequiredResourceTypeCapability.builder().withResourceType(extendedResourceType);
+        if (isOptional) {
+            requiredBuilder.withIsOptional();
+        }
+        extractVersionRange(extendsFile, requiredBuilder, extendsParameter.getValue().getVersion());
+        requiredCapabilities.add(requiredBuilder.build());
+    }
+
     void processRequiresFile(@NotNull Path requiresFile,
                                     @NotNull Set<RequiredResourceTypeCapability> requiredCapabilities) {
         try {
             List<String> requiredResourceTypes = Files.readAllLines(requiresFile, StandardCharsets.UTF_8);
-            for (String requiredResourceType : requiredResourceTypes) {
-                Parameters parameters = OSGiHeader.parseHeader(requiredResourceType);
-                if (parameters.size() < 1 || parameters.size() > 1) {
-                    throw new IllegalArgumentException(String.format("Each line in file '%s' must contain one clause only (not multiple ones separated by ',')", requiresFile));
-                }
-                Entry<String, Attrs> requiresParameter = parameters.entrySet().iterator().next();
-                for (String attributeName : requiresParameter.getValue().keySet()) {
-                    if (!REQUIRES_ALLOWED_ATTRIBUTE_NAMES.contains(attributeName)) {
-                        throw new IllegalArgumentException(String.format("Found unsupported attribute/directive '%s' in file '%s'. Only the following attributes or directives may be used in the requires file: %s", attributeName, requiresFile, String.join(",", REQUIRES_ALLOWED_ATTRIBUTE_NAMES)));
-                    }
-                }
-                String resourceType = FilenameUtils.normalize(requiresParameter.getKey(), true);
-                boolean isOptional = aQute.bnd.osgi.Constants.OPTIONAL.equals(requiresParameter.getValue().get(aQute.bnd.osgi.Constants.RESOLUTION_DIRECTIVE));
-                RequiredResourceTypeCapability.Builder requiredBuilder =
-                        RequiredResourceTypeCapability.builder().withResourceType(resourceType);
-                if (isOptional) {
-                    requiredBuilder.withIsOptional();
-                }
-                extractVersionRange(requiresFile, requiredBuilder, requiresParameter.getValue().getVersion());
-                requiredCapabilities.add(requiredBuilder.build());
-            }
+            processRequiredResourceTypes(requiresFile, requiredCapabilities, requiredResourceTypes);
         } catch (IOException e) {
-            throw new UncheckedIOException(String.format("Unable to read file %s.", requiresFile.toString()), e);
+            throw new UncheckedIOException(String.format("Unable to read file %s.", requiresFile), e);
+        }
+    }
+
+    private void processRequiredResourceTypes(@NotNull Path requiresFile, @NotNull Set<RequiredResourceTypeCapability> requiredCapabilities,
+                           List<String> requiredResourceTypes) {
+        for (String requiredResourceType : requiredResourceTypes) {
+            Parameters parameters = OSGiHeader.parseHeader(requiredResourceType);
+            if (parameters.size() != 1) {
+                throw new IllegalArgumentException(String.format("Each line in file '%s' must contain one clause only (not multiple ones separated by ',')",
+                        requiresFile));
+            }
+            Entry<String, Attrs> requiresParameter = parameters.entrySet().iterator().next();
+            for (String attributeName : requiresParameter.getValue().keySet()) {
+                if (!REQUIRES_ALLOWED_ATTRIBUTE_NAMES.contains(attributeName)) {
+                    throw new IllegalArgumentException(String.format("Found unsupported attribute/directive '%s' in file '%s'. Only the following attributes or directives may be used in the requires file: %s", attributeName,
+                            requiresFile, String.join(",", REQUIRES_ALLOWED_ATTRIBUTE_NAMES)));
+                }
+            }
+            String resourceType = FilenameUtils.normalize(requiresParameter.getKey(), true);
+            boolean isOptional = aQute.bnd.osgi.Constants.OPTIONAL.equals(requiresParameter.getValue().get(aQute.bnd.osgi.Constants.RESOLUTION_DIRECTIVE));
+            RequiredResourceTypeCapability.Builder requiredBuilder =
+                    RequiredResourceTypeCapability.builder().withResourceType(resourceType);
+            if (isOptional) {
+                requiredBuilder.withIsOptional();
+            }
+            extractVersionRange(requiresFile, requiredBuilder, requiresParameter.getValue().getVersion());
+            requiredCapabilities.add(requiredBuilder.build());
         }
     }
 
     public void processScriptFile(@NotNull Path resourceTypeDirectory, @NotNull Path scriptPath,
-                                   @NotNull ResourceType resourceType, @NotNull Set<ProvidedResourceTypeCapability> providedCapabilities) {
+                                   @NotNull ResourceType resourceType, @NotNull Set<ProvidedResourceTypeCapability> providedCapabilities,
+                                  boolean inContentPackage) {
         String filePath = scriptPath.toString();
         String extension = FilenameUtils.getExtension(filePath);
         if (StringUtils.isNotEmpty(extension)) {
@@ -158,11 +175,14 @@
                 List<String> selectors = new ArrayList<>();
                 if (pathSegments > 1) {
                     for (int i = 0; i < pathSegments - 1; i++) {
-                        selectors.add(relativeResourceTypeFolder.getName(i).toString());
+                        selectors.add(inContentPackage ?
+                                PlatformNameFormat.getRepositoryName(relativeResourceTypeFolder.getName(i).toString()) :
+                                relativeResourceTypeFolder.getName(i).toString()
+                        );
                     }
                 }
                 String scriptFileName = scriptFile.toString();
-                Script script = Script.parseScript(scriptFileName);
+                Script script = Script.parseScript(inContentPackage ? PlatformNameFormat.getRepositoryPath(scriptFileName) : scriptFileName);
                 if (script != null) {
                     String scriptEngine = scriptEngineMappings.get(script.getScriptExtension());
                     if (scriptEngine != null) {
@@ -236,4 +256,21 @@
             log.warn(String.format("Invalid version range format %s in file %s.", version, requiresFile));
         }
     }
+
+    public void processVaultFile(@NotNull Path entry, @NotNull ResourceType resourceType,
+                                 @NotNull Set<ProvidedResourceTypeCapability> providedCapabilities,
+                                 @NotNull Set<RequiredResourceTypeCapability> requiredCapabilities) {
+        try {
+            VaultContentXmlReader reader = new VaultContentXmlReader(entry);
+            Optional<String> slingResourceSuperType = reader.getSlingResourceSuperType();
+            slingResourceSuperType.ifPresent(
+                    resourceSuperType -> processExtendedResourceType(resourceType, entry, providedCapabilities, requiredCapabilities,
+                            resourceSuperType));
+            if (!reader.getSlingRequiredResourceTypes().isEmpty()) {
+                processRequiredResourceTypes(entry, requiredCapabilities, new ArrayList<>(reader.getSlingRequiredResourceTypes()));
+            }
+        } catch (IOException e) {
+            throw new UncheckedIOException(String.format("Unable to read file %s.", entry), e);
+        }
+    }
 }
diff --git a/src/main/java/org/apache/sling/scriptingbundle/plugin/processor/PathOnlyScriptAnalyser.java b/src/main/java/org/apache/sling/scriptingbundle/plugin/processor/PathOnlyScriptAnalyser.java
index 81f0e3f..666a04c 100644
--- a/src/main/java/org/apache/sling/scriptingbundle/plugin/processor/PathOnlyScriptAnalyser.java
+++ b/src/main/java/org/apache/sling/scriptingbundle/plugin/processor/PathOnlyScriptAnalyser.java
@@ -29,6 +29,7 @@
 
 import org.apache.commons.io.FilenameUtils;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.jackrabbit.vault.util.PlatformNameFormat;
 import org.apache.sling.scriptingbundle.plugin.capability.Capabilities;
 import org.apache.sling.scriptingbundle.plugin.capability.ProvidedScriptCapability;
 import org.apache.sling.scriptingbundle.plugin.capability.RequiredResourceTypeCapability;
@@ -40,13 +41,15 @@
     private final Predicate<Path> isNotAResourceTypeFolder;
     private final Map<String, String> scriptEngineMappings;
     private final FileProcessor fileProcessor;
+    private final boolean inContentPackage;
 
     public PathOnlyScriptAnalyser(@NotNull Logger logger, @NotNull Path scriptsDirectory, @NotNull Map<String, String> scriptEngineMappings,
-                                  @NotNull FileProcessor fileProcessor) {
+                                  @NotNull FileProcessor fileProcessor, boolean inContentPackage) {
         this.scriptsDirectory = scriptsDirectory;
-        this.isNotAResourceTypeFolder = new ResourceTypeFolderPredicate(logger).negate();
+        this.isNotAResourceTypeFolder = new ResourceTypeFolderPredicate(logger, inContentPackage).negate();
         this.scriptEngineMappings = scriptEngineMappings;
         this.fileProcessor = fileProcessor;
+        this.inContentPackage = inContentPackage;
     }
 
     public @NotNull Capabilities getProvidedScriptCapability(@NotNull Path file) {
@@ -68,6 +71,9 @@
                         int dotLastIndex = name.lastIndexOf('.');
                         if (dotLastIndex > -1 && dotLastIndex != name.length() - 1) {
                             String scriptPath = FilenameUtils.normalize("/" + scriptsDirectory.relativize(file).toString(), true);
+                            if (inContentPackage) {
+                                scriptPath = PlatformNameFormat.getRepositoryPath(scriptPath);
+                            }
                             ProvidedScriptCapability providedScriptCapability =
                                     ProvidedScriptCapability.builder(scriptEngineMappings).withPath(scriptPath).build();
                             Path requires = parent.resolve(Constants.REQUIRES_FILE);
diff --git a/src/main/java/org/apache/sling/scriptingbundle/plugin/processor/ResourceTypeFolderAnalyser.java b/src/main/java/org/apache/sling/scriptingbundle/plugin/processor/ResourceTypeFolderAnalyser.java
index b7b6820..d1df4df 100644
--- a/src/main/java/org/apache/sling/scriptingbundle/plugin/processor/ResourceTypeFolderAnalyser.java
+++ b/src/main/java/org/apache/sling/scriptingbundle/plugin/processor/ResourceTypeFolderAnalyser.java
@@ -29,6 +29,7 @@
 import java.util.stream.Stream;
 
 import org.apache.commons.io.FilenameUtils;
+import org.apache.jackrabbit.vault.util.PlatformNameFormat;
 import org.apache.sling.api.resource.type.ResourceType;
 import org.apache.sling.scriptingbundle.plugin.capability.Capabilities;
 import org.apache.sling.scriptingbundle.plugin.capability.ProvidedResourceTypeCapability;
@@ -41,13 +42,16 @@
     private final Logger logger;
     private final Path scriptsDirectory;
     private final ResourceTypeFolderPredicate resourceTypeFolderPredicate;
-    private FileProcessor fileProcessor;
+    private final FileProcessor fileProcessor;
+    private final boolean inContentPackage;
 
-    public ResourceTypeFolderAnalyser(@NotNull Logger logger, @NotNull Path scriptsDirectory, @NotNull FileProcessor fileProcessor) {
+    public ResourceTypeFolderAnalyser(@NotNull Logger logger, @NotNull Path scriptsDirectory, @NotNull FileProcessor fileProcessor,
+                                      boolean inContentPackage) {
         this.logger = logger;
         this.scriptsDirectory = scriptsDirectory;
-        this.resourceTypeFolderPredicate = new ResourceTypeFolderPredicate(logger);
+        this.resourceTypeFolderPredicate = new ResourceTypeFolderPredicate(logger, inContentPackage);
         this.fileProcessor = fileProcessor;
+        this.inContentPackage = inContentPackage;
     }
 
     public Capabilities getCapabilities(@NotNull Path resourceTypeDirectory) {
@@ -56,8 +60,14 @@
         if (resourceTypeDirectory.startsWith(scriptsDirectory) && resourceTypeFolderPredicate.test(resourceTypeDirectory)) {
             try (DirectoryStream<Path> resourceTypeDirectoryStream = Files.newDirectoryStream(resourceTypeDirectory)) {
                 Path relativeResourceTypeDirectory = scriptsDirectory.relativize(resourceTypeDirectory);
-                final ResourceType
-                        resourceType = ResourceType.parseResourceType(FilenameUtils.normalize(relativeResourceTypeDirectory.toString(), true));
+                final ResourceType resourceType =
+                        ResourceType.parseResourceType(
+                            FilenameUtils.normalize(inContentPackage ?
+                                PlatformNameFormat.getRepositoryPath(relativeResourceTypeDirectory.toString()) :
+                                relativeResourceTypeDirectory.toString(),
+                        true
+                            )
+                        );
                 resourceTypeDirectoryStream.forEach(entry -> {
                     if (Files.isRegularFile(entry)) {
                         Path file = entry.getFileName();
@@ -66,8 +76,10 @@
                                 fileProcessor.processExtendsFile(resourceType, entry, providedCapabilities, requiredCapabilities);
                             } else if (Constants.REQUIRES_FILE.equals(file.toString())) {
                                 fileProcessor.processRequiresFile(entry, requiredCapabilities);
+                            } else if (org.apache.jackrabbit.vault.util.Constants.DOT_CONTENT_XML.equals(file.toString())) {
+                                fileProcessor.processVaultFile(entry, resourceType, providedCapabilities, requiredCapabilities);
                             } else {
-                                fileProcessor.processScriptFile(resourceTypeDirectory, entry, resourceType, providedCapabilities);
+                                fileProcessor.processScriptFile(resourceTypeDirectory, entry, resourceType, providedCapabilities, inContentPackage);
                             }
                         }
                     } else if (Files.isDirectory(entry) && !resourceTypeFolderPredicate.test(entry)) {
@@ -82,7 +94,8 @@
                             return true;
                         })) {
                             selectorFilesStream.forEach(
-                                    file -> fileProcessor.processScriptFile(resourceTypeDirectory, file, resourceType, providedCapabilities)
+                                    file -> fileProcessor.processScriptFile(resourceTypeDirectory, file, resourceType,
+                                            providedCapabilities, inContentPackage)
                             );
                         } catch (IOException e) {
                             logger.error(String.format("Unable to scan folder %s.", entry.toString()), e);
diff --git a/src/main/java/org/apache/sling/scriptingbundle/plugin/processor/ResourceTypeFolderPredicate.java b/src/main/java/org/apache/sling/scriptingbundle/plugin/processor/ResourceTypeFolderPredicate.java
index 684c685..fe92ca4 100644
--- a/src/main/java/org/apache/sling/scriptingbundle/plugin/processor/ResourceTypeFolderPredicate.java
+++ b/src/main/java/org/apache/sling/scriptingbundle/plugin/processor/ResourceTypeFolderPredicate.java
@@ -24,14 +24,18 @@
 import java.nio.file.Path;
 import java.util.function.Predicate;
 
+import org.apache.jackrabbit.vault.util.PlatformNameFormat;
+import org.apache.sling.scriptingbundle.plugin.processor.filevault.VaultContentXmlReader;
 import org.osgi.framework.Version;
 
 public class ResourceTypeFolderPredicate implements Predicate<Path> {
 
     private final Logger logger;
+    private final boolean inContentPackage;
 
-    public ResourceTypeFolderPredicate(Logger logger) {
+    public ResourceTypeFolderPredicate(Logger logger, boolean inContentPackage) {
         this.logger = logger;
+        this.inContentPackage = inContentPackage;
     }
 
     @Override
@@ -61,14 +65,18 @@
             } else {
                 resourceTypeLabel = lastSegmentString;
             }
+            if (inContentPackage) {
+                resourceTypeLabel = PlatformNameFormat.getRepositoryPath(resourceTypeLabel);
+            }
             try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(folder, Files::isRegularFile)) {
                 for (Path path : directoryStream) {
                     Path fileName = path.getFileName();
                     if (fileName != null) {
                         String childName = fileName.toString();
-                        Script script = Script.parseScript(childName);
+                        Script script = Script.parseScript(inContentPackage ? PlatformNameFormat.getRepositoryPath(childName) : childName);
                         if (
                             Constants.EXTENDS_FILE.equals(childName) ||
+                            org.apache.jackrabbit.vault.util.Constants.DOT_CONTENT_XML.equals(childName) && new VaultContentXmlReader(path).getSlingResourceSuperType().isPresent() ||
                             (
                                 script != null &&
                                 (
diff --git a/src/main/java/org/apache/sling/scriptingbundle/plugin/processor/filevault/VaultContentXmlReader.java b/src/main/java/org/apache/sling/scriptingbundle/plugin/processor/filevault/VaultContentXmlReader.java
new file mode 100644
index 0000000..edee6bc
--- /dev/null
+++ b/src/main/java/org/apache/sling/scriptingbundle/plugin/processor/filevault/VaultContentXmlReader.java
@@ -0,0 +1,158 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.sling.scriptingbundle.plugin.processor.filevault;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+
+import javax.jcr.PropertyType;
+import javax.xml.XMLConstants;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.jackrabbit.spi.commons.name.NameConstants;
+import org.apache.jackrabbit.vault.util.DocViewProperty;
+import org.apache.sling.jcr.resource.api.JcrResourceConstants;
+import org.apache.sling.scriptingbundle.plugin.processor.Constants;
+import org.jetbrains.annotations.NotNull;
+import org.w3c.dom.Document;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+public class VaultContentXmlReader {
+
+    private static final DocumentBuilderFactory documentBuilderFactory;
+
+    static {
+        try {
+            documentBuilderFactory = DocumentBuilderFactory.newInstance();
+            documentBuilderFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
+            documentBuilderFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
+            documentBuilderFactory.setAttribute(XMLConstants.FEATURE_SECURE_PROCESSING, Boolean.TRUE);
+            documentBuilderFactory.setExpandEntityReferences(false);
+            documentBuilderFactory.setNamespaceAware(true);
+        } catch (IllegalArgumentException e) {
+            throw new IllegalStateException("Cannot disable DTD features.", e);
+        }
+    }
+
+    private final String resourceSuperType;
+    private final Path path;
+    private final Set<String> requiredResourceTypes;
+
+    public VaultContentXmlReader(@NotNull Path path) throws IOException {
+        this.path = path;
+        this.requiredResourceTypes = new HashSet<>();
+        try (BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
+            DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
+            Document document = documentBuilder.parse(new InputSource(reader));
+            NodeList nodeList = document.getElementsByTagNameNS(NameConstants.JCR_ROOT.getNamespaceURI(),
+                    NameConstants.JCR_ROOT.getLocalName());
+            if (nodeList.getLength() == 1 && nodeList.item(0).equals(document.getDocumentElement())) {
+                String resourceSuperTypeRawValue = document.getDocumentElement().getAttributeNS(JcrResourceConstants.SLING_NAMESPACE_URI,
+                        Constants.SLING_RESOURCE_SUPER_TYPE_XML_LOCAL_NAME);
+                if (StringUtils.isNotEmpty(resourceSuperTypeRawValue)) {
+                    DocViewProperty resourceSuperTypeDocViewProperty =
+                            DocViewProperty.parse(JcrResourceConstants.SLING_RESOURCE_SUPER_TYPE_PROPERTY, resourceSuperTypeRawValue);
+                    if ((resourceSuperTypeDocViewProperty.type == PropertyType.STRING ||
+                            resourceSuperTypeDocViewProperty.type == PropertyType.UNDEFINED) && !resourceSuperTypeDocViewProperty.isMulti) {
+                        this.resourceSuperType = resourceSuperTypeDocViewProperty.values[0];
+                    } else {
+                        throw new IllegalArgumentException(String.format("Invalid %s property value (%s) in file %s.",
+                                JcrResourceConstants.SLING_RESOURCE_SUPER_TYPE_PROPERTY, resourceSuperTypeRawValue, path));
+                    }
+                } else {
+                    this.resourceSuperType = null;
+                }
+
+                String requiredResourceTypesRawValue =
+                        document.getDocumentElement().getAttributeNS(JcrResourceConstants.SLING_NAMESPACE_URI
+                                , Constants.SLING_REQUIRED_RESOURCE_TYPES_XML_LOCAL_NAME);
+                if (StringUtils.isNotEmpty(requiredResourceTypesRawValue)) {
+                    DocViewProperty requiredResourceTypesDocViewProperty =
+                            DocViewProperty.parse(Constants.SLING_REQUIRED_RESOURCE_TYPES,
+                                    requiredResourceTypesRawValue);
+                    if (requiredResourceTypesDocViewProperty.isMulti &&
+                            (requiredResourceTypesDocViewProperty.type == PropertyType.STRING ||
+                                    requiredResourceTypesDocViewProperty.type == PropertyType.UNDEFINED)) {
+                        requiredResourceTypes.addAll(Arrays.asList(requiredResourceTypesDocViewProperty.values));
+                    } else {
+                       throw new IllegalArgumentException(String.format("Invalid %s property value (%s) in file %s.",
+                                Constants.SLING_REQUIRED_RESOURCE_TYPES, requiredResourceTypesRawValue, path));
+                    }
+                }
+
+
+            } else {
+                throw new IllegalArgumentException(String.format(
+                        "Path %s does not seem to provide a Docview format - https://jackrabbit.apache.org/filevault/docview.html.",
+                        path));
+            }
+        } catch (ParserConfigurationException | SAXException e) {
+            throw new IOException(e);
+        }
+
+
+    }
+
+    @NotNull
+    public Path getPath() {
+        return path;
+    }
+
+    @NotNull
+    public Optional<String> getSlingResourceSuperType() {
+        return Optional.ofNullable(resourceSuperType);
+    }
+
+    @NotNull
+    public Set<String> getSlingRequiredResourceTypes() {
+        return Collections.unmodifiableSet(requiredResourceTypes);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(path, resourceSuperType, requiredResourceTypes);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj instanceof VaultContentXmlReader) {
+            VaultContentXmlReader other = (VaultContentXmlReader) obj;
+            return Objects.equals(path, other.path) && Objects.equals(resourceSuperType, other.resourceSuperType) &&
+                    Objects.equals(requiredResourceTypes, other.requiredResourceTypes);
+        }
+        return false;
+    }
+}
diff --git a/src/test/java/org/apache/sling/scriptingbundle/plugin/AbstractPluginTest.java b/src/test/java/org/apache/sling/scriptingbundle/plugin/AbstractPluginTest.java
index 9a96b9b..ee5adb2 100644
--- a/src/test/java/org/apache/sling/scriptingbundle/plugin/AbstractPluginTest.java
+++ b/src/test/java/org/apache/sling/scriptingbundle/plugin/AbstractPluginTest.java
@@ -38,6 +38,8 @@
 /** Common base class for both Bnd plugin and Maven plugin */
 public abstract class AbstractPluginTest {
 
+    protected static final Set<String> FILEVAULT_PROJECTS = new HashSet<>(Arrays.asList("filevault-1"));
+
     public abstract PluginExecution executePluginOnProject(String projectName) throws Exception;
     
     public abstract void cleanUp(String projectName) throws Exception;
@@ -197,6 +199,31 @@
     }
 
     @Test
+    public void testFileVault1() throws Exception {
+        try {
+            PluginExecution execution = executePluginOnProject("filevault-1");
+            Capabilities capabilities = execution.getCapabilities();
+            Set<ProvidedResourceTypeCapability> pExpected = new HashSet<>(Arrays.asList(
+                ProvidedResourceTypeCapability.builder().withResourceTypes("my-scripts/image", "/apps/my-scripts/image")
+                        .withScriptEngine("htl").withScriptExtension("html").withExtendsResourceType("generic/image").build(),
+                ProvidedResourceTypeCapability.builder().withResourceTypes("my-scripts/teaser", "/apps/my-scripts/teaser")
+                        .withScriptEngine("htl").withScriptExtension("html").build(),
+                ProvidedResourceTypeCapability.builder().withResourceTypes("my-scripts/escaped:test", "/apps/my-scripts/escaped:test")
+                        .withScriptEngine("htl").withScriptExtension("html").build()
+            ));
+            Set<RequiredResourceTypeCapability> rExpected = new HashSet<>(Arrays.asList(
+               RequiredResourceTypeCapability.builder().withResourceType("generic/image").withIsOptional().build(),
+               RequiredResourceTypeCapability.builder().withResourceType("required/one").withIsOptional().build(),
+               RequiredResourceTypeCapability.builder().withResourceType("required/two").withIsOptional().build(),
+               RequiredResourceTypeCapability.builder().withResourceType("my-scripts/image").build()
+            ));
+            verifyCapabilities(capabilities, pExpected, rExpected, Collections.emptySet());
+        } finally {
+            cleanUp("filevault-1");
+        }
+    }
+
+    @Test
     public void testProject4() throws Exception {
         try {
             PluginExecution execution = executePluginOnProject("project-4");
diff --git a/src/test/java/org/apache/sling/scriptingbundle/plugin/bnd/BundledScriptsScannerPluginTest.java b/src/test/java/org/apache/sling/scriptingbundle/plugin/bnd/BundledScriptsScannerPluginTest.java
index 1f694e7..12a2976 100644
--- a/src/test/java/org/apache/sling/scriptingbundle/plugin/bnd/BundledScriptsScannerPluginTest.java
+++ b/src/test/java/org/apache/sling/scriptingbundle/plugin/bnd/BundledScriptsScannerPluginTest.java
@@ -49,6 +49,9 @@
         builder.setProperties(bndFile.getParentFile(), builder.loadProperties(bndFile));
         builder.set(BundledScriptsScannerPlugin.PROJECT_ROOT_FOLDER, projectRootFolder.toString());
         builder.set(BundledScriptsScannerPlugin.PROJECT_BUILD_FOLDER, projectTargetFolder.toString());
+        if (FILEVAULT_PROJECTS.contains(projectName)) {
+            builder.set("project.packaging", "content-package");
+        }
         return builder;
     }
 
diff --git a/src/test/java/org/apache/sling/scriptingbundle/plugin/maven/MetadataMojoTest.java b/src/test/java/org/apache/sling/scriptingbundle/plugin/maven/MetadataMojoTest.java
index 0c053bb..7b1327e 100644
--- a/src/test/java/org/apache/sling/scriptingbundle/plugin/maven/MetadataMojoTest.java
+++ b/src/test/java/org/apache/sling/scriptingbundle/plugin/maven/MetadataMojoTest.java
@@ -20,6 +20,9 @@
 
 import java.io.File;
 import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
 
 import org.apache.commons.io.FileUtils;
 import org.apache.maven.execution.MavenSession;
@@ -45,6 +48,9 @@
         File projectDirectory = Paths.get("src", "test", "resources", projectName).toFile();
         MavenProject project = mojoRule.readMavenProject(projectDirectory);
         MavenSession session = mojoRule.newMavenSession(project);
+        if (FILEVAULT_PROJECTS.contains(projectName)) {
+            project.setPackaging("content-package");
+        }
         MojoExecution execution = mojoRule.newMojoExecution("metadata");
         MetadataMojo validateMojo = (MetadataMojo) mojoRule.lookupConfiguredMojo(session, execution);
         MojoProject mojoProject = new MojoProject();
diff --git a/src/test/java/org/apache/sling/scriptingbundle/plugin/processor/FileProcessorTest.java b/src/test/java/org/apache/sling/scriptingbundle/plugin/processor/FileProcessorTest.java
index 55e074c..fc1c57e 100644
--- a/src/test/java/org/apache/sling/scriptingbundle/plugin/processor/FileProcessorTest.java
+++ b/src/test/java/org/apache/sling/scriptingbundle/plugin/processor/FileProcessorTest.java
@@ -58,7 +58,7 @@
         Assert.assertEquals(expectedRequiredCapability, requiredCapabilities.iterator().next());
         Assert.assertEquals(1, providedCapabilities.size());
         ProvidedResourceTypeCapability expectedProvidedCapability = ProvidedResourceTypeCapability.builder()
-                .withResourceTypes(new HashSet<String>(Arrays.asList("my/resource", "/apps/my/resource")))
+                .withResourceTypes(new HashSet<>(Arrays.asList("my/resource", "/apps/my/resource")))
                 .withVersion(MY_RESOURCE_TYPE.getVersion())
                 .withExtendsResourceType("org/apache/sling/bar")
                 .build();
@@ -111,7 +111,7 @@
     public void testScriptValid() {
         Path resourceTypeFolder = Paths.get("apps", "my", "resource", "2.0");
         Path script = Paths.get("apps", "my", "resource", "2.0", "selectorb", "selectora.POST.html");
-        processor.processScriptFile(resourceTypeFolder, script, MY_RESOURCE_TYPE, providedCapabilities);
+        processor.processScriptFile(resourceTypeFolder, script, MY_RESOURCE_TYPE, providedCapabilities, false);
         Assert.assertEquals(1, providedCapabilities.size());
         ProvidedResourceTypeCapability expectedProvidedCapability = ProvidedResourceTypeCapability.builder()
                 .withResourceTypes(new HashSet<>(Arrays.asList("my/resource", "/apps/my/resource")))
@@ -128,7 +128,7 @@
     public void testScriptUnknownExtension() {
         Path resourceTypeFolder = Paths.get("scripts",  "apps", "my", "resource", "2.0");
         Path script = Paths.get("scripts", "apps", "my", "resource", "2.0", "selectorb", "selectora.POST.abc");
-        processor.processScriptFile(resourceTypeFolder, script, MY_RESOURCE_TYPE, providedCapabilities);
+        processor.processScriptFile(resourceTypeFolder, script, MY_RESOURCE_TYPE, providedCapabilities, false);
         // this must not throw an exception but a WARN should be emitted in the log to make users aware of potential misconfigurations
         Assert.assertEquals(0, providedCapabilities.size());
     }
diff --git a/src/test/java/org/apache/sling/scriptingbundle/plugin/processor/filevault/VaultContentXmlReaderTest.java b/src/test/java/org/apache/sling/scriptingbundle/plugin/processor/filevault/VaultContentXmlReaderTest.java
new file mode 100644
index 0000000..fb5f702
--- /dev/null
+++ b/src/test/java/org/apache/sling/scriptingbundle/plugin/processor/filevault/VaultContentXmlReaderTest.java
@@ -0,0 +1,74 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.sling.scriptingbundle.plugin.processor.filevault;
+
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Optional;
+
+import org.apache.sling.scriptingbundle.plugin.processor.Slf4jLogger;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+public class VaultContentXmlReaderTest {
+
+    @Test
+    public void testReader() throws IOException {
+        VaultContentXmlReader vaultContentXmlReader = new VaultContentXmlReader(Paths.get("src/test/resources" +
+                "/filevault-1/src/main/content/jcr_root/apps/my-scripts/image/.content.xml"));
+        Optional<String> resourceSuperType = vaultContentXmlReader.getSlingResourceSuperType();
+        assertTrue(resourceSuperType.isPresent());
+        assertEquals("generic/image", resourceSuperType.get());
+
+        assertTrue(
+                vaultContentXmlReader.getSlingRequiredResourceTypes().size() == 2 &&
+                        vaultContentXmlReader.getSlingRequiredResourceTypes().containsAll(Arrays.asList("required/one", "required/two"))
+        );
+
+    }
+
+    @Test
+    public void multipleValueResourceSuperType() throws IOException {
+        IllegalArgumentException iae = assertThrows(IllegalArgumentException.class, () -> new VaultContentXmlReader(
+                Paths.get("src/test/resources/filevault-docview-examples/multiple-resource-super-type.xml")));
+        assertTrue(iae.getMessage().contains("Invalid sling:resourceSuperType property value ([generic/image])"));
+    }
+
+    @Test
+    public void singleValueRequiredResourceTypes() throws IOException {
+        IllegalArgumentException iae = assertThrows(IllegalArgumentException.class, () -> new VaultContentXmlReader(
+                Paths.get("src/test/resources/filevault-docview-examples/single-value-required-resource-types.xml")));
+        assertTrue(iae.getMessage().contains("Invalid sling:requiredResourceTypes property value (required/one,required/two)"));
+    }
+
+    @Test
+    public void notADocView() throws IOException {
+        IllegalArgumentException iae = assertThrows(IllegalArgumentException.class, () -> new VaultContentXmlReader(
+                Paths.get("src/test/resources/filevault-docview-examples/not-a-docview.xml")));
+        assertTrue(iae.getMessage().contains("does not seem to provide a Docview format - https://jackrabbit.apache.org/filevault/docview.html"));
+    }
+}
diff --git a/src/test/resources/filevault-1/bnd.bnd b/src/test/resources/filevault-1/bnd.bnd
new file mode 100644
index 0000000..dd5a963
--- /dev/null
+++ b/src/test/resources/filevault-1/bnd.bnd
@@ -0,0 +1,18 @@
+# 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.
+-plugin:    org.apache.sling.scriptingbundle.plugin.bnd.BundledScriptsScannerPlugin; \
+            sourceDirectories="src/main/content/jcr_root"
diff --git a/src/test/resources/filevault-1/pom.xml b/src/test/resources/filevault-1/pom.xml
new file mode 100644
index 0000000..c9f6949
--- /dev/null
+++ b/src/test/resources/filevault-1/pom.xml
@@ -0,0 +1,49 @@
+<?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.
+  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>org.apache.sling</groupId>
+    <artifactId>scriptingbundle-maven-plugin-filevault-1</artifactId>
+    <version>0.0.1</version>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.sling</groupId>
+                <artifactId>scriptingbundle-maven-plugin</artifactId>
+                <configuration>
+                    <sourceDirectories>src/main/content/jcr_root</sourceDirectories>
+                </configuration>
+                <executions>
+                    <execution>
+                        <phase>prepare-package</phase>
+                        <goals>
+                            <goal>metadata</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/src/test/resources/filevault-1/src/main/content/jcr_root/apps/my-scripts/_escaped_test/.content.xml b/src/test/resources/filevault-1/src/main/content/jcr_root/apps/my-scripts/_escaped_test/.content.xml
new file mode 100644
index 0000000..3409a80
--- /dev/null
+++ b/src/test/resources/filevault-1/src/main/content/jcr_root/apps/my-scripts/_escaped_test/.content.xml
@@ -0,0 +1,22 @@
+<?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:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0"
+          jcr:title="Escaped Test"
+/>
diff --git a/src/test/resources/filevault-1/src/main/content/jcr_root/apps/my-scripts/_escaped_test/_escaped_test.html b/src/test/resources/filevault-1/src/main/content/jcr_root/apps/my-scripts/_escaped_test/_escaped_test.html
new file mode 100644
index 0000000..2853663
--- /dev/null
+++ b/src/test/resources/filevault-1/src/main/content/jcr_root/apps/my-scripts/_escaped_test/_escaped_test.html
@@ -0,0 +1,18 @@
+<!--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+  ~ 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.
+  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-->
diff --git a/src/test/resources/filevault-1/src/main/content/jcr_root/apps/my-scripts/image/.content.xml b/src/test/resources/filevault-1/src/main/content/jcr_root/apps/my-scripts/image/.content.xml
new file mode 100644
index 0000000..77528b2
--- /dev/null
+++ b/src/test/resources/filevault-1/src/main/content/jcr_root/apps/my-scripts/image/.content.xml
@@ -0,0 +1,23 @@
+<?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:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0"
+          sling:resourceSuperType="generic/image"
+          sling:requiredResourceTypes="[required/one,required/two]"
+/>
diff --git a/src/test/resources/filevault-1/src/main/content/jcr_root/apps/my-scripts/image/image.html b/src/test/resources/filevault-1/src/main/content/jcr_root/apps/my-scripts/image/image.html
new file mode 100644
index 0000000..2853663
--- /dev/null
+++ b/src/test/resources/filevault-1/src/main/content/jcr_root/apps/my-scripts/image/image.html
@@ -0,0 +1,18 @@
+<!--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+  ~ 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.
+  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-->
diff --git a/src/test/resources/filevault-1/src/main/content/jcr_root/apps/my-scripts/teaser/.content.xml b/src/test/resources/filevault-1/src/main/content/jcr_root/apps/my-scripts/teaser/.content.xml
new file mode 100644
index 0000000..ce48607
--- /dev/null
+++ b/src/test/resources/filevault-1/src/main/content/jcr_root/apps/my-scripts/teaser/.content.xml
@@ -0,0 +1,22 @@
+<?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:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0"
+          sling:requiredResourceTypes="[my-scripts/image]"
+/>
diff --git a/src/test/resources/filevault-1/src/main/content/jcr_root/apps/my-scripts/teaser/teaser.html b/src/test/resources/filevault-1/src/main/content/jcr_root/apps/my-scripts/teaser/teaser.html
new file mode 100644
index 0000000..2853663
--- /dev/null
+++ b/src/test/resources/filevault-1/src/main/content/jcr_root/apps/my-scripts/teaser/teaser.html
@@ -0,0 +1,18 @@
+<!--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+  ~ 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.
+  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-->
diff --git a/src/test/resources/filevault-docview-examples/multiple-resource-super-type.xml b/src/test/resources/filevault-docview-examples/multiple-resource-super-type.xml
new file mode 100644
index 0000000..eaf0192
--- /dev/null
+++ b/src/test/resources/filevault-docview-examples/multiple-resource-super-type.xml
@@ -0,0 +1,23 @@
+<?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:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0"
+          sling:resourceSuperType="[generic/image]"
+          sling:requiredResourceTypes="[required/one,required/two]"
+/>
diff --git a/src/test/resources/filevault-docview-examples/not-a-docview.xml b/src/test/resources/filevault-docview-examples/not-a-docview.xml
new file mode 100644
index 0000000..0bbb270
--- /dev/null
+++ b/src/test/resources/filevault-docview-examples/not-a-docview.xml
@@ -0,0 +1,21 @@
+<?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 sling:resourceSuperType="a/b/c" xmlns:jcr="http://this/is/not/the/namespace/you/are/looking/for"
+          xmlns:sling="http://this/is/not/the/namespace/you/are/looking/for"/>
diff --git a/src/test/resources/filevault-docview-examples/single-value-required-resource-types.xml b/src/test/resources/filevault-docview-examples/single-value-required-resource-types.xml
new file mode 100644
index 0000000..92e2a4f
--- /dev/null
+++ b/src/test/resources/filevault-docview-examples/single-value-required-resource-types.xml
@@ -0,0 +1,23 @@
+<?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:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0"
+          sling:resourceSuperType="generic/image"
+          sling:requiredResourceTypes="required/one,required/two"
+/>