JCRVLT-471 allow to include additional files/folders in META-INF (#48)

diff --git a/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/AbstractMetadataPackageMojo.java b/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/AbstractMetadataPackageMojo.java
index d2d13d7..a3a1a43 100644
--- a/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/AbstractMetadataPackageMojo.java
+++ b/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/AbstractMetadataPackageMojo.java
@@ -37,6 +37,10 @@
 
     private static final String PROPERTIES_EMBEDDEDFILESMAP_KEY = "embeddedfiles.map";
 
+    protected String getProjectRelativeFilePath(File file) {
+        return "'" + project.getBasedir().toPath().relativize(file.toPath()).toString() + "'";
+    }
+
     protected static File getFirstExistingDirectory(File[] directories) {
         for (File dir: directories) {
             if (dir.exists() && dir.isDirectory()) {
diff --git a/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/AbstractValidateMojo.java b/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/AbstractValidateMojo.java
index 8c9cd22..236fd76 100644
--- a/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/AbstractValidateMojo.java
+++ b/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/AbstractValidateMojo.java
@@ -16,6 +16,7 @@
  */
 package org.apache.jackrabbit.filevault.maven.packaging;
 
+import java.io.File;
 import java.io.IOException;
 import java.util.Collection;
 import java.util.Comparator;
@@ -173,6 +174,10 @@
      */
     public static final Artifact IGNORE_ARTIFACT = new DefaultArtifact("ignore", "ignore", "1.0", "", "", "", null);
 
+    protected String getProjectRelativeFilePath(File file) {
+        return "'" + project.getBasedir().toPath().relativize(file.toPath()).toString() + "'";
+    }
+
     public AbstractValidateMojo() {
         super();
         this.validationExecutorFactory = new ValidationExecutorFactory(this.getClass().getClassLoader());
diff --git a/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/GenerateMetadataMojo.java b/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/GenerateMetadataMojo.java
index 13a7860..e5537b4 100644
--- a/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/GenerateMetadataMojo.java
+++ b/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/GenerateMetadataMojo.java
@@ -449,9 +449,9 @@
             // only execute in case of changes towards the filter.xml as the generated one contains a merge
             if (filterSource != null) {
                 if (buildContext.hasDelta(filterSource)) {
-                    getLog().debug("Detecting a change on '" + filterSource + "' therefore not cancelling build");
+                    getLog().debug("Detecting a change on " + getProjectRelativeFilePath(filterSource) + " therefore not cancelling build");
                 } else {
-                    getLog().debug("'" + filterSource + "' unchanged therefore cancelling build");
+                    getLog().debug(getProjectRelativeFilePath(filterSource) + " unchanged therefore cancelling build");
                     return;
                 }
             } else {
@@ -558,7 +558,7 @@
             try {
                 filters.load(filterFile);
             } catch (ConfigurationException e) {
-                throw new IOException("Error loading filter file '" + filterFile + "'", e);
+                throw new IOException("Error loading filter file " + getProjectRelativeFilePath(filterFile) + "", e);
             }
 
             getLog().warn("The project is using a filter.xml provided via the resource plugin.");
@@ -618,7 +618,7 @@
             try {
                 sourceFilters.load(filterSource);
             } catch (ConfigurationException e) {
-                throw new IOException("Error loading filter file '" + filterSource + "'", e);
+                throw new IOException("Error loading filter file " + getProjectRelativeFilePath(filterSource), e);
             }
         }
 
diff --git a/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/ValidateFilesMojo.java b/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/ValidateFilesMojo.java
index af9693c..fd298cb 100644
--- a/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/ValidateFilesMojo.java
+++ b/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/ValidateFilesMojo.java
@@ -177,7 +177,7 @@
                 metaInfRootDirectory = metaInfoVaultSourceDirectory.getParentFile();
             }
             File generatedMetaInfRootDirectory = new File(workDirectory, Constants.META_INF);
-            getLog().info("Using generatedMetaInfRootDirectory: " + generatedMetaInfRootDirectory + " and metaInfRootDir: " + metaInfRootDirectory);
+            getLog().info("Validate files in generatedMetaInfRootDirectory " + getProjectRelativeFilePath(generatedMetaInfRootDirectory) + " and metaInfRootDir " + getProjectRelativeFilePath(generatedMetaInfRootDirectory));
             ValidationContext context = new DirectoryValidationContext(generatedMetaInfRootDirectory, metaInfRootDirectory, resolver, getLog());
             ValidationExecutor executor = validationExecutorFactory.createValidationExecutor(context, false, false, getValidatorSettingsForPackage(context.getProperties().getId(), false));
             if (executor == null) {
@@ -205,7 +205,7 @@
         scanner.setExcludes(excludes);
         scanner.addDefaultExcludes();
         scanner.scan();
-        getLog().info("Scanning baseDir '" + baseDir + "'...");
+        getLog().info("Scanning baseDir " + getProjectRelativeFilePath(baseDir) + "...");
         List<String> sortedFileNames = Arrays.asList(scanner.getIncludedFiles());
         sortedFileNames.sort(new DotContentXmlFirstComparator());
         for (String fileName : sortedFileNames) {
@@ -220,24 +220,24 @@
     private void validateFile(ValidationExecutor executor, File baseDir, boolean isMetaInf, String relativeFile) {
         File absoluteFile = new File(baseDir, relativeFile);
         validationHelper.clearPreviousValidationMessages(buildContext, absoluteFile);
-        getLog().debug("Validating file '" + absoluteFile + "'...");
+        getLog().debug("Validating file " + getProjectRelativeFilePath(absoluteFile) + "...");
         try (InputStream input = new FileInputStream(absoluteFile)) {
             validateInputStream(executor, input, baseDir, isMetaInf, relativeFile);
         } catch (FileNotFoundException e) {
-            getLog().error("Could not find file " + absoluteFile, e);
+            getLog().error("Could not find file " + getProjectRelativeFilePath(absoluteFile), e);
         } catch (IOException e) {
-            getLog().error("Could not validate file " + absoluteFile, e);
+            getLog().error("Could not validate file " + getProjectRelativeFilePath(absoluteFile), e);
         }
     }
     
     private void validateFolder(ValidationExecutor executor, File baseDir, boolean isMetaInf, String relativeFile) {
         File absoluteFile = new File(baseDir, relativeFile);
         validationHelper.clearPreviousValidationMessages(buildContext, absoluteFile);
-        getLog().debug("Validating folder '" + absoluteFile + "'...");
+        getLog().debug("Validating folder " + getProjectRelativeFilePath(absoluteFile) + "...");
         try {
             validateInputStream(executor, null, baseDir, isMetaInf, relativeFile);
         } catch (IOException e) {
-            getLog().error("Could not validate folder " + absoluteFile, e);
+            getLog().error("Could not validate folder " + getProjectRelativeFilePath(absoluteFile), e);
         }
     }
     
diff --git a/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/ValidatePackageMojo.java b/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/ValidatePackageMojo.java
index a7a78f5..748e322 100644
--- a/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/ValidatePackageMojo.java
+++ b/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/ValidatePackageMojo.java
@@ -106,7 +106,7 @@
     }
 
     private void validatePackage(File file) throws IOException, ParserConfigurationException, SAXException, MojoExecutionException {
-        getLog().info("Start validating package '" + file + "'...");
+        getLog().info("Start validating package " + getProjectRelativeFilePath(file) + "...");
 
         // open file to extract the meta data for the validation context
         ArchiveValidationContextImpl context;
@@ -121,7 +121,7 @@
             } else {
                 throw new MojoExecutionException("No registered validators found!");
             }
-            getLog().debug("End validating package '" + file + "'.");
+            getLog().debug("End validating package " + getProjectRelativeFilePath(file) + ".");
         }
     }
 
diff --git a/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/VaultMojo.java b/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/VaultMojo.java
index 61ebda2..60c4ff0 100644
--- a/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/VaultMojo.java
+++ b/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/VaultMojo.java
@@ -79,6 +79,13 @@
 
     @Component
     private ArtifactHandlerManager artifactHandlerManager;
+    
+    /**
+     * The directory that contains additional files and folders to end up in the package's META-INF folder.
+     * Every file and subfolder is considered except for the subfolder named {@code vault} and a file named {@code MANIFEST.MF}.
+     */
+    @Parameter(property = "vault.metaInfDirectory", required = false)
+    File metaInfDirectory;
 
     /** Set to {@code true} to fail the build in case of files are being contained in the {@code jcrRootSourceDirectory} which are not
      * covered by the filter rules and therefore would not end up in the package. */
@@ -235,7 +242,7 @@
         Path destFile = Paths.get(destFileName);
         if ((destFile.startsWith(Constants.ROOT_DIR) && enableJcrRootFiltering) ||
             (destFile.startsWith(Constants.META_INF) && enableMetaInfFiltering)) {
-            getLog().info("Apply filtering to " + sourceFile);
+            getLog().info("Apply filtering to " + getProjectRelativeFilePath(sourceFile));
             Resource resource = new Resource();
             resource.setDirectory(sourceFile.getParent());
             resource.setIncludes(Collections.singletonList(sourceFile.getName()));
@@ -243,7 +250,7 @@
             File newTargetDirectory = applyFiltering(destFile.getParent().toString(), mavenResourcesExecution, resource);
             sourceFile = new File(newTargetDirectory, sourceFile.getName());
         }
-        getLog().debug("Adding file '" + sourceFile + "' to package at '" + destFileName + "'");
+        getLog().debug("Adding file " + getProjectRelativeFilePath(sourceFile) + " to package at " + destFileName + "'");
         archiver.addFile(sourceFile, destFileName);
 
     }
@@ -260,7 +267,7 @@
         if ((fileSet.getPrefix().startsWith(Constants.ROOT_DIR) && enableJcrRootFiltering) ||
             (fileSet.getPrefix().startsWith(Constants.META_INF) && enableMetaInfFiltering)) {
             
-            getLog().info("Apply filtering to FileSet below " + fileSet.getDirectory());
+            getLog().info("Apply filtering to FileSet below " + getProjectRelativeFilePath(fileSet.getDirectory()));
             Resource resource = new Resource();
             resource.setDirectory(fileSet.getDirectory().getPath());
             if (fileSet.getIncludes() != null) {
@@ -377,32 +384,39 @@
     public void execute() throws MojoExecutionException, MojoFailureException {
         final File finalFile = getZipFile(outputDirectory, finalName, classifier);
 
-        MavenResourcesExecution mavenResourcesExection = setupMavenResourcesExecution();
+        MavenResourcesExecution mavenResourcesExecution = setupMavenResourcesExecution();
         try {
-            // find the meta-inf source directory
-            File metaInfDirectory = getMetaInfVaultSourceDirectory();
-            // find the source directory
-            final File jcrSourceDirectory = getJcrSourceDirectory();
-            if (jcrSourceDirectory != null) {
-                getLog().info("Packaging content from " + jcrSourceDirectory.getPath());
+            ContentPackageArchiver contentPackageArchiver = new ContentPackageArchiver();
+            contentPackageArchiver.setEncoding(resourceEncoding);
+
+            if (metaInfDirectory != null) {
+                if (metaInfDirectory.exists() && metaInfDirectory.isDirectory()) {
+                    DefaultFileSet fileSet = createFileSet(metaInfDirectory, Constants.META_INF + "/",
+                            Collections.singletonList(Constants.VAULT_DIR));
+                    addFileSetToArchive(mavenResourcesExecution, contentPackageArchiver, fileSet);
+                    getLog().info("Include additional META-INF files/folders from " + getProjectRelativeFilePath(metaInfDirectory) + " in package.");
+                } else {
+                    getLog().warn("Given metaInfDirectory " + getProjectRelativeFilePath(metaInfDirectory) + " does not exist or is no directory. It won't be included in the package.");
+                }
             }
+            
+            // find the meta-inf/vault source directory
+            File metaInfVaultDirectory = getMetaInfVaultSourceDirectory();
+            
             // retrieve filters
             Filters filters = loadGeneratedFilterFile();
             Map<String, File> embeddedFiles = getEmbeddedFilesMap();
 
-            ContentPackageArchiver contentPackageArchiver = new ContentPackageArchiver();
-            contentPackageArchiver.setEncoding(resourceEncoding);
-
             // A map with key = relative file in zip and value = absolute source file name)
             Map<File, File> duplicateFiles = new HashMap<>();
             contentPackageArchiver.setIncludeEmptyDirs(true);
-            if (metaInfDirectory != null) {
+            if (metaInfVaultDirectory != null) {
                 // first add the metadata from the metaInfDirectory (they should take precedence over the generated ones from workDirectory,
                 // except for the filter.xml, which should always come from the work directory)
-                DefaultFileSet fileSet = createFileSet(metaInfDirectory, Constants.META_DIR + "/",
+                DefaultFileSet fileSet = createFileSet(metaInfVaultDirectory, Constants.META_DIR + "/",
                         Collections.singletonList(Constants.FILTER_XML));
                 duplicateFiles.putAll(getOverwrittenProtectedFiles(fileSet, true));
-                addFileSetToArchive(mavenResourcesExection, contentPackageArchiver, fileSet);
+                addFileSetToArchive(mavenResourcesExecution, contentPackageArchiver, fileSet);
             }
             // then add all files from the workDirectory (they might overlap with the ones from metaInfDirectory, but the duplicates are
             // just ignored in the package)
@@ -410,8 +424,8 @@
             // issue warning in case of overlaps
             Map<File, File> overwrittenWorkFiles = getOverwrittenProtectedFiles(fileSet, true);
             for (Entry<File, File> entry : overwrittenWorkFiles.entrySet()) {
-                String message = "Found duplicate file '" + entry.getKey() + "' from sources '" + protectedFiles.get(entry.getKey())
-                        + "' and '" + entry.getValue() + "'.";
+                String message = "Found duplicate file '" + entry.getKey() + "' from sources " + getProjectRelativeFilePath(protectedFiles.get(entry.getKey()))
+                        + " and " + getProjectRelativeFilePath(entry.getValue()) + ".";
 
                 // INFO for the static ones all others warn
                 if (STATIC_META_INF_FILES.contains(entry.getKey())) {
@@ -420,23 +434,25 @@
                     getLog().warn(message);
                 }
             }
-            addFileSetToArchive(mavenResourcesExection, contentPackageArchiver, fileSet);
+            addFileSetToArchive(mavenResourcesExecution, contentPackageArchiver, fileSet);
 
             // add embedded files
             for (Map.Entry<String, File> entry : embeddedFiles.entrySet()) {
                 protectedFiles.put(new File(entry.getKey()), entry.getValue());
-                addFileToArchive(mavenResourcesExection, contentPackageArchiver, entry.getValue(), entry.getKey());
+                addFileToArchive(mavenResourcesExecution, contentPackageArchiver, entry.getValue(), entry.getKey());
             }
-
+            // find the source directory
+            final File jcrSourceDirectory = getJcrSourceDirectory();
             // include content from build only if it exists
             if (jcrSourceDirectory != null && jcrSourceDirectory.exists()) {
-                Map<File, File> overwrittenFiles = addSourceDirectory(mavenResourcesExection, contentPackageArchiver, jcrSourceDirectory, filters, embeddedFiles);
+                getLog().info("Packaging content from " + getProjectRelativeFilePath(jcrSourceDirectory));
+                Map<File, File> overwrittenFiles = addSourceDirectory(mavenResourcesExecution, contentPackageArchiver, jcrSourceDirectory, filters, embeddedFiles);
                 duplicateFiles.putAll(overwrittenFiles);
 
                 if (!duplicateFiles.isEmpty()) {
                     for (Entry<File, File> entry : duplicateFiles.entrySet()) {
-                        String message = "Found duplicate file '" + entry.getKey() + "' from sources '" + protectedFiles.get(entry.getKey())
-                                + "' and '" + entry.getValue() + "'.";
+                        String message = "Found duplicate file '" + entry.getKey() + "' from sources " + getProjectRelativeFilePath(protectedFiles.get(entry.getKey()))
+                                + " and " + getProjectRelativeFilePath(entry.getValue()) + ".";
                         if (failOnDuplicateEntries) {
                             getLog().error(message);
                         } else {
@@ -454,8 +470,8 @@
                         contentPackageArchiver.getFiles().keySet());
                 if (!uncoveredFiles.isEmpty()) {
                     for (File uncoveredFile : uncoveredFiles) {
-                        String message = "File '" + uncoveredFile
-                                + "' not covered by a filter rule and therefore not contained in the resulting package";
+                        String message = "File " + getProjectRelativeFilePath(uncoveredFile)
+                                + " not covered by a filter rule and therefore not contained in the resulting package";
                         if (failOnUncoveredSourceFiles) {
                             getLog().error(message);
                         } else {
@@ -573,7 +589,7 @@
         // is there an according .content.xml available? (ignore full-coverage files)
         File genericAggregate = new File(inputFile, Constants.DOT_CONTENT_XML);
         if (genericAggregate.exists()) {
-            getLog().debug("Adding ancestor file '" + genericAggregate + "' to package at '" + destFile + "/" + Constants.DOT_CONTENT_XML +"'");
+            getLog().debug("Adding ancestor file " + getProjectRelativeFilePath(genericAggregate) + " to package at '" + destFile + "/" + Constants.DOT_CONTENT_XML +"'");
             contentPackageArchiver.addFile(genericAggregate, destFile + "/" + Constants.DOT_CONTENT_XML);
         }
         addAncestors(contentPackageArchiver, inputFile.getParentFile(), inputRootFile, StringUtils.chomp(destFile, "/"));
diff --git a/src/test/java/org/apache/jackrabbit/filevault/maven/packaging/it/DefaultProjectIT.java b/src/test/java/org/apache/jackrabbit/filevault/maven/packaging/it/DefaultProjectIT.java
index 0e599f6..c8c8445 100644
--- a/src/test/java/org/apache/jackrabbit/filevault/maven/packaging/it/DefaultProjectIT.java
+++ b/src/test/java/org/apache/jackrabbit/filevault/maven/packaging/it/DefaultProjectIT.java
@@ -155,11 +155,20 @@
                 .build()
                 .verifyExpectedFilesChecksum();
     }
-    
+
     @Test
     public void empty_package() throws VerificationException, IOException {
         new ProjectBuilder()
         .setTestProjectDir(TEST_PROJECT_NAME + "empty")
         .build();
     }
+
+    @Test
+    public void additional_metainf_files() throws Exception {
+        new ProjectBuilder()
+                .setTestProjectDir(TEST_PROJECT_NAME + "additional-metainf-files")
+                .build()
+                .verifyExpectedFiles()
+                .verifyExpectedManifest();
+    }
 }
diff --git a/src/test/java/org/apache/jackrabbit/filevault/maven/packaging/it/FilterIT.java b/src/test/java/org/apache/jackrabbit/filevault/maven/packaging/it/FilterIT.java
index 1961cb0..f0a0daa 100644
--- a/src/test/java/org/apache/jackrabbit/filevault/maven/packaging/it/FilterIT.java
+++ b/src/test/java/org/apache/jackrabbit/filevault/maven/packaging/it/FilterIT.java
@@ -154,6 +154,6 @@
     @Test 
     public void test_filter_not_covering_all_files() throws Exception {
         ProjectBuilder builder = verify("filter-not-covering-all-files", true);
-        builder.verifyExpectedLogLines(new File(builder.getTestProjectDir(), "jcr_root/apps/.content.xml").getAbsolutePath());
+        builder.verifyExpectedLogLines(new File("jcr_root/apps/.content.xml").toString());
     }
 }
diff --git a/src/test/java/org/apache/jackrabbit/filevault/maven/packaging/it/ProjectBuilder.java b/src/test/java/org/apache/jackrabbit/filevault/maven/packaging/it/ProjectBuilder.java
index 4c82551..f0ab5b3 100644
--- a/src/test/java/org/apache/jackrabbit/filevault/maven/packaging/it/ProjectBuilder.java
+++ b/src/test/java/org/apache/jackrabbit/filevault/maven/packaging/it/ProjectBuilder.java
@@ -262,7 +262,7 @@
     }
 
     static List<String> verifyPackageZipEntries(File packageFile) throws IOException {
-        assertTrue("Project generates package file at " + packageFile, packageFile.exists());
+        assertTrue("Project did not generate package file at " + packageFile, packageFile.exists());
 
         List<String> pkgZipEntries = new ArrayList<>();
         try (JarFile jar = new JarFile(packageFile)) {
@@ -276,7 +276,7 @@
         if ("META-INF/".equals(first)) {
             first = pkgZipEntries.get(1);
         }
-        assertEquals("MANIFEST.MF must be first entry in package " + packageFile, "META-INF/MANIFEST.MF", first);
+        assertEquals("MANIFEST.MF is not first entry in package " + packageFile, "META-INF/MANIFEST.MF", first);
 
         // ensure that there is a jcr_root directory
         assertTrue("Package does not contain mandatory 'jcr_root' folder in package " + packageFile, pkgZipEntries.contains("jcr_root/"));
@@ -286,7 +286,7 @@
         if (buildExpectedToFail) {
             return this;
         }
-        assertEquals("Property '" + key + "' has correct value", value, getPackageProperty(key));
+        assertEquals("Property '" + key + "' has incorrect value", value, getPackageProperty(key));
         return this;
     }
 
@@ -338,7 +338,7 @@
     public ProjectBuilder verifyExpectedFiles(File expectedFilesFile, List<String> pkgZipEntries) throws IOException {
         // first check that only the expected entries are there in the package (regardless of the order)
         List<String> expectedEntries = Files.readAllLines(expectedFilesFile.toPath(), StandardCharsets.UTF_8);
-        assertEquals("Package contains the expected entry names",
+        assertEquals("Package does not contain the expected entry names",
                 toTidyString(expectedEntries),
                 toTidyString(pkgZipEntries));
         return this;
diff --git a/src/test/resources/test-projects/default-test-projects/additional-metainf-files/additional-metainf/MANIFEST.MF b/src/test/resources/test-projects/default-test-projects/additional-metainf-files/additional-metainf/MANIFEST.MF
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/test/resources/test-projects/default-test-projects/additional-metainf-files/additional-metainf/MANIFEST.MF
diff --git a/src/test/resources/test-projects/default-test-projects/additional-metainf-files/additional-metainf/license.txt b/src/test/resources/test-projects/default-test-projects/additional-metainf-files/additional-metainf/license.txt
new file mode 100644
index 0000000..13cd903
--- /dev/null
+++ b/src/test/resources/test-projects/default-test-projects/additional-metainf-files/additional-metainf/license.txt
@@ -0,0 +1,16 @@
+<!--
+  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.
+  -->
\ No newline at end of file
diff --git a/src/test/resources/test-projects/default-test-projects/additional-metainf-files/additional-metainf/subfolder/anotherfile.txt b/src/test/resources/test-projects/default-test-projects/additional-metainf-files/additional-metainf/subfolder/anotherfile.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/test/resources/test-projects/default-test-projects/additional-metainf-files/additional-metainf/subfolder/anotherfile.txt
diff --git a/src/test/resources/test-projects/default-test-projects/additional-metainf-files/expected-files.txt b/src/test/resources/test-projects/default-test-projects/additional-metainf-files/expected-files.txt
new file mode 100644
index 0000000..8d9a65d
--- /dev/null
+++ b/src/test/resources/test-projects/default-test-projects/additional-metainf-files/expected-files.txt
@@ -0,0 +1,20 @@
+META-INF/
+META-INF/MANIFEST.MF
+META-INF/license.txt
+META-INF/subfolder/
+META-INF/subfolder/anotherfile.txt
+META-INF/maven/
+META-INF/maven/org.apache.jackrabbit.filevault/
+META-INF/maven/org.apache.jackrabbit.filevault/package-plugin-test-pkg/
+META-INF/maven/org.apache.jackrabbit.filevault/package-plugin-test-pkg/pom.properties
+META-INF/maven/org.apache.jackrabbit.filevault/package-plugin-test-pkg/pom.xml
+META-INF/vault/
+META-INF/vault/config.xml
+META-INF/vault/filter.xml
+META-INF/vault/properties.xml
+META-INF/vault/settings.xml
+jcr_root/
+jcr_root/apps/
+jcr_root/apps/htl/
+jcr_root/apps/htl/test/
+jcr_root/apps/htl/test/test.html
\ No newline at end of file
diff --git a/src/test/resources/test-projects/default-test-projects/additional-metainf-files/expected-manifest.txt b/src/test/resources/test-projects/default-test-projects/additional-metainf-files/expected-manifest.txt
new file mode 100644
index 0000000..5468d39
--- /dev/null
+++ b/src/test/resources/test-projects/default-test-projects/additional-metainf-files/expected-manifest.txt
@@ -0,0 +1,9 @@
+Content-Package-Description:Packaging test
+Content-Package-Id:org.apache.jackrabbit.filevault:package-plugin-test-pkg:1.0.0-SNAPSHOT
+Content-Package-Roots:/apps/htl/test
+Content-Package-Type:mixed
+Implementation-Title:Packaging test
+Implementation-Version:1.0.0-SNAPSHOT
+Import-Package:javax.jcr;version="[2.0.0,3.0.0)"
+Import-Package:org.apache.sling.scripting.sightly;version="[2.0.0,3.0.0)"
+Manifest-Version:1.0
\ No newline at end of file
diff --git a/src/test/resources/test-projects/default-test-projects/additional-metainf-files/jcr_root/apps/htl/test/test.html b/src/test/resources/test-projects/default-test-projects/additional-metainf-files/jcr_root/apps/htl/test/test.html
new file mode 100644
index 0000000..98e2e96
--- /dev/null
+++ b/src/test/resources/test-projects/default-test-projects/additional-metainf-files/jcr_root/apps/htl/test/test.html
@@ -0,0 +1,28 @@
+<!--
+  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 html>
+<html lang="en-US">
+<head>
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+    <title>Default Page Title</title>
+<body>
+<!-- note that the session is not a good example of a use class. but for only used the sake of this test -->
+<div class="session" data-sly-use.session="javax.jcr.Session">
+    <p>${session.userId}</p>
+</div>
+</body>
+</html>
diff --git a/src/test/resources/test-projects/default-test-projects/additional-metainf-files/pom.xml b/src/test/resources/test-projects/default-test-projects/additional-metainf-files/pom.xml
new file mode 100755
index 0000000..56f85dc
--- /dev/null
+++ b/src/test/resources/test-projects/default-test-projects/additional-metainf-files/pom.xml
@@ -0,0 +1,93 @@
+<!--
+  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/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <!-- ====================================================================== -->
+    <!-- P R O J E C T  D E S C R I P T I O N                                   -->
+    <!-- ====================================================================== -->
+    <groupId>org.apache.jackrabbit.filevault</groupId>
+    <artifactId>package-plugin-test-pkg</artifactId>
+    <version>1.0.0-SNAPSHOT</version>
+    <packaging>content-package</packaging>
+    <name>Packaging test</name>
+
+    <build>
+        <sourceDirectory>src/content/jcr_root</sourceDirectory>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.jackrabbit</groupId>
+                <artifactId>filevault-package-maven-plugin</artifactId>
+                <version>${plugin.version}</version>
+                <extensions>true</extensions>
+                <configuration>
+                    <packageType>mixed</packageType>
+                    <filters>
+                        <filter>
+                            <root>/apps/htl/test</root>
+                        </filter>
+                    </filters>
+                    <metaInfDirectory>additional-metainf</metaInfDirectory>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.sling</groupId>
+                <artifactId>htl-maven-plugin</artifactId>
+                <version>1.1.2</version>
+                <executions>
+                    <execution>
+                        <id>validate-scripts</id>
+                        <goals>
+                            <goal>validate</goal>
+                        </goals>
+                        <phase>generate-sources</phase>
+                        <configuration>
+                            <sourceDirectory>jcr_root</sourceDirectory>
+                            <includes>
+                                <include>**/*.html</include>
+                            </includes>
+                            <failOnWarnings>true</failOnWarnings>
+                            <generateJavaClasses>true</generateJavaClasses>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+    <dependencies>
+        <!-- needed for HTL compilation validation -->
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.scripting.sightly.compiler</artifactId>
+            <version>1.0.14</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.scripting.sightly.compiler.java</artifactId>
+            <version>1.0.16</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>javax.jcr</groupId>
+            <artifactId>jcr</artifactId>
+            <version>2.0</version>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+
+</project>