You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by si...@apache.org on 2019/03/27 14:02:50 UTC

[sling-whiteboard] branch master updated: [cp2fm] create empty content-package for each detected sub-package with same metadata in order to keep satisfying the dependencies and ACL policies

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

simonetripodi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-whiteboard.git


The following commit(s) were added to refs/heads/master by this push:
     new f73ba8c  [cp2fm] create empty content-package for each detected sub-package with same metadata in order to keep satisfying the dependencies and ACL policies
f73ba8c is described below

commit f73ba8c9f7fa66b395fe8fd1d5ec89d523c98f04
Author: Simo Tripodi <st...@adobe.com>
AuthorDate: Wed Mar 27 15:02:35 2019 +0100

    [cp2fm] create empty content-package for each detected sub-package with
    same metadata in order to keep satisfying the dependencies and ACL
    policies
---
 .../ContentPackage2FeatureModelConverter.java      | 167 +++++---------------
 .../cp2fm/handlers/ContentPackageEntryHandler.java |   2 +-
 .../sling/cp2fm/handlers/DefaultEntryHandler.java  |  58 -------
 .../sling/cp2fm/vltpkg/VaultPackageAssembler.java  | 172 +++++++++++++++++++++
 .../apache/sling/cp2fm/vltpkg/package-info.java    |  21 +++
 .../sling/cp2fm/writers/FileArtifactWriter.java    |  42 +++++
 .../org/apache/sling/cp2fm/{ => vltpkg}/config.xml |   0
 .../cp2fm/{ => vltpkg}/definition/.content.xml     |   0
 .../apache/sling/cp2fm/{ => vltpkg}/settings.xml   |   0
 .../ContentPackage2FeatureModelConverterTest.java  |   9 +-
 .../cp2fm/handlers/DefaultEntryHandlerTest.java    |  85 ----------
 .../cp2fm/vltpkg/VaultPackageAssemblerTest.java    |  88 +++++++++++
 12 files changed, 372 insertions(+), 272 deletions(-)

diff --git a/content-package-2-feature-model/src/main/java/org/apache/sling/cp2fm/ContentPackage2FeatureModelConverter.java b/content-package-2-feature-model/src/main/java/org/apache/sling/cp2fm/ContentPackage2FeatureModelConverter.java
index 3a4a561..e77e44d 100644
--- a/content-package-2-feature-model/src/main/java/org/apache/sling/cp2fm/ContentPackage2FeatureModelConverter.java
+++ b/content-package-2-feature-model/src/main/java/org/apache/sling/cp2fm/ContentPackage2FeatureModelConverter.java
@@ -17,13 +17,7 @@
 package org.apache.sling.cp2fm;
 
 import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
 import java.io.FileWriter;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.text.SimpleDateFormat;
-import java.util.Date;
 import java.util.Dictionary;
 import java.util.Enumeration;
 import java.util.HashMap;
@@ -31,25 +25,20 @@ import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Map;
 import java.util.Objects;
-import java.util.Properties;
 import java.util.ServiceLoader;
 import java.util.Set;
-import java.util.stream.Collectors;
 
-import org.apache.commons.io.IOUtils;
-import org.apache.jackrabbit.vault.fs.io.AccessControlHandling;
 import org.apache.jackrabbit.vault.fs.io.Archive;
 import org.apache.jackrabbit.vault.fs.io.Archive.Entry;
 import org.apache.jackrabbit.vault.packaging.Dependency;
 import org.apache.jackrabbit.vault.packaging.PackageManager;
 import org.apache.jackrabbit.vault.packaging.PackageProperties;
-import org.apache.jackrabbit.vault.packaging.PackageType;
 import org.apache.jackrabbit.vault.packaging.VaultPackage;
 import org.apache.jackrabbit.vault.packaging.impl.PackageManagerImpl;
-import org.apache.sling.cp2fm.handlers.DefaultEntryHandler;
 import org.apache.sling.cp2fm.spi.BundlesDeployer;
 import org.apache.sling.cp2fm.spi.EntryHandler;
-import org.apache.sling.cp2fm.writers.InputStreamArtifactWriter;
+import org.apache.sling.cp2fm.vltpkg.VaultPackageAssembler;
+import org.apache.sling.cp2fm.writers.FileArtifactWriter;
 import org.apache.sling.cp2fm.writers.MavenPomSupplierWriter;
 import org.apache.sling.feature.Artifact;
 import org.apache.sling.feature.ArtifactId;
@@ -59,9 +48,6 @@ import org.apache.sling.feature.ExtensionType;
 import org.apache.sling.feature.Extensions;
 import org.apache.sling.feature.Feature;
 import org.apache.sling.feature.io.json.FeatureJSONWriter;
-import org.codehaus.plexus.archiver.Archiver;
-import org.codehaus.plexus.archiver.jar.JarArchiver;
-import org.codehaus.plexus.archiver.util.DefaultFileSet;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -71,40 +57,28 @@ public class ContentPackage2FeatureModelConverter {
 
     public static final String POM_TYPE = "pom";
 
-    private static final String ZIP_TYPE = "zip";
+    public static final String ZIP_TYPE = "zip";
 
     public static final String NAME_GROUP_ID = "groupId";
 
     public static final String NAME_ARTIFACT_ID = "artifactId";
 
-    private static final String NAME_CLASSIFIER = "classifier";
-
-    private static final String NAME_PATH = "path";
-
-    private static final String FEATURE_CLASSIFIER = "cp2fm-converted-feature";
+    public static final String FEATURE_CLASSIFIER = "cp2fm-converted-feature";
 
     private static final String SLING_OSGI_FEATURE_TILE_TYPE = "slingosgifeature";
 
-    private static final String VAULT_PROPERTIES_FILE = "META-INF/vault/properties.xml";
-
     private static final String JSON_FILE_EXTENSION = ".json";
 
-    private static final String[] INCLUDE_RESOURCES = { "definition/.content.xml", "config.xml", "settings.xml" };
-
     private final Logger logger = LoggerFactory.getLogger(getClass());
 
     private final PackageManager packageManager = new PackageManagerImpl();
 
     private final ServiceLoader<EntryHandler> entryHandlers = ServiceLoader.load(EntryHandler.class);
 
-    private final EntryHandler defaultEntryHandler = new DefaultEntryHandler();
-
     private final Map<String, Feature> runModes = new HashMap<>();
 
     private final Set<String> dependencies = new HashSet<>();
 
-    private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss.SZ");
-
     private final RegexBasedResourceFilter filter = new RegexBasedResourceFilter();
 
     private BundlesDeployer artifactDeployer;
@@ -119,6 +93,8 @@ public class ContentPackage2FeatureModelConverter {
 
     private Feature targetFeature = null;
 
+    private VaultPackageAssembler mainPackageAssembler = null;
+
     public ContentPackage2FeatureModelConverter setStrictValidation(boolean strictValidation) {
         this.strictValidation = strictValidation;
         return this;
@@ -206,13 +182,11 @@ public class ContentPackage2FeatureModelConverter {
             artifactDeployer = artifactDeployerLoader.next();
         }
 
-        String userName = System.getProperty("user.name");
-
         if (!outputDirectory.exists() && !outputDirectory.mkdirs()) {
             throw new IllegalStateException("output directory "
                                             + outputDirectory
                                             + " does not exist and can not be created, please make sure current user '"
-                                            + userName
+                                            + System.getProperty("user.name")
                                             + " has enough rights to write on the File System.");
         }
 
@@ -221,8 +195,9 @@ public class ContentPackage2FeatureModelConverter {
         try (VaultPackage vaultPackage = packageManager.open(contentPackage, strictValidation)) {
             logger.info("content-package '{}' successfully read!", contentPackage);
 
-            PackageProperties packageProperties = vaultPackage.getProperties();
+            mainPackageAssembler = VaultPackageAssembler.create(vaultPackage);
 
+            PackageProperties packageProperties = vaultPackage.getProperties();
             String groupId = packageProperties.getProperty(NAME_GROUP_ID);
             String artifactId = packageProperties.getProperty(NAME_ARTIFACT_ID);
             String version = packageProperties.getProperty(PackageProperties.NAME_VERSION);
@@ -241,91 +216,33 @@ public class ContentPackage2FeatureModelConverter {
 
             // attach all unmatched resources as new content-package
 
-            File deflatedDir = new File(outputDirectory, DefaultEntryHandler.TMP_DEFLATED);
-
-            if (deflatedDir.listFiles().length > 0) {
-                Properties properties = new Properties();
-                copyProperty(PackageProperties.NAME_GROUP, packageProperties, properties);
-                properties.setProperty(PackageProperties.NAME_NAME, packageProperties.getProperty(PackageProperties.NAME_NAME) + ' ' + FEATURE_CLASSIFIER);
-                copyProperty(PackageProperties.NAME_VERSION, packageProperties, properties);
-                copyProperty(NAME_GROUP_ID, packageProperties, properties);
-                copyProperty(NAME_ARTIFACT_ID, packageProperties, properties);
-                properties.setProperty(NAME_CLASSIFIER, FEATURE_CLASSIFIER);
-                properties.setProperty(PackageProperties.NAME_DEPENDENCIES, dependencies.stream().collect(Collectors.joining(",")));
-                properties.setProperty(PackageProperties.NAME_CREATED_BY, userName);
-                properties.setProperty(PackageProperties.NAME_CREATED, dateFormat.format(new Date()));
-                properties.setProperty(PackageProperties.NAME_REQUIRES_ROOT, String.valueOf(false));
-                properties.setProperty(NAME_PATH, String.format("/etc/packages/%s/%s-%s.zip",
-                                                                properties.getProperty(PackageProperties.NAME_GROUP),
-                                                                properties.getProperty(NAME_ARTIFACT_ID),
-                                                                FEATURE_CLASSIFIER));
-                properties.setProperty(PackageProperties.NAME_PACKAGE_TYPE, PackageType.APPLICATION.name());
-                properties.setProperty(PackageProperties.NAME_AC_HANDLING, AccessControlHandling.MERGE_PRESERVE.name());
-
-                // generate the Vault properties XML file
-
-                File xmlProperties = new File(deflatedDir, VAULT_PROPERTIES_FILE);
-                xmlProperties.getParentFile().mkdirs();
-
-                try (FileOutputStream fos = new FileOutputStream(xmlProperties)) {
-                    properties.storeToXML(fos, null);
-                }
-
-                // copy the required resources
-
-                for (String resource : INCLUDE_RESOURCES) {
-                    File targetResource = new File(xmlProperties.getParentFile(), resource);
-                    targetResource.getParentFile().mkdirs();
-
-                    try (InputStream input = getClass().getResourceAsStream(resource);
-                            OutputStream output = new FileOutputStream(targetResource)) {
-                        IOUtils.copy(input, output);
-                    }
-                }
-
-                // create the target archiver
-
-                Archiver archiver = new JarArchiver();
-                archiver.setIncludeEmptyDirs(true);
-
-                File destFile = File.createTempFile(targetFeature.getId().getArtifactId(), '.' + ZIP_TYPE);
-
-                archiver.setDestFile(destFile);
-                archiver.addFileSet(new DefaultFileSet(deflatedDir));
-                archiver.createArchive();
-
-                try (InputStream input = new FileInputStream(destFile)) {
-                    artifactDeployer.deploy(new InputStreamArtifactWriter(input),
-                                            targetFeature.getId().getGroupId(),
-                                            targetFeature.getId().getArtifactId(),
-                                            targetFeature.getId().getVersion(),
-                                            FEATURE_CLASSIFIER,
-                                            ZIP_TYPE);
-
-                    attach(null,
-                           targetFeature.getId().getGroupId(),
-                           targetFeature.getId().getArtifactId(),
-                           targetFeature.getId().getVersion(),
-                           FEATURE_CLASSIFIER,
-                           ZIP_TYPE);
-                } finally {
-                    destFile.delete();
-                }
-
-                // deploy the new zip content-package to the local mvn bundles dir
-
-                artifactDeployer.deploy(new MavenPomSupplierWriter(targetFeature.getId().getGroupId(),
-                                                                          targetFeature.getId().getArtifactId(),
-                                                                          targetFeature.getId().getVersion(),
-                                                                          ZIP_TYPE),
-                                               targetFeature.getId().getGroupId(),
-                                               targetFeature.getId().getArtifactId(),
-                                               targetFeature.getId().getVersion(),
-                                               null,
-                                               POM_TYPE);
-            } else {
-                logger.info("No resources to be repackaged.");
-            }
+            File contentPackageArchive = mainPackageAssembler.createPackage(outputDirectory);
+
+            // deploy the new zip content-package to the local mvn bundles dir
+
+            artifactDeployer.deploy(new FileArtifactWriter(contentPackageArchive),
+                                                           targetFeature.getId().getGroupId(),
+                                                           targetFeature.getId().getArtifactId(),
+                                                           targetFeature.getId().getVersion(),
+                                                           FEATURE_CLASSIFIER,
+                                                           ZIP_TYPE);
+
+            artifactDeployer.deploy(new MavenPomSupplierWriter(targetFeature.getId().getGroupId(),
+                                                               targetFeature.getId().getArtifactId(),
+                                                               targetFeature.getId().getVersion(),
+                                                               ZIP_TYPE),
+                                    targetFeature.getId().getGroupId(),
+                                    targetFeature.getId().getArtifactId(),
+                                    targetFeature.getId().getVersion(),
+                                    null,
+                                    POM_TYPE);
+
+            attach(null,
+                   targetFeature.getId().getGroupId(),
+                   targetFeature.getId().getArtifactId(),
+                   targetFeature.getId().getVersion(),
+                   FEATURE_CLASSIFIER,
+                   ZIP_TYPE);
 
             // finally serialize the Feature Model(s) file(s)
 
@@ -397,11 +314,15 @@ public class ContentPackage2FeatureModelConverter {
         }
     }
 
-    public void processSubPackage(File contentPackage) throws Exception {
+    public void processSubPackage(String path, File contentPackage) throws Exception {
+        Objects.requireNonNull(path, "Impossible to process a null vault package");
         Objects.requireNonNull(contentPackage, "Impossible to process a null vault package");
 
         try (VaultPackage vaultPackage = packageManager.open(contentPackage, strictValidation)) {
             process(vaultPackage);
+
+            File clonedPackage = VaultPackageAssembler.create(vaultPackage).createPackage();
+            mainPackageAssembler.addEntry(path, clonedPackage);
         }
     }
 
@@ -473,7 +394,7 @@ public class ContentPackage2FeatureModelConverter {
             }
         }
 
-        return defaultEntryHandler;
+        return mainPackageAssembler;
     }
 
     public void attach(String runMode,
@@ -507,8 +428,4 @@ public class ContentPackage2FeatureModelConverter {
         }
     }
 
-    private static void copyProperty(String key, PackageProperties source, Properties target) {
-        target.setProperty(key, source.getProperty(key));
-    }
-
 }
diff --git a/content-package-2-feature-model/src/main/java/org/apache/sling/cp2fm/handlers/ContentPackageEntryHandler.java b/content-package-2-feature-model/src/main/java/org/apache/sling/cp2fm/handlers/ContentPackageEntryHandler.java
index 94fb2ce..e2eb0a2 100644
--- a/content-package-2-feature-model/src/main/java/org/apache/sling/cp2fm/handlers/ContentPackageEntryHandler.java
+++ b/content-package-2-feature-model/src/main/java/org/apache/sling/cp2fm/handlers/ContentPackageEntryHandler.java
@@ -43,7 +43,7 @@ public final class ContentPackageEntryHandler extends AbstractRegexEntryHandler
             IOUtils.copy(input, output);
         }
 
-        converter.processSubPackage(temporaryContentPackage);
+        converter.processSubPackage(path, temporaryContentPackage);
     }
 
 }
diff --git a/content-package-2-feature-model/src/main/java/org/apache/sling/cp2fm/handlers/DefaultEntryHandler.java b/content-package-2-feature-model/src/main/java/org/apache/sling/cp2fm/handlers/DefaultEntryHandler.java
deleted file mode 100644
index fbc76f4..0000000
--- a/content-package-2-feature-model/src/main/java/org/apache/sling/cp2fm/handlers/DefaultEntryHandler.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * 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.cp2fm.handlers;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-import org.apache.commons.io.IOUtils;
-import org.apache.jackrabbit.vault.fs.io.Archive;
-import org.apache.jackrabbit.vault.fs.io.Archive.Entry;
-import org.apache.sling.cp2fm.ContentPackage2FeatureModelConverter;
-import org.apache.sling.cp2fm.spi.EntryHandler;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public final class DefaultEntryHandler implements EntryHandler {
-
-    public static final String TMP_DEFLATED = "tmp-deflated";
-
-    private final Logger logger = LoggerFactory.getLogger(getClass());
-
-    @Override
-    public boolean matches(String path) {
-        return true;
-    }
-
-    @Override
-    public void handle(String path, Archive archive, Entry entry, ContentPackage2FeatureModelConverter converter) throws Exception {
-        File deflatedDir = new File(converter.getOutputDirectory(), TMP_DEFLATED);
-        File target = new File(deflatedDir, path);
-
-        target.getParentFile().mkdirs();
-
-        try (InputStream input = archive.openInputStream(entry);
-                OutputStream output = new FileOutputStream(target)) {
-            logger.info("Copying {} archived resource to {}...", path, target);
-
-            IOUtils.copy(input, output);
-        }
-    }
-
-}
diff --git a/content-package-2-feature-model/src/main/java/org/apache/sling/cp2fm/vltpkg/VaultPackageAssembler.java b/content-package-2-feature-model/src/main/java/org/apache/sling/cp2fm/vltpkg/VaultPackageAssembler.java
new file mode 100644
index 0000000..170bd5d
--- /dev/null
+++ b/content-package-2-feature-model/src/main/java/org/apache/sling/cp2fm/vltpkg/VaultPackageAssembler.java
@@ -0,0 +1,172 @@
+/*
+ * 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.cp2fm.vltpkg;
+
+import static org.apache.sling.cp2fm.ContentPackage2FeatureModelConverter.FEATURE_CLASSIFIER;
+import static org.apache.sling.cp2fm.ContentPackage2FeatureModelConverter.NAME_ARTIFACT_ID;
+import static org.apache.sling.cp2fm.ContentPackage2FeatureModelConverter.NAME_GROUP_ID;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Properties;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.jackrabbit.vault.fs.io.Archive;
+import org.apache.jackrabbit.vault.fs.io.Archive.Entry;
+import org.apache.jackrabbit.vault.packaging.PackageProperties;
+import org.apache.jackrabbit.vault.packaging.VaultPackage;
+import org.apache.sling.cp2fm.ContentPackage2FeatureModelConverter;
+import org.apache.sling.cp2fm.spi.EntryHandler;
+import org.codehaus.plexus.archiver.Archiver;
+import org.codehaus.plexus.archiver.jar.JarArchiver;
+import org.codehaus.plexus.archiver.util.DefaultFileSet;
+
+public final class VaultPackageAssembler implements EntryHandler {
+
+    private static final String META_INF_VAULT_DIRECTORY = "META-INF/vault/";
+
+    private static final String VAULT_PROPERTIES_FILE = META_INF_VAULT_DIRECTORY + "properties.xml";
+
+    private static final String NAME_PATH = "path";
+
+    private static final String[] INCLUDE_RESOURCES = { "definition/.content.xml", "config.xml", "settings.xml" };
+
+    private static final File TMP_DIR = new File(System.getProperty("java.io.tmpdir"));
+
+    public static VaultPackageAssembler create(VaultPackage vaultPackage) {
+        File storingDirectory = new File(TMP_DIR, vaultPackage.getFile().getName() + "-deflated");
+        storingDirectory.mkdirs();
+
+        PackageProperties packageProperties = vaultPackage.getProperties();
+
+        Properties properties = new Properties();
+        properties.setProperty(PackageProperties.NAME_VERSION,
+                               packageProperties.getProperty(PackageProperties.NAME_VERSION)
+                                                             + '-'
+                                                             + FEATURE_CLASSIFIER);
+
+        for (String key : new String[] {
+                PackageProperties.NAME_GROUP,
+                PackageProperties.NAME_NAME,
+                PackageProperties.NAME_DEPENDENCIES,
+                PackageProperties.NAME_CREATED_BY,
+                PackageProperties.NAME_CREATED,
+                PackageProperties.NAME_REQUIRES_ROOT,
+                PackageProperties.NAME_PACKAGE_TYPE,
+                PackageProperties.NAME_AC_HANDLING,
+                NAME_GROUP_ID,
+                NAME_ARTIFACT_ID,
+                NAME_PATH
+        }) {
+            String value = packageProperties.getProperty(key);
+            if (value != null && !value.isEmpty()) {
+                properties.setProperty(key, value);
+            }
+        }
+
+        return new VaultPackageAssembler(storingDirectory, properties);
+    }
+
+    private final File storingDirectory;
+
+    private final Properties properties;
+
+    @Override
+    public boolean matches(String path) {
+        return true;
+    }
+
+    @Override
+    public void handle(String path, Archive archive, Entry entry, ContentPackage2FeatureModelConverter converter)
+            throws Exception {
+        addEntry(path, archive, entry);
+    }
+
+    /**
+     * This class can not be instantiated from outside
+     *
+     * @param properties
+     */
+    private VaultPackageAssembler(File storingDirectory, Properties properties) {
+        this.storingDirectory = storingDirectory;
+        this.properties = properties;
+    }
+
+    public void addEntry(String path, Archive archive, Entry entry) throws IOException {
+        try (InputStream input = archive.openInputStream(entry)) {
+            addEntry(path, input);
+        }
+    }
+
+    public void addEntry(String path, File file) throws IOException {
+        try (InputStream input = new FileInputStream(file)) {
+            addEntry(path, input);
+        }
+    }
+
+    public void addEntry(String path, InputStream input) throws IOException {
+        File target = new File(storingDirectory, path);
+
+        target.getParentFile().mkdirs();
+
+        try (OutputStream output = new FileOutputStream(target)) {
+            IOUtils.copy(input, output);
+        }
+    }
+
+    public File createPackage() throws IOException {
+        return createPackage(TMP_DIR);
+    }
+
+    public File createPackage(File outputDirectory) throws IOException {
+        // generate the Vault properties XML file
+
+        File xmlProperties = new File(storingDirectory, VAULT_PROPERTIES_FILE);
+        xmlProperties.getParentFile().mkdirs();
+
+        try (FileOutputStream fos = new FileOutputStream(xmlProperties)) {
+            properties.storeToXML(fos, null);
+        }
+
+        // copy the required resources
+
+        for (String resource : INCLUDE_RESOURCES) {
+            try (InputStream input = getClass().getResourceAsStream(resource)) {
+                addEntry(META_INF_VAULT_DIRECTORY + resource, input);
+            }
+        }
+
+        // create the target archiver
+
+        Archiver archiver = new JarArchiver();
+        archiver.setIncludeEmptyDirs(true);
+
+        String destFileName = storingDirectory.getName().substring(0, storingDirectory.getName().lastIndexOf('-'));
+        File destFile = new File(TMP_DIR, destFileName);
+
+        archiver.setDestFile(destFile);
+        archiver.addFileSet(new DefaultFileSet(storingDirectory));
+        archiver.createArchive();
+
+        return destFile;
+    }
+
+}
diff --git a/content-package-2-feature-model/src/main/java/org/apache/sling/cp2fm/vltpkg/package-info.java b/content-package-2-feature-model/src/main/java/org/apache/sling/cp2fm/vltpkg/package-info.java
new file mode 100644
index 0000000..0df723f
--- /dev/null
+++ b/content-package-2-feature-model/src/main/java/org/apache/sling/cp2fm/vltpkg/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+/**
+ * Easy to use Apache Jackrabbit Vault packager.
+ */
+package org.apache.sling.cp2fm.vltpkg;
diff --git a/content-package-2-feature-model/src/main/java/org/apache/sling/cp2fm/writers/FileArtifactWriter.java b/content-package-2-feature-model/src/main/java/org/apache/sling/cp2fm/writers/FileArtifactWriter.java
new file mode 100644
index 0000000..f9d461d
--- /dev/null
+++ b/content-package-2-feature-model/src/main/java/org/apache/sling/cp2fm/writers/FileArtifactWriter.java
@@ -0,0 +1,42 @@
+/*
+ * 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.cp2fm.writers;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.apache.sling.cp2fm.spi.ArtifactWriter;
+
+public final class FileArtifactWriter implements ArtifactWriter {
+
+    private final File fileArtifact;
+
+    public FileArtifactWriter(File fileArtifact) {
+        this.fileArtifact = fileArtifact;
+    }
+
+    @Override
+    public void write(OutputStream output) throws IOException {
+        try (InputStream input = new FileInputStream(fileArtifact)) {
+            new InputStreamArtifactWriter(input).write(output);
+        }
+    }
+
+}
diff --git a/content-package-2-feature-model/src/main/resources/org/apache/sling/cp2fm/config.xml b/content-package-2-feature-model/src/main/resources/org/apache/sling/cp2fm/vltpkg/config.xml
similarity index 100%
rename from content-package-2-feature-model/src/main/resources/org/apache/sling/cp2fm/config.xml
rename to content-package-2-feature-model/src/main/resources/org/apache/sling/cp2fm/vltpkg/config.xml
diff --git a/content-package-2-feature-model/src/main/resources/org/apache/sling/cp2fm/definition/.content.xml b/content-package-2-feature-model/src/main/resources/org/apache/sling/cp2fm/vltpkg/definition/.content.xml
similarity index 100%
rename from content-package-2-feature-model/src/main/resources/org/apache/sling/cp2fm/definition/.content.xml
rename to content-package-2-feature-model/src/main/resources/org/apache/sling/cp2fm/vltpkg/definition/.content.xml
diff --git a/content-package-2-feature-model/src/main/resources/org/apache/sling/cp2fm/settings.xml b/content-package-2-feature-model/src/main/resources/org/apache/sling/cp2fm/vltpkg/settings.xml
similarity index 100%
rename from content-package-2-feature-model/src/main/resources/org/apache/sling/cp2fm/settings.xml
rename to content-package-2-feature-model/src/main/resources/org/apache/sling/cp2fm/vltpkg/settings.xml
diff --git a/content-package-2-feature-model/src/test/java/org/apache/sling/cp2fm/ContentPackage2FeatureModelConverterTest.java b/content-package-2-feature-model/src/test/java/org/apache/sling/cp2fm/ContentPackage2FeatureModelConverterTest.java
index 112a2f6..153fae8 100644
--- a/content-package-2-feature-model/src/test/java/org/apache/sling/cp2fm/ContentPackage2FeatureModelConverterTest.java
+++ b/content-package-2-feature-model/src/test/java/org/apache/sling/cp2fm/ContentPackage2FeatureModelConverterTest.java
@@ -81,12 +81,12 @@ public class ContentPackage2FeatureModelConverterTest {
 
     @Test(expected = NullPointerException.class)
     public void processRequiresNotNullPackage() throws Exception {
-        converter.processSubPackage(null);
+        converter.processSubPackage("", null);
     }
 
     @Test(expected = IllegalStateException.class)
     public void processRequiresConvertInvoked() throws Exception {
-        converter.processSubPackage(mock(File.class));
+        converter.processSubPackage("", mock(File.class));
     }
 
     @Test(expected = NullPointerException.class)
@@ -150,7 +150,10 @@ public class ContentPackage2FeatureModelConverterTest {
                 "META-INF/vault/properties.xml",
                 "META-INF/vault/config.xml",
                 "META-INF/vault/settings.xml",
-                "META-INF/vault/definition/.content.xml"
+                "META-INF/vault/definition/.content.xml",
+                "jcr_root/etc/packages/asd/test-bundles.zip",
+                "jcr_root/etc/packages/asd/test-configurations.zip",
+                "jcr_root/etc/packages/asd/test-content.zip",
                 }) {
             assertNotNull(zipFile.getEntry(expectedEntry));
         }
diff --git a/content-package-2-feature-model/src/test/java/org/apache/sling/cp2fm/handlers/DefaultEntryHandlerTest.java b/content-package-2-feature-model/src/test/java/org/apache/sling/cp2fm/handlers/DefaultEntryHandlerTest.java
deleted file mode 100644
index 8afcca2..0000000
--- a/content-package-2-feature-model/src/test/java/org/apache/sling/cp2fm/handlers/DefaultEntryHandlerTest.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * 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.cp2fm.handlers;
-
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.when;
-
-import java.io.File;
-import java.util.Arrays;
-import java.util.Collection;
-
-import org.apache.jackrabbit.vault.fs.io.Archive;
-import org.apache.jackrabbit.vault.fs.io.Archive.Entry;
-import org.apache.sling.cp2fm.ContentPackage2FeatureModelConverter;
-import org.apache.sling.cp2fm.handlers.DefaultEntryHandler;
-import org.apache.sling.cp2fm.spi.EntryHandler;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
-
-@RunWith(Parameterized.class)
-public class DefaultEntryHandlerTest {
-
-    private final String resourceLocation;
-
-    private final EntryHandler defaultEntryHandler;
-
-    public DefaultEntryHandlerTest(String recourceLocation, EntryHandler defaultEntryHandler) {
-        this.resourceLocation = recourceLocation;
-        this.defaultEntryHandler = defaultEntryHandler;
-    }
-
-    @Test
-    public void matchAll() {
-        assertTrue(defaultEntryHandler.matches(resourceLocation));
-    }
-
-    @Test
-    public void copyEverything() throws Exception {
-        Archive archive = mock(Archive.class);
-        Entry entry = mock(Entry.class);
-        when(archive.openInputStream(entry)).thenReturn(getClass().getResourceAsStream(resourceLocation));
-
-        ContentPackage2FeatureModelConverter converter = spy(ContentPackage2FeatureModelConverter.class);
-
-        File testDirectory = new File(System.getProperty("testDirectory"), getClass().getName());
-        when(converter.getOutputDirectory()).thenReturn(testDirectory);
-
-        defaultEntryHandler.handle(resourceLocation, archive, entry, converter);
-
-        File targetDirectory = new File(testDirectory, "tmp-deflated");
-        File targetFile = new File(targetDirectory, resourceLocation);
-        assertTrue(targetFile.exists());
-    }
-
-    @Parameters
-    public static Collection<Object[]> data() {
-        EntryHandler defaultEntryHandler = new DefaultEntryHandler();
-
-        return Arrays.asList(new Object[][] {
-            { "jcr_root/.content.xml", defaultEntryHandler },
-            { "jcr_root/asd/.content.xml", defaultEntryHandler },
-            { "jcr_root/asd/public/_rep_policy.xml", defaultEntryHandler },
-            { "jcr_root/asd/public/license.txt", defaultEntryHandler }
-        });
-    }
-
-}
diff --git a/content-package-2-feature-model/src/test/java/org/apache/sling/cp2fm/vltpkg/VaultPackageAssemblerTest.java b/content-package-2-feature-model/src/test/java/org/apache/sling/cp2fm/vltpkg/VaultPackageAssemblerTest.java
new file mode 100644
index 0000000..c3bc689
--- /dev/null
+++ b/content-package-2-feature-model/src/test/java/org/apache/sling/cp2fm/vltpkg/VaultPackageAssemblerTest.java
@@ -0,0 +1,88 @@
+/*
+ * 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.cp2fm.vltpkg;
+
+import static org.junit.Assert.assertNotNull;
+
+import java.io.File;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.jackrabbit.vault.packaging.VaultPackage;
+import org.apache.jackrabbit.vault.packaging.impl.PackageManagerImpl;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class VaultPackageAssemblerTest {
+
+    private File testDirectory;
+
+    private final VaultPackageAssembler assembler;
+
+    private final String resourceLocation;
+
+    public VaultPackageAssemblerTest(String resourceLocation, VaultPackageAssembler assembler) {
+        this.resourceLocation = resourceLocation;
+        this.assembler = assembler;
+    }
+
+    @Before
+    public void setUp() {
+        testDirectory = new File(System.getProperty("testDirectory"), getClass().getName() + '_' + System.currentTimeMillis());
+    }
+
+    @Test
+    public void matchAll() {
+        assembler.matches(resourceLocation);
+    }
+
+    @Test
+    public void packageResource() throws Exception {
+        assembler.addEntry(resourceLocation, getClass().getResourceAsStream("../handlers/" + resourceLocation));
+        File contentPackage = assembler.createPackage(testDirectory);
+
+        ZipFile zipFile = new ZipFile(contentPackage);
+        ZipEntry entry = zipFile.getEntry(resourceLocation);
+        assertNotNull(entry);
+        zipFile.close();
+    }
+
+    @Parameters
+    public static Collection<Object[]> data() throws Exception {
+        URL resource = VaultPackageAssemblerTest.class.getResource("../test-content-package.zip");
+        File file = FileUtils.toFile(resource);
+        VaultPackage vaultPackage = new PackageManagerImpl().open(file);
+
+        VaultPackageAssembler assembler = VaultPackageAssembler.create(vaultPackage);
+
+        return Arrays.asList(new Object[][] {
+            { "jcr_root/.content.xml", assembler },
+            { "jcr_root/asd/.content.xml", assembler },
+            { "jcr_root/asd/public/_rep_policy.xml", assembler },
+            { "jcr_root/asd/public/license.txt", assembler }
+        });
+    }
+
+}