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/04/30 21:35:56 UTC

[sling-org-apache-sling-feature-cpconverter] branch master updated (80e408f -> 411aeb2)

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

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


    from 80e408f  initial prototype of json file indexer
     new 0994c04  minor trivial tweaks
     new 411aeb2  include RunmodeMapper generated file verifications

The 29 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../ContentPackage2FeatureModelConverter.java      | 29 +++++++++++-----------
 .../sling/feature/cpconverter/RunmodeMapper.java   | 11 ++++----
 .../feature/cpconverter/cli/ShutDownHook.java      |  2 +-
 .../ContentPackage2FeatureModelConverterTest.java  | 17 +++++++++++++
 4 files changed, 38 insertions(+), 21 deletions(-)


[sling-org-apache-sling-feature-cpconverter] 09/29: typo

Posted by si...@apache.org.
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-org-apache-sling-feature-cpconverter.git

commit e1105b2be8224d21c922cfc2f0b153b7d2b2cde8
Author: stripodi <st...@192.168.1.111>
AuthorDate: Fri Apr 12 00:06:21 2019 +0200

    typo
---
 .../cpconverter/cli/ContentPackage2FeatureModelConverterLauncher.java | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/main/java/org/apache/sling/feature/cpconverter/cli/ContentPackage2FeatureModelConverterLauncher.java b/src/main/java/org/apache/sling/feature/cpconverter/cli/ContentPackage2FeatureModelConverterLauncher.java
index e780d10..4fe2a57 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/cli/ContentPackage2FeatureModelConverterLauncher.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/cli/ContentPackage2FeatureModelConverterLauncher.java
@@ -68,7 +68,7 @@ public final class ContentPackage2FeatureModelConverterLauncher implements Runna
     private File featureModelsOutputDirectory;
 
     @Option(names = { "-i", "--artifact-id" }, description = "The optional Artifact Id the Feature File will have, once generated; it will be derived, if not specified.", required = false)
-    private String groupId;
+    private String artifactId;
 
     @Override
     public void run() {
@@ -104,7 +104,7 @@ public final class ContentPackage2FeatureModelConverterLauncher implements Runna
                                                              .setBundlesStartOrder(bundlesStartOrder)
                                                              .setArtifactsOutputDirectory(artifactsOutputDirectory)
                                                              .setFeatureModelsOutputDirectory(featureModelsOutputDirectory)
-                                                             .setId(groupId);
+                                                             .setId(artifactId);
 
             if (filteringPatterns != null && filteringPatterns.length > 0) {
                 for (String filteringPattern : filteringPatterns) {


[sling-org-apache-sling-feature-cpconverter] 08/29: specify the whole artifact id, rather than overriding pieces

Posted by si...@apache.org.
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-org-apache-sling-feature-cpconverter.git

commit 6053648d47d6b9c23329601a8ea5dae110436315
Author: stripodi <st...@192.168.1.111>
AuthorDate: Fri Apr 12 00:04:12 2019 +0200

    specify the whole artifact id, rather than overriding pieces
---
 .../ContentPackage2FeatureModelConverter.java      | 58 ++++++++++++----------
 ...ntentPackage2FeatureModelConverterLauncher.java |  4 +-
 2 files changed, 33 insertions(+), 29 deletions(-)

diff --git a/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java b/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
index 966728c..8c051e8 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
@@ -92,7 +92,7 @@ public class ContentPackage2FeatureModelConverter {
 
     private VaultPackageAssembler mainPackageAssembler = null;
 
-    private String groupId;
+    private String id;
 
     public ContentPackage2FeatureModelConverter setStrictValidation(boolean strictValidation) {
         this.strictValidation = strictValidation;
@@ -144,8 +144,8 @@ public class ContentPackage2FeatureModelConverter {
         filter.addFilteringPattern(filteringPattern);
     }
 
-    public ContentPackage2FeatureModelConverter setGroupId(String groupId) {
-        this.groupId = groupId;
+    public ContentPackage2FeatureModelConverter setId(String id) {
+        this.id = id;
         return this;
     }
 
@@ -210,34 +210,38 @@ public class ContentPackage2FeatureModelConverter {
             logger.info("content-package '{}' successfully read!", contentPackage);
 
             mainPackageAssembler = VaultPackageAssembler.create(vaultPackage);
-
             PackageProperties packageProperties = vaultPackage.getProperties();
-            String group;
-            if (groupId != null && !groupId.isEmpty()) {
-                group = groupId;
+
+            ArtifactId artifactId;
+            if (id != null && !id.isEmpty()) {
+                artifactId = ArtifactId.fromMvnId(id);
             } else {
-                group = requireNonNull(packageProperties.getProperty(PackageProperties.NAME_GROUP),
-                                       "'packageGroupId' parameter not specified and "
-                                       + PackageProperties.NAME_GROUP
-                                       + " property not found in content-package "
-                                       + contentPackage
-                                       + ", please check META-INF/vault/properties.xml");
-            }
-            String name = requireNonNull(packageProperties.getProperty(PackageProperties.NAME_NAME),
-                                         PackageProperties.NAME_NAME
-                                         + " property not found in content-package "
-                                         + contentPackage
-                                         + ", please check META-INF/vault/properties.xml");
-            String version = packageProperties.getProperty(PackageProperties.NAME_VERSION);
-            if (version == null || version.isEmpty()) {
-                version = DEFEAULT_VERSION;
+                String group = requireNonNull(packageProperties.getProperty(PackageProperties.NAME_GROUP),
+                                              PackageProperties.NAME_GROUP
+                                              + " property not found in content-package "
+                                              + contentPackage
+                                              + ", please check META-INF/vault/properties.xml")
+                               .replace('/', '.');
+
+                String name = requireNonNull(packageProperties.getProperty(PackageProperties.NAME_NAME),
+                                             PackageProperties.NAME_NAME
+                                             + " property not found in content-package "
+                                             + contentPackage
+                                             + ", please check META-INF/vault/properties.xml");
+
+                String version = packageProperties.getProperty(PackageProperties.NAME_VERSION);
+                if (version == null || version.isEmpty()) {
+                    version = DEFEAULT_VERSION;
+                }
+
+                artifactId = new ArtifactId(group,
+                                            name,
+                                            version,
+                                            FEATURE_CLASSIFIER,
+                                            SLING_OSGI_FEATURE_TILE_TYPE);
             }
 
-            targetFeature = new Feature(new ArtifactId(group.replace('/', '.'),
-                                                       name,
-                                                       version,
-                                                       FEATURE_CLASSIFIER,
-                                                       SLING_OSGI_FEATURE_TILE_TYPE));
+            targetFeature = new Feature(artifactId);
 
             targetFeature.setDescription(packageProperties.getDescription());
 
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/cli/ContentPackage2FeatureModelConverterLauncher.java b/src/main/java/org/apache/sling/feature/cpconverter/cli/ContentPackage2FeatureModelConverterLauncher.java
index 2943f82..e780d10 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/cli/ContentPackage2FeatureModelConverterLauncher.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/cli/ContentPackage2FeatureModelConverterLauncher.java
@@ -67,7 +67,7 @@ public final class ContentPackage2FeatureModelConverterLauncher implements Runna
     @Option(names = { "-o", "--features-output-directory" }, description = "The output directory where the Feature File will be generated.", required = true)
     private File featureModelsOutputDirectory;
 
-    @Option(names = { "-g", "--groupId" }, description = "The output directory where the Feature File will be generated.", required = false)
+    @Option(names = { "-i", "--artifact-id" }, description = "The optional Artifact Id the Feature File will have, once generated; it will be derived, if not specified.", required = false)
     private String groupId;
 
     @Override
@@ -104,7 +104,7 @@ public final class ContentPackage2FeatureModelConverterLauncher implements Runna
                                                              .setBundlesStartOrder(bundlesStartOrder)
                                                              .setArtifactsOutputDirectory(artifactsOutputDirectory)
                                                              .setFeatureModelsOutputDirectory(featureModelsOutputDirectory)
-                                                             .setGroupId(groupId);
+                                                             .setId(groupId);
 
             if (filteringPatterns != null && filteringPatterns.length > 0) {
                 for (String filteringPattern : filteringPatterns) {


[sling-org-apache-sling-feature-cpconverter] 11/29: override only supposed to affect FeatureFile, not filenames & packageIds

Posted by si...@apache.org.
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-org-apache-sling-feature-cpconverter.git

commit c76dea6c98566e4945e2dcf75caa4e65da4abd1f
Author: Dominik Suess <su...@adobe.com>
AuthorDate: Fri Apr 12 09:01:50 2019 +0200

    override only supposed to affect FeatureFile, not filenames & packageIds
---
 .../ContentPackage2FeatureModelConverter.java      | 51 ++++++++++++++-------
 ...ntentPackage2FeatureModelConverterLauncher.java |  4 +-
 .../ContentPackage2FeatureModelConverterTest.java  | 52 ++++++++++++++++++++++
 3 files changed, 90 insertions(+), 17 deletions(-)

diff --git a/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java b/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
index 149c673..d686eb6 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
@@ -93,6 +93,8 @@ public class ContentPackage2FeatureModelConverter {
     private VaultPackageAssembler mainPackageAssembler = null;
 
     private String id;
+    
+    private String idOverride;
 
     public ContentPackage2FeatureModelConverter setStrictValidation(boolean strictValidation) {
         this.strictValidation = strictValidation;
@@ -148,6 +150,12 @@ public class ContentPackage2FeatureModelConverter {
         this.id = id;
         return this;
     }
+    
+    public ContentPackage2FeatureModelConverter setIdOverride(String id) {
+        this.idOverride = id;
+        return this;
+    }
+
 
     public Feature getRunMode(String runMode) {
         if (getTargetFeature() == null) {
@@ -158,19 +166,27 @@ public class ContentPackage2FeatureModelConverter {
             return getTargetFeature();
         }
 
-        ArtifactId id = getTargetFeature().getId();
-        final String classifier;
-        if (id.getClassifier() != null && !id.getClassifier().isEmpty()) {
-            classifier = id.getClassifier() + '-' + runMode;
+        ArtifactId newId = appendRunmode(getTargetFeature().getId(), runMode);
+
+        return runModes.computeIfAbsent(runMode, k -> new Feature(newId));
+    }
+
+    private ArtifactId appendRunmode(ArtifactId id, String runMode) {
+
+        ArtifactId newId;
+        if (runMode == null) {
+            newId = id;
         } else {
-            classifier = runMode;
-        }
+            final String classifier;
+            if (id.getClassifier() != null && !id.getClassifier().isEmpty()) {
+                classifier = id.getClassifier() + '-' + runMode;
+            } else {
+                classifier = runMode;
+            }
 
-        return runModes.computeIfAbsent(runMode, k -> new Feature(new ArtifactId(id.getGroupId(),
-                                                                                 id.getArtifactId(),
-                                                                                 id.getVersion(),
-                                                                                 classifier,
-                                                                                 id.getType())));
+            newId = new ArtifactId(id.getGroupId(), id.getArtifactId(), id.getVersion(), classifier, id.getType());
+        }
+        return newId;
     }
 
     public BundlesDeployer getArtifactDeployer() {
@@ -287,11 +303,11 @@ public class ContentPackage2FeatureModelConverter {
 
             // finally serialize the Feature Model(s) file(s)
 
-            seralize(targetFeature);
+            seralize(targetFeature, null);
 
             if (!runModes.isEmpty()) {
-                for (Feature runMode : runModes.values()) {
-                    seralize(runMode);
+                for (java.util.Map.Entry<String, Feature> runmodeEntry : runModes.entrySet()) {
+                    seralize(runmodeEntry.getValue(), runmodeEntry.getKey());
                 }
             }
         }
@@ -334,7 +350,7 @@ public class ContentPackage2FeatureModelConverter {
         }
     }
 
-    private void seralize(Feature feature) throws Exception {
+    private void seralize(Feature feature, String runMode) throws Exception {
         StringBuilder fileName = new StringBuilder().append(feature.getId().getArtifactId());
 
         String classifier = feature.getId().getClassifier();
@@ -348,6 +364,11 @@ public class ContentPackage2FeatureModelConverter {
 
         logger.info("Conversion complete!", targetFile);
         logger.info("Writing resulting Feature File to '{}'...", targetFile);
+        
+        if ( idOverride != null ) {
+            ArtifactId idOverrride = appendRunmode(ArtifactId.parse(idOverride), runMode);
+            feature = feature.copy(idOverrride);
+        }
 
         try (FileWriter targetWriter = new FileWriter(targetFile)) {
             FeatureJSONWriter.write(targetWriter, feature);
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/cli/ContentPackage2FeatureModelConverterLauncher.java b/src/main/java/org/apache/sling/feature/cpconverter/cli/ContentPackage2FeatureModelConverterLauncher.java
index 4fe2a57..46edf6f 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/cli/ContentPackage2FeatureModelConverterLauncher.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/cli/ContentPackage2FeatureModelConverterLauncher.java
@@ -104,11 +104,11 @@ public final class ContentPackage2FeatureModelConverterLauncher implements Runna
                                                              .setBundlesStartOrder(bundlesStartOrder)
                                                              .setArtifactsOutputDirectory(artifactsOutputDirectory)
                                                              .setFeatureModelsOutputDirectory(featureModelsOutputDirectory)
-                                                             .setId(artifactId);
+                                                             .setIdOverride(artifactId);
 
             if (filteringPatterns != null && filteringPatterns.length > 0) {
                 for (String filteringPattern : filteringPatterns) {
-                    converter.addFilteringPattern(filteringPattern);
+                    converter.addFilteringPattern(filteringPattern);    
                 }
             }
 
diff --git a/src/test/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverterTest.java b/src/test/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverterTest.java
index 7fb3b15..0e06589 100644
--- a/src/test/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverterTest.java
+++ b/src/test/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverterTest.java
@@ -237,4 +237,56 @@ public class ContentPackage2FeatureModelConverterTest {
                  .convert(packageFile);
     }
 
+    
+    @Test
+    public void overrideFeatureId() throws Exception {
+        URL packageUrl = getClass().getResource("test-content-package.zip");
+        File packageFile = FileUtils.toFile(packageUrl);
+
+        File outputDirectory = new File(System.getProperty("testDirectory"), getClass().getName() + '_' + System.currentTimeMillis());
+
+        String overrideId = "${project.groupId}:${project.artifactId}:slingosgifeature:asd.test.all-1.0.0:${project.version}";
+        converter.setBundlesStartOrder(5)
+                 .setArtifactsOutputDirectory(outputDirectory)
+                 .setFeatureModelsOutputDirectory(outputDirectory)
+                 .setIdOverride(overrideId)
+                 .convert(packageFile);
+
+        verifyFeatureFile(outputDirectory,
+                          "asd.retail.all.json",
+                          "${project.groupId}:${project.artifactId}:slingosgifeature:asd.test.all-1.0.0:${project.version}",
+                          Arrays.asList("org.apache.felix:org.apache.felix.framework:6.0.1"),
+                          Arrays.asList("org.apache.sling.commons.log.LogManager.factory.config-asd-retail"),
+                          Arrays.asList("asd.sample:asd.retail.all:zip:cp2fm-converted:0.0.1"));
+        verifyFeatureFile(outputDirectory,
+                          "asd.retail.all-author.json",
+                          "${project.groupId}:${project.artifactId}:slingosgifeature:asd.test.all-1.0.0-author:${project.version}",
+                          Arrays.asList("org.apache.sling:org.apache.sling.api:2.20.0"),
+                          Collections.emptyList(),
+                          Collections.emptyList());
+        verifyFeatureFile(outputDirectory,
+                          "asd.retail.all-publish.json",
+                          "${project.groupId}:${project.artifactId}:slingosgifeature:asd.test.all-1.0.0-publish:${project.version}",
+                          Arrays.asList("org.apache.sling:org.apache.sling.models.api:1.3.8"),
+                          Arrays.asList("org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended-asd-retail"),
+                          Collections.emptyList());
+
+        ZipFile zipFile = new ZipFile(new File(outputDirectory, "asd/sample/asd.retail.all/0.0.1/asd.retail.all-0.0.1-cp2fm-converted.zip"));
+        for (String expectedEntry : new String[] {
+                "jcr_root/content/asd/.content.xml",
+                "jcr_root/content/asd/resources.xml",
+                "jcr_root/apps/.content.xml",
+                "META-INF/vault/properties.xml",
+                "META-INF/vault/config.xml",
+                "META-INF/vault/settings.xml",
+                "META-INF/vault/filter.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));
+        }
+        zipFile.close();
+    }
 }


[sling-org-apache-sling-feature-cpconverter] 25/29: added missing jenkinsfile

Posted by si...@apache.org.
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-org-apache-sling-feature-cpconverter.git

commit bed04b3c4995c109903c6b97134c6184ed6f1789
Author: stripodi <st...@simos-mbp>
AuthorDate: Sat Apr 27 00:31:46 2019 +0200

    added missing jenkinsfile
---
 Jenkinsfile | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/Jenkinsfile b/Jenkinsfile
new file mode 100644
index 0000000..b20ef4f
--- /dev/null
+++ b/Jenkinsfile
@@ -0,0 +1,20 @@
+/**
+ * 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.
+ */
+
+slingOsgiBundleBuild()
\ No newline at end of file


[sling-org-apache-sling-feature-cpconverter] 06/29: create the featureModelsOutputDirectory if does not exists already

Posted by si...@apache.org.
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-org-apache-sling-feature-cpconverter.git

commit 606ed63e9a9e0d10faf873f7730170dc3a8c3297
Author: stripodi <st...@192.168.1.111>
AuthorDate: Thu Apr 11 18:01:59 2019 +0200

    create the featureModelsOutputDirectory if does not exists already
---
 .../sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java b/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
index e0e1c32..e67b04f 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
@@ -179,6 +179,8 @@ public class ContentPackage2FeatureModelConverter {
 
         if (featureModelsOutputDirectory == null) {
             throw new IllegalStateException("Null models output directory not supported, it must be set before invoking the convert(File) method.");
+        } else if (!featureModelsOutputDirectory.exists()) {
+            featureModelsOutputDirectory.mkdirs();
         }
 
         Iterator<BundlesDeployer> artifactDeployerLoader = ServiceLoader.load(BundlesDeployer.class).iterator();


[sling-org-apache-sling-feature-cpconverter] 03/29: added missing .gitignore resource

Posted by si...@apache.org.
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-org-apache-sling-feature-cpconverter.git

commit 984fd4188bcb47d665023f47c94ca0d6516987c5
Author: stripodi <st...@192.168.1.111>
AuthorDate: Thu Apr 11 15:17:02 2019 +0200

    added missing .gitignore resource
---
 .gitignore | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f7422e7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
+.project
+.classpath
+.settings
+target
+bin
+*.iml
+.idea
+.DS_Store
+dependency-reduced-pom.xml


[sling-org-apache-sling-feature-cpconverter] 28/29: minor trivial tweaks

Posted by si...@apache.org.
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-org-apache-sling-feature-cpconverter.git

commit 0994c04a2e40f3deac73ee3649df8d3f136ff8b9
Author: stripodi <st...@simos-mbp>
AuthorDate: Tue Apr 30 23:24:25 2019 +0200

    minor trivial tweaks
---
 .../ContentPackage2FeatureModelConverter.java      | 29 +++++++++++-----------
 .../sling/feature/cpconverter/RunmodeMapper.java   | 11 ++++----
 .../feature/cpconverter/cli/ShutDownHook.java      |  2 +-
 3 files changed, 21 insertions(+), 21 deletions(-)

diff --git a/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java b/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
index 94d9e24..64dd006 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
@@ -301,16 +301,16 @@ public class ContentPackage2FeatureModelConverter {
 
             aclManager.addRepoinitExtension(getTargetFeature());
 
+            logger.info("Conversion complete!");
+
             RunmodeMapper runmodeMapper = RunmodeMapper.open(featureModelsOutputDirectory);
 
-            File featureModel = seralize(getTargetFeature(), null);
-            runmodeMapper.addOrUpdate(null, featureModel);
+            seralize(getTargetFeature(), null, runmodeMapper);
 
             if (!runModes.isEmpty()) {
                 for (java.util.Map.Entry<String, Feature> runmodeEntry : runModes.entrySet()) {
                     String runmode = runmodeEntry.getKey();
-                    featureModel = seralize(runmodeEntry.getValue(), runmode);
-                    runmodeMapper.addOrUpdate(runmode, featureModel);
+                    seralize(runmodeEntry.getValue(), runmode, runmodeMapper);
                 }
             }
 
@@ -341,22 +341,23 @@ public class ContentPackage2FeatureModelConverter {
         }
     }
 
-    private File seralize(Feature feature, String runMode) throws Exception {
-        StringBuilder fileName = new StringBuilder().append(feature.getId().getArtifactId());
+    private void seralize(Feature feature, String runMode, RunmodeMapper runmodeMapper) throws Exception {
+        StringBuilder fileNameBuilder = new StringBuilder().append(feature.getId().getArtifactId());
 
         String classifier = feature.getId().getClassifier();
         if (classifier != null && !classifier.isEmpty()) {
-            fileName.append('-').append(classifier);
+            fileNameBuilder.append('-').append(classifier);
         }
 
-        fileName.append(JSON_FILE_EXTENSION);
+        fileNameBuilder.append(JSON_FILE_EXTENSION);
+
+        String fileName = fileNameBuilder.toString();
 
-        File targetFile = new File(featureModelsOutputDirectory, fileName.toString());
+        File targetFile = new File(featureModelsOutputDirectory, fileName);
 
-        logger.info("Conversion complete!", targetFile);
-        logger.info("Writing resulting Feature File to '{}'...", targetFile);
+        logger.info("Writing resulting Feature Model '{}' to file '{}'...", feature.getId(), targetFile);
 
-        if ( idOverride != null ) {
+        if (idOverride != null && !idOverride.isEmpty()) {
             ArtifactId idOverrride = appendRunmode(ArtifactId.parse(idOverride), runMode);
             feature = feature.copy(idOverrride);
         }
@@ -365,9 +366,9 @@ public class ContentPackage2FeatureModelConverter {
             FeatureJSONWriter.write(targetWriter, feature);
 
             logger.info("'{}' Feature File successfully written!", targetFile);
-        }
 
-        return targetFile;
+            runmodeMapper.addOrUpdate(runMode, fileName);
+        }
     }
 
     public void processSubPackage(String path, File contentPackage) throws Exception {
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/RunmodeMapper.java b/src/main/java/org/apache/sling/feature/cpconverter/RunmodeMapper.java
index 601974f..01b0072 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/RunmodeMapper.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/RunmodeMapper.java
@@ -50,13 +50,12 @@ final class RunmodeMapper {
         this.properties = properties;
     }
 
-    public void addOrUpdate(String runmode, File jsonFile) {
-        if (runmode == null) {
-            runmode = DEFAULT;
+    public void addOrUpdate(String runMode, String jsonFileName) {
+        if (runMode == null) {
+            runMode = DEFAULT;
         }
 
-        String jsonFileName = jsonFile.getName();
-        String value = properties.getProperty(runmode);
+        String value = properties.getProperty(runMode);
 
         if (value != null && !value.contains(jsonFileName)) {
             value += ',' + jsonFileName;
@@ -64,7 +63,7 @@ final class RunmodeMapper {
             value = jsonFileName;
         }
 
-        properties.setProperty(runmode, value);
+        properties.setProperty(runMode, value);
     }
 
     public void save() throws IOException {
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/cli/ShutDownHook.java b/src/main/java/org/apache/sling/feature/cpconverter/cli/ShutDownHook.java
index 0c5d2fc..a1253b0 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/cli/ShutDownHook.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/cli/ShutDownHook.java
@@ -33,7 +33,6 @@ final class ShutDownHook extends Thread {
 
     @Override
     public void run() {
-        logger.info("+-----------------------------------------------------+");
         logger.info("");
 
         // format the uptime string
@@ -76,6 +75,7 @@ final class ShutDownHook extends Thread {
         logger.info("Final Memory: {}M/{}M",
                     (runtime.totalMemory() - runtime.freeMemory()) / megaUnit,
                     runtime.totalMemory() / megaUnit);
+        logger.info("+-----------------------------------------------------+");
     }
 
 }


[sling-org-apache-sling-feature-cpconverter] 07/29: added the ability to override the final Features/content-package groupId

Posted by si...@apache.org.
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-org-apache-sling-feature-cpconverter.git

commit cbbdfe96ad719a4971471e0a8ab3f97eeea8a76a
Author: stripodi <st...@192.168.1.111>
AuthorDate: Thu Apr 11 23:28:44 2019 +0200

    added the ability to override the final Features/content-package groupId
---
 .../ContentPackage2FeatureModelConverter.java      | 56 +++++++++++++---------
 ...ntentPackage2FeatureModelConverterLauncher.java |  6 ++-
 2 files changed, 39 insertions(+), 23 deletions(-)

diff --git a/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java b/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
index e67b04f..966728c 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
@@ -92,6 +92,8 @@ public class ContentPackage2FeatureModelConverter {
 
     private VaultPackageAssembler mainPackageAssembler = null;
 
+    private String groupId;
+
     public ContentPackage2FeatureModelConverter setStrictValidation(boolean strictValidation) {
         this.strictValidation = strictValidation;
         return this;
@@ -142,6 +144,11 @@ public class ContentPackage2FeatureModelConverter {
         filter.addFilteringPattern(filteringPattern);
     }
 
+    public ContentPackage2FeatureModelConverter setGroupId(String groupId) {
+        this.groupId = groupId;
+        return this;
+    }
+
     public Feature getRunMode(String runMode) {
         if (getTargetFeature() == null) {
             throw new IllegalStateException("Target Feature not initialized yet, please make sure convert() method was invoked first.");
@@ -164,6 +171,20 @@ public class ContentPackage2FeatureModelConverter {
         return artifactDeployer;
     }
 
+    private static void checkDirectory(File directory, String name) {
+        if (directory == null) {
+            throw new IllegalStateException("Null " + name + " output directory not supported, it must be set before invoking the convert(File) method.");
+        }
+
+        if (!directory.exists() && !directory.mkdirs()) {
+            throw new IllegalStateException("output directory "
+                                            + directory
+                                            + " does not exist and can not be created, please make sure current user '"
+                                            + System.getProperty("user.name")
+                                            + " has enough rights to write on the File System.");
+        }
+    }
+
     public void convert(File contentPackage) throws Exception {
         Objects.requireNonNull(contentPackage , "Null content-package can not be converted.");
 
@@ -173,15 +194,8 @@ public class ContentPackage2FeatureModelConverter {
                                             + " does not exist or it is not a valid file.");
         }
 
-        if (artifactsOutputDirectory == null) {
-            throw new IllegalStateException("Null artifacts output directory not supported, it must be set before invoking the convert(File) method.");
-        }
-
-        if (featureModelsOutputDirectory == null) {
-            throw new IllegalStateException("Null models output directory not supported, it must be set before invoking the convert(File) method.");
-        } else if (!featureModelsOutputDirectory.exists()) {
-            featureModelsOutputDirectory.mkdirs();
-        }
+        checkDirectory(artifactsOutputDirectory, "artifacts");
+        checkDirectory(featureModelsOutputDirectory, "models");
 
         Iterator<BundlesDeployer> artifactDeployerLoader = ServiceLoader.load(BundlesDeployer.class).iterator();
         if (!artifactDeployerLoader.hasNext()) {
@@ -190,14 +204,6 @@ public class ContentPackage2FeatureModelConverter {
             artifactDeployer = artifactDeployerLoader.next();
         }
 
-        if (!artifactsOutputDirectory.exists() && !artifactsOutputDirectory.mkdirs()) {
-            throw new IllegalStateException("output directory "
-                                            + artifactsOutputDirectory
-                                            + " does not exist and can not be created, please make sure current user '"
-                                            + System.getProperty("user.name")
-                                            + " has enough rights to write on the File System.");
-        }
-
         logger.info("Reading content-package '{}'...", contentPackage);
 
         try (VaultPackage vaultPackage = packageManager.open(contentPackage, strictValidation)) {
@@ -206,11 +212,17 @@ public class ContentPackage2FeatureModelConverter {
             mainPackageAssembler = VaultPackageAssembler.create(vaultPackage);
 
             PackageProperties packageProperties = vaultPackage.getProperties();
-            String group = requireNonNull(packageProperties.getProperty(PackageProperties.NAME_GROUP),
-                                          PackageProperties.NAME_GROUP
-                                          + " property not found in content-package "
-                                          + contentPackage
-                                          + ", please check META-INF/vault/properties.xml");
+            String group;
+            if (groupId != null && !groupId.isEmpty()) {
+                group = groupId;
+            } else {
+                group = requireNonNull(packageProperties.getProperty(PackageProperties.NAME_GROUP),
+                                       "'packageGroupId' parameter not specified and "
+                                       + PackageProperties.NAME_GROUP
+                                       + " property not found in content-package "
+                                       + contentPackage
+                                       + ", please check META-INF/vault/properties.xml");
+            }
             String name = requireNonNull(packageProperties.getProperty(PackageProperties.NAME_NAME),
                                          PackageProperties.NAME_NAME
                                          + " property not found in content-package "
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/cli/ContentPackage2FeatureModelConverterLauncher.java b/src/main/java/org/apache/sling/feature/cpconverter/cli/ContentPackage2FeatureModelConverterLauncher.java
index cba9553..2943f82 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/cli/ContentPackage2FeatureModelConverterLauncher.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/cli/ContentPackage2FeatureModelConverterLauncher.java
@@ -67,6 +67,9 @@ public final class ContentPackage2FeatureModelConverterLauncher implements Runna
     @Option(names = { "-o", "--features-output-directory" }, description = "The output directory where the Feature File will be generated.", required = true)
     private File featureModelsOutputDirectory;
 
+    @Option(names = { "-g", "--groupId" }, description = "The output directory where the Feature File will be generated.", required = false)
+    private String groupId;
+
     @Override
     public void run() {
         if (quiet) {
@@ -100,7 +103,8 @@ public final class ContentPackage2FeatureModelConverterLauncher implements Runna
                                                              .setMergeConfigurations(mergeConfigurations)
                                                              .setBundlesStartOrder(bundlesStartOrder)
                                                              .setArtifactsOutputDirectory(artifactsOutputDirectory)
-                                                             .setFeatureModelsOutputDirectory(featureModelsOutputDirectory);
+                                                             .setFeatureModelsOutputDirectory(featureModelsOutputDirectory)
+                                                             .setGroupId(groupId);
 
             if (filteringPatterns != null && filteringPatterns.length > 0) {
                 for (String filteringPattern : filteringPatterns) {


[sling-org-apache-sling-feature-cpconverter] 13/29: SLING-8363 - [cp2fm] map rep:SystemUser JCR nodes to repoinit Feature extension

Posted by si...@apache.org.
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-org-apache-sling-feature-cpconverter.git

commit 677c41117e09feb596532e7524aac8aeb932892a
Author: stripodi <st...@192.168.1.111>
AuthorDate: Thu Apr 18 15:03:24 2019 +0200

    SLING-8363 - [cp2fm] map rep:SystemUser JCR nodes to repoinit Feature
    extension
    
    initial checkin
---
 .../ContentPackage2FeatureModelConverter.java      | 17 ++++
 .../handlers/AbstractJcrNodeParser.java            | 58 +++++++++++++
 .../handlers/SystemUsersEntryHandler.java          | 64 +++++++++++++++
 .../handlers/XmlConfigurationEntryHandler.java     | 60 ++++++--------
 ...ache.sling.feature.cpconverter.spi.EntryHandler |  1 +
 .../handlers/SystemUsersEntryHandlerTest.java      | 95 ++++++++++++++++++++++
 .../asd-index-definition-invalid/.content.xml      | 22 +++++
 .../asd-index-definition-reader/.content.xml       | 22 +++++
 8 files changed, 303 insertions(+), 36 deletions(-)

diff --git a/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java b/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
index d686eb6..2bda617 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
@@ -66,6 +66,8 @@ public class ContentPackage2FeatureModelConverter {
 
     private static final String DEFEAULT_VERSION = "0.0.0";
 
+    private static final String REPOINIT = "repoinit";
+
     private final Logger logger = LoggerFactory.getLogger(getClass());
 
     private final PackageManager packageManager = new PackageManagerImpl();
@@ -313,6 +315,21 @@ public class ContentPackage2FeatureModelConverter {
         }
     }
 
+    public void addRepoinitStatement(String format, Object...args) {
+        Extension repoInitExtension = getTargetFeature().getExtensions().getByName(REPOINIT);
+        if (repoInitExtension == null) {
+            repoInitExtension = new Extension(ExtensionType.TEXT, REPOINIT, true);
+            getTargetFeature().getExtensions().add(repoInitExtension);
+        }
+
+        String statement = String.format(format, args);
+        if (repoInitExtension.getText() == null) {
+            repoInitExtension.setText(statement);
+        } else {
+            repoInitExtension.setText(repoInitExtension.getText() + '\n' + statement);
+        }
+    }
+
     public void addConfiguration(String runMode, String pid, Dictionary<String, Object> configurationProperties) {
         if (!mergeConfigurations) {
             checkConfigurationExist(getTargetFeature(), pid);
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/handlers/AbstractJcrNodeParser.java b/src/main/java/org/apache/sling/feature/cpconverter/handlers/AbstractJcrNodeParser.java
new file mode 100644
index 0000000..12e3089
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/cpconverter/handlers/AbstractJcrNodeParser.java
@@ -0,0 +1,58 @@
+/*
+ * 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.feature.cpconverter.handlers;
+
+import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE;
+
+import java.io.InputStream;
+
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+abstract class AbstractJcrNodeParser extends DefaultHandler {
+
+    private static final String JCR_ROOT = "jcr:root";
+
+    private static final SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
+
+    private final String primaryType;
+
+    public AbstractJcrNodeParser(String primaryType) {
+        this.primaryType = primaryType;
+    }
+
+    public void parse(InputStream input) throws Exception {
+        SAXParser saxParser = saxParserFactory.newSAXParser();
+        saxParser.parse(input, this);
+    }
+
+    @Override
+    public final void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
+        String primaryType = attributes.getValue(JCR_PRIMARYTYPE);
+
+        if (JCR_ROOT.equals(qName) && this.primaryType.equals(primaryType)) {
+            onJcrRootElement(uri, localName, qName, attributes);
+        }
+    }
+
+    protected abstract void onJcrRootElement(String uri, String localName, String qName, Attributes attributes) throws SAXException;
+
+}
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/handlers/SystemUsersEntryHandler.java b/src/main/java/org/apache/sling/feature/cpconverter/handlers/SystemUsersEntryHandler.java
new file mode 100644
index 0000000..b451e8b
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/cpconverter/handlers/SystemUsersEntryHandler.java
@@ -0,0 +1,64 @@
+/*
+ * 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.feature.cpconverter.handlers;
+
+import java.io.InputStream;
+
+import org.apache.jackrabbit.vault.fs.io.Archive;
+import org.apache.jackrabbit.vault.fs.io.Archive.Entry;
+import org.apache.sling.feature.cpconverter.ContentPackage2FeatureModelConverter;
+import org.xml.sax.Attributes;
+
+public final class SystemUsersEntryHandler extends AbstractRegexEntryHandler {
+
+    public SystemUsersEntryHandler() {
+        super("(jcr_root)?/home/users/.*/\\.content.xml");
+    }
+
+    @Override
+    public void handle(String path, Archive archive, Entry entry, ContentPackage2FeatureModelConverter converter)
+            throws Exception {
+        SystemUserParser systemUserParser = new SystemUserParser(converter);
+        try (InputStream input = archive.openInputStream(entry)) {
+            systemUserParser.parse(input);
+        }
+    }
+
+    private static final class SystemUserParser extends AbstractJcrNodeParser {
+
+        private final static String REP_SYSTEM_USER = "rep:SystemUser";
+
+        private final static String REP_AUTHORIZABLE_ID = "rep:authorizableId";
+
+        private final ContentPackage2FeatureModelConverter converter;
+
+        public SystemUserParser(ContentPackage2FeatureModelConverter converter) {
+            super(REP_SYSTEM_USER);
+            this.converter = converter;
+        }
+
+        @Override
+        protected void onJcrRootElement(String uri, String localName, String qName, Attributes attributes) {
+            String authorizableId = attributes.getValue(REP_AUTHORIZABLE_ID);
+            if (authorizableId != null && !authorizableId.isEmpty()) {
+                converter.addRepoinitStatement("create service user %s", authorizableId);
+            }
+        }
+
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/handlers/XmlConfigurationEntryHandler.java b/src/main/java/org/apache/sling/feature/cpconverter/handlers/XmlConfigurationEntryHandler.java
index ed6eabe..5188b24 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/handlers/XmlConfigurationEntryHandler.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/handlers/XmlConfigurationEntryHandler.java
@@ -16,41 +16,30 @@
  */
 package org.apache.sling.feature.cpconverter.handlers;
 
-import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE;
-
 import java.io.InputStream;
 import java.util.Dictionary;
 import java.util.Hashtable;
 
-import javax.xml.parsers.SAXParser;
-import javax.xml.parsers.SAXParserFactory;
-
 import org.apache.jackrabbit.vault.util.DocViewProperty;
 import org.xml.sax.Attributes;
 import org.xml.sax.SAXException;
-import org.xml.sax.helpers.DefaultHandler;
 
 public final class XmlConfigurationEntryHandler extends AbstractConfigurationEntryHandler {
 
-    private static final String JCR_ROOT = "jcr:root";
-
-    private static final String SLING_OSGICONFIG = "sling:OsgiConfig";
-
-    private final SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
-
     public XmlConfigurationEntryHandler() {
         super("xml");
     }
 
     @Override
     protected Dictionary<String, Object> parseConfiguration(String name, InputStream input) throws Exception {
-        SAXParser saxParser = saxParserFactory.newSAXParser();
         JcrConfigurationHandler configurationHandler = new JcrConfigurationHandler();
-        saxParser.parse(input, configurationHandler);
+        configurationHandler.parse(input);
         return configurationHandler.getConfiguration();
     }
 
-    private static final class JcrConfigurationHandler extends DefaultHandler {
+    private static final class JcrConfigurationHandler extends AbstractJcrNodeParser {
+
+        private static final String SLING_OSGICONFIG = "sling:OsgiConfig";
 
         private final Dictionary<String, Object> configuration = new Hashtable<>();
 
@@ -58,28 +47,27 @@ public final class XmlConfigurationEntryHandler extends AbstractConfigurationEnt
             return configuration;
         }
 
+        public JcrConfigurationHandler() {
+            super(SLING_OSGICONFIG);
+        }
+
         @Override
-        public void startElement(String uri, String localName, String qName, Attributes attributes)
-                throws SAXException {
-            String primaryType = attributes.getValue(JCR_PRIMARYTYPE);
-
-            if (JCR_ROOT.equals(qName) && SLING_OSGICONFIG.equals(primaryType)) {
-                for (int i = 0; i < attributes.getLength(); i++) {
-                    String attributeQName = attributes.getQName(i);
-
-                    // ignore jcr: and similar properties
-                    if (attributeQName.indexOf(':') == -1) {
-                        String attributeValue = attributes.getValue(i);
-
-                        if (attributeValue != null && !attributeValue.isEmpty()) {
-                            DocViewProperty property = DocViewProperty.parse(attributeQName, attributeValue);
-
-                            if (property.values.length > 0) {
-                                if (property.isMulti) {
-                                    configuration.put(attributeQName, property.values);
-                                } else {
-                                    configuration.put(attributeQName, property.values[0]);
-                                }
+        protected void onJcrRootElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
+            for (int i = 0; i < attributes.getLength(); i++) {
+                String attributeQName = attributes.getQName(i);
+
+                // ignore jcr: and similar properties
+                if (attributeQName.indexOf(':') == -1) {
+                    String attributeValue = attributes.getValue(i);
+
+                    if (attributeValue != null && !attributeValue.isEmpty()) {
+                        DocViewProperty property = DocViewProperty.parse(attributeQName, attributeValue);
+
+                        if (property.values.length > 0) {
+                            if (property.isMulti) {
+                                configuration.put(attributeQName, property.values);
+                            } else {
+                                configuration.put(attributeQName, property.values[0]);
                             }
                         }
                     }
diff --git a/src/main/resources/META-INF/services/org.apache.sling.feature.cpconverter.spi.EntryHandler b/src/main/resources/META-INF/services/org.apache.sling.feature.cpconverter.spi.EntryHandler
index dfa616c..b4b9ec0 100644
--- a/src/main/resources/META-INF/services/org.apache.sling.feature.cpconverter.spi.EntryHandler
+++ b/src/main/resources/META-INF/services/org.apache.sling.feature.cpconverter.spi.EntryHandler
@@ -3,4 +3,5 @@ org.apache.sling.feature.cpconverter.handlers.ConfigurationEntryHandler
 org.apache.sling.feature.cpconverter.handlers.ContentPackageEntryHandler
 org.apache.sling.feature.cpconverter.handlers.JsonConfigurationEntryHandler
 org.apache.sling.feature.cpconverter.handlers.PropertiesConfigurationEntryHandler
+org.apache.sling.feature.cpconverter.handlers.SystemUsersEntryHandler
 org.apache.sling.feature.cpconverter.handlers.XmlConfigurationEntryHandler
diff --git a/src/test/java/org/apache/sling/feature/cpconverter/handlers/SystemUsersEntryHandlerTest.java b/src/test/java/org/apache/sling/feature/cpconverter/handlers/SystemUsersEntryHandlerTest.java
new file mode 100644
index 0000000..5126e0f
--- /dev/null
+++ b/src/test/java/org/apache/sling/feature/cpconverter/handlers/SystemUsersEntryHandlerTest.java
@@ -0,0 +1,95 @@
+/*
+ * 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.feature.cpconverter.handlers;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.*;
+
+import org.apache.jackrabbit.vault.fs.io.Archive;
+import org.apache.jackrabbit.vault.fs.io.Archive.Entry;
+import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.Extension;
+import org.apache.sling.feature.ExtensionType;
+import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.cpconverter.ContentPackage2FeatureModelConverter;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class SystemUsersEntryHandlerTest {
+
+    private SystemUsersEntryHandler systemUsersEntryHandler;
+
+    @Before
+    public void setUp() {
+        systemUsersEntryHandler = new SystemUsersEntryHandler();
+    }
+
+    @After
+    public void tearDown() {
+        systemUsersEntryHandler = null;
+    }
+
+    @Test
+    public void doesNotMatch() {
+        assertFalse(systemUsersEntryHandler.matches("/this/is/a/path/not/pointing/to/a/valid/configuration.asd"));
+    }
+
+    @Test
+    public void matches() {
+        assertTrue(systemUsersEntryHandler.matches("/home/users/system/asd-share-commons/asd-index-definition-reader/.content.xml"));
+        assertTrue(systemUsersEntryHandler.matches("jcr_root/home/users/system/asd-share-commons/asd-index-definition-reader/.content.xml"));
+    }
+
+    @Test
+    public void parseSystemUser() throws Exception {
+        String path = "jcr_root/home/users/system/asd-share-commons/asd-index-definition-reader/.content.xml";
+        Extension repoinitExtension = parseAndSetRepoinit(path);
+
+        assertNotNull(repoinitExtension);
+        assertEquals(ExtensionType.TEXT, repoinitExtension.getType());
+        assertTrue(repoinitExtension.isRequired());
+        assertEquals("create service user asd-share-commons-asd-index-definition-reader-service", repoinitExtension.getText());
+    }
+
+    @Test
+    public void unrecognisedSystemUserJcrNode() throws Exception {
+        String path = "jcr_root/home/users/system/asd-share-commons/asd-index-definition-invalid/.content.xml";
+        Extension repoinitExtension = parseAndSetRepoinit(path);
+        assertNull(repoinitExtension);
+    }
+
+    private Extension parseAndSetRepoinit(String path) throws Exception {
+        Archive archive = mock(Archive.class);
+        Entry entry = mock(Entry.class);
+
+        when(archive.openInputStream(entry)).thenReturn(getClass().getResourceAsStream(path));
+
+        Feature feature = new Feature(new ArtifactId("org.apache.sling", "org.apache.sling.cp2fm", "0.0.1", null, null));
+        ContentPackage2FeatureModelConverter converter = spy(ContentPackage2FeatureModelConverter.class);
+        when(converter.getTargetFeature()).thenReturn(feature);
+
+        systemUsersEntryHandler.handle(path, archive, entry, converter);
+
+        return feature.getExtensions().getByName("repoinit");
+    }
+
+}
diff --git a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/home/users/system/asd-share-commons/asd-index-definition-invalid/.content.xml b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/home/users/system/asd-share-commons/asd-index-definition-invalid/.content.xml
new file mode 100644
index 0000000..10d13f6
--- /dev/null
+++ b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/home/users/system/asd-share-commons/asd-index-definition-invalid/.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:jcr="http://www.jcp.org/jcr/1.0" xmlns:rep="internal"
+          jcr:primaryType="rep:InvalidSystemUser"
+          jcr:uuid="b77dce45-d88d-31df-878b-8b076a68cb48"
+          rep:authorizableId="asd-share-commons-asd-index-definition-reader-service"
+          rep:principalName="asd-share-commons-asd-index-definition-reader-service"/>
diff --git a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/home/users/system/asd-share-commons/asd-index-definition-reader/.content.xml b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/home/users/system/asd-share-commons/asd-index-definition-reader/.content.xml
new file mode 100644
index 0000000..6b0ebe6
--- /dev/null
+++ b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/home/users/system/asd-share-commons/asd-index-definition-reader/.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:jcr="http://www.jcp.org/jcr/1.0" xmlns:rep="internal"
+          jcr:primaryType="rep:SystemUser"
+          jcr:uuid="b77dce45-d88d-31df-878b-8b076a68cb48"
+          rep:authorizableId="asd-share-commons-asd-index-definition-reader-service"
+          rep:principalName="asd-share-commons-asd-index-definition-reader-service"/>


[sling-org-apache-sling-feature-cpconverter] 02/29: SLING-8336 - [cp2fm] filter.xml files not properly merged

Posted by si...@apache.org.
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-org-apache-sling-feature-cpconverter.git

commit e5c3880a879b701aad7513a99b9153645748fe00
Author: Simo Tripodi <st...@adobe.com>
AuthorDate: Fri Mar 29 16:03:52 2019 +0100

    SLING-8336 - [cp2fm] filter.xml files not properly merged
---
 .../ContentPackage2FeatureModelConverter.java      | 16 ++++-------
 .../cpconverter/vltpkg/VaultPackageAssembler.java  | 31 +++++++++++++++++++---
 .../ContentPackage2FeatureModelConverterTest.java  |  1 +
 3 files changed, 34 insertions(+), 14 deletions(-)

diff --git a/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java b/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
index 32c0197..6fde8d4 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
@@ -21,16 +21,13 @@ import java.io.FileWriter;
 import java.util.Dictionary;
 import java.util.Enumeration;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Map;
 import java.util.Objects;
 import java.util.ServiceLoader;
-import java.util.Set;
 
 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.VaultPackage;
@@ -77,8 +74,6 @@ public class ContentPackage2FeatureModelConverter {
 
     private final Map<String, Feature> runModes = new HashMap<>();
 
-    private final Set<String> dependencies = new HashSet<>();
-
     private final RegexBasedResourceFilter filter = new RegexBasedResourceFilter();
 
     private BundlesDeployer artifactDeployer;
@@ -319,8 +314,13 @@ public class ContentPackage2FeatureModelConverter {
         Objects.requireNonNull(contentPackage, "Impossible to process a null vault package");
 
         try (VaultPackage vaultPackage = packageManager.open(contentPackage, strictValidation)) {
+            // scan the detected package, first
             process(vaultPackage);
 
+            // merge filters to the main new package
+            mainPackageAssembler.mergeFilters(vaultPackage.getMetaInf().getFilter());
+
+            // add the metadata-only package one to the main package
             File clonedPackage = VaultPackageAssembler.create(vaultPackage).createPackage();
             mainPackageAssembler.addEntry(path, clonedPackage);
         }
@@ -333,12 +333,6 @@ public class ContentPackage2FeatureModelConverter {
             throw new IllegalStateException("Target Feature not initialized yet, please make sure convert() method was invoked first.");
         }
 
-        dependencies.remove(vaultPackage.getId().toString());
-
-        for (Dependency dependency : vaultPackage.getDependencies()) {
-            dependencies.add(dependency.toString());
-        }
-
         Archive archive = vaultPackage.getArchive();
         try {
             archive.open(strictValidation);
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/vltpkg/VaultPackageAssembler.java b/src/main/java/org/apache/sling/feature/cpconverter/vltpkg/VaultPackageAssembler.java
index d5ec573..b46ec61 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/vltpkg/VaultPackageAssembler.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/vltpkg/VaultPackageAssembler.java
@@ -27,8 +27,12 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.util.Properties;
+import java.util.regex.Pattern;
 
 import org.apache.commons.io.IOUtils;
+import org.apache.jackrabbit.vault.fs.api.PathFilterSet;
+import org.apache.jackrabbit.vault.fs.api.WorkspaceFilter;
+import org.apache.jackrabbit.vault.fs.config.DefaultWorkspaceFilter;
 import org.apache.jackrabbit.vault.fs.io.Archive;
 import org.apache.jackrabbit.vault.fs.io.Archive.Entry;
 import org.apache.jackrabbit.vault.packaging.PackageProperties;
@@ -45,12 +49,16 @@ public final class VaultPackageAssembler implements EntryHandler {
 
     private static final String VAULT_PROPERTIES_FILE = META_INF_VAULT_DIRECTORY + "properties.xml";
 
+    private static final String VAULT_FILTER_FILE = META_INF_VAULT_DIRECTORY + "filter.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"));
 
+    private static final Pattern OSGI_BUNDLE_PATTERN = Pattern.compile("(jcr_root)?/apps/[^/]+/install(\\.([^/]+))?/.+\\.jar");
+
     public static VaultPackageAssembler create(VaultPackage vaultPackage) {
         File storingDirectory = new File(TMP_DIR, vaultPackage.getFile().getName() + "-deflated");
         storingDirectory.mkdirs();
@@ -82,9 +90,13 @@ public final class VaultPackageAssembler implements EntryHandler {
             }
         }
 
-        return new VaultPackageAssembler(storingDirectory, properties);
+        VaultPackageAssembler assembler = new VaultPackageAssembler(storingDirectory, properties);
+        assembler.mergeFilters(vaultPackage.getMetaInf().getFilter());
+        return assembler;
     }
 
+    private final DefaultWorkspaceFilter filter = new DefaultWorkspaceFilter();
+
     private final File storingDirectory;
 
     private final Properties properties;
@@ -102,14 +114,20 @@ public final class VaultPackageAssembler implements EntryHandler {
 
     /**
      * This class can not be instantiated from outside
-     *
-     * @param properties
      */
     private VaultPackageAssembler(File storingDirectory, Properties properties) {
         this.storingDirectory = storingDirectory;
         this.properties = properties;
     }
 
+    public void mergeFilters(WorkspaceFilter filter) {
+        for (PathFilterSet pathFilterSet : filter.getFilterSets()) {
+            if (!OSGI_BUNDLE_PATTERN.matcher(pathFilterSet.getRoot()).matches()) {
+                this.filter.add(pathFilterSet);
+            }
+        }
+    }
+
     public void addEntry(String path, Archive archive, Entry entry) throws IOException {
         try (InputStream input = archive.openInputStream(entry)) {
             addEntry(path, input);
@@ -146,6 +164,13 @@ public final class VaultPackageAssembler implements EntryHandler {
             properties.storeToXML(fos, null);
         }
 
+        // generate the Vault filter XML file
+        File xmlFilter = new File(storingDirectory, VAULT_FILTER_FILE);
+        try (InputStream input = filter.getSource();
+                FileOutputStream output = new FileOutputStream(xmlFilter)) {
+            IOUtils.copy(input, output);
+        }
+
         // copy the required resources
 
         for (String resource : INCLUDE_RESOURCES) {
diff --git a/src/test/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverterTest.java b/src/test/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverterTest.java
index 0514504..08ec0b2 100644
--- a/src/test/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverterTest.java
+++ b/src/test/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverterTest.java
@@ -150,6 +150,7 @@ public class ContentPackage2FeatureModelConverterTest {
                 "META-INF/vault/properties.xml",
                 "META-INF/vault/config.xml",
                 "META-INF/vault/settings.xml",
+                "META-INF/vault/filter.xml",
                 "META-INF/vault/definition/.content.xml",
                 "jcr_root/etc/packages/asd/test-bundles.zip",
                 "jcr_root/etc/packages/asd/test-configurations.zip",


[sling-org-apache-sling-feature-cpconverter] 17/29: invoke the 'artifacts.add' only once

Posted by si...@apache.org.
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-org-apache-sling-feature-cpconverter.git

commit 5b54e3d3615f5585344718fb7c6439ab062d0b48
Author: stripodi <st...@192.168.1.111>
AuthorDate: Fri Apr 26 11:55:36 2019 +0200

    invoke the 'artifacts.add' only once
---
 .../feature/cpconverter/ContentPackage2FeatureModelConverter.java | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java b/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
index d4df35c..4014256 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
@@ -35,6 +35,7 @@ import org.apache.jackrabbit.vault.packaging.VaultPackage;
 import org.apache.jackrabbit.vault.packaging.impl.PackageManagerImpl;
 import org.apache.sling.feature.Artifact;
 import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.Artifacts;
 import org.apache.sling.feature.Configuration;
 import org.apache.sling.feature.Extension;
 import org.apache.sling.feature.ExtensionType;
@@ -467,6 +468,7 @@ public class ContentPackage2FeatureModelConverter {
         Artifact artifact = new Artifact(new ArtifactId(groupId, artifactId, version, classifier, type));
 
         Feature targetFeature = getRunMode(runMode);
+        Artifacts artifacts;
 
         if (ZIP_TYPE.equals(type) ) {
             Extensions extensions = targetFeature.getExtensions();
@@ -477,11 +479,13 @@ public class ContentPackage2FeatureModelConverter {
                 extensions.add(extension);
             }
 
-            extension.getArtifacts().add(artifact);
+            artifacts = extension.getArtifacts();
         } else {
             artifact.setStartOrder(bundlesStartOrder);
-            targetFeature.getBundles().add(artifact);
+            artifacts = targetFeature.getBundles();
         }
+
+        artifacts.add(artifact);
     }
 
 }


[sling-org-apache-sling-feature-cpconverter] 24/29: handle OSGi bundles which are not valid Maven artifacts

Posted by si...@apache.org.
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-org-apache-sling-feature-cpconverter.git

commit f7fbffe1dddf9ff5841d120601b7d9d8b6dcdb6b
Author: stripodi <st...@192.168.1.111>
AuthorDate: Fri Apr 26 18:37:10 2019 +0200

    handle OSGi bundles which are not valid Maven artifacts
---
 .../cpconverter/handlers/BundleEntryHandler.java   | 37 ++++++++++++++++++----
 1 file changed, 31 insertions(+), 6 deletions(-)

diff --git a/src/main/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandler.java b/src/main/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandler.java
index 7a370b7..8e39cfa 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandler.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandler.java
@@ -23,6 +23,7 @@ import java.io.InputStream;
 import java.util.Properties;
 import java.util.jar.JarEntry;
 import java.util.jar.JarInputStream;
+import java.util.jar.Manifest;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -39,6 +40,12 @@ public final class BundleEntryHandler extends AbstractRegexEntryHandler {
 
     private static final String NAME_ARTIFACT_ID = "artifactId";
 
+    private static final String BUNDLE_SYMBOLIC_NAME = "Bundle-SymbolicName";
+
+    private static final String BUNDLE_NAME = "Bundle-Name";
+
+    private static final String BUNDLE_VERSION = "Bundle-Version";
+
     private static final String JAR_TYPE = "jar";
 
     private final Logger logger = LoggerFactory.getLogger(getClass());
@@ -53,9 +60,14 @@ public final class BundleEntryHandler extends AbstractRegexEntryHandler {
     public void handle(String path, Archive archive, Entry entry, ContentPackage2FeatureModelConverter converter) throws Exception {
         logger.info("Processing bundle {}...", entry.getName());
 
-        Properties properties = new Properties();
+        String groupId;
+        String artifactId;
+        String version;
 
         try (JarInputStream jarInput = new JarInputStream(archive.openInputStream(entry))) {
+            Properties properties = new Properties();
+            Manifest manifest = jarInput.getManifest();
+
             JarEntry jarEntry;
             while ((jarEntry = jarInput.getNextJarEntry()) != null) {
                 String entryName = jarEntry.getName();
@@ -66,11 +78,17 @@ public final class BundleEntryHandler extends AbstractRegexEntryHandler {
                     properties.load(jarInput);
                 }
             }
-        }
 
-        String groupId = getCheckedProperty(properties, NAME_GROUP_ID);
-        String artifactId = getCheckedProperty(properties, NAME_ARTIFACT_ID);
-        String version = getCheckedProperty(properties, NAME_VERSION);
+            if (!properties.isEmpty()) {
+                groupId = getCheckedProperty(properties, NAME_GROUP_ID);
+                artifactId = getCheckedProperty(properties, NAME_ARTIFACT_ID);
+                version = getCheckedProperty(properties, NAME_VERSION);
+            } else { // maybe the included jar is just an OSGi bundle but not a valid Maven artifact
+                groupId = getCheckedProperty(manifest, BUNDLE_SYMBOLIC_NAME);
+                artifactId = getCheckedProperty(manifest, BUNDLE_NAME);
+                version = getCheckedProperty(manifest, BUNDLE_VERSION);
+            }
+        }
 
         Matcher matcher = getPattern().matcher(path);
         String runMode = null;
@@ -103,9 +121,16 @@ public final class BundleEntryHandler extends AbstractRegexEntryHandler {
         }
     }
 
+    private static String getCheckedProperty(Manifest manifest, String name) {
+        String property = manifest.getMainAttributes().getValue(name).trim();
+        return requireNonNull(property, "Jar file can not be defined as a valid OSGi bundle without specifying a valid '"
+                                         + name
+                                         + "' property.");
+    }
+
     private static String getCheckedProperty(Properties properties, String name) {
         String property = properties.getProperty(name).trim();
-        return requireNonNull(property, "Bundle can not be defined as a valid Maven artifact without specifying a valid '"
+        return requireNonNull(property, "Jar file can not be defined as a valid Maven artifact without specifying a valid '"
                                          + name
                                          + "' property.");
     }


[sling-org-apache-sling-feature-cpconverter] 05/29: allow the tool generating Features and deplopying Artifacts in two different directories

Posted by si...@apache.org.
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-org-apache-sling-feature-cpconverter.git

commit 4acbf58e08c3592f6a92e2fc1b459167c2f89b1b
Author: stripodi <st...@192.168.1.111>
AuthorDate: Thu Apr 11 17:47:39 2019 +0200

    allow the tool generating Features and deplopying Artifacts in two
    different directories
---
 README.md                                          | 26 ++++++++++------
 .../ContentPackage2FeatureModelConverter.java      | 35 +++++++++++++--------
 .../cpconverter/DefaultBundlesDeployer.java        |  4 +--
 ...ntentPackage2FeatureModelConverterLauncher.java | 10 ++++--
 .../ContentPackage2FeatureModelConverterTest.java  | 36 ++++++++++++----------
 .../cpconverter/DefaultBundlesDeployerTest.java    |  1 -
 .../handlers/BundleEntryHandlerTest.java           |  6 ++--
 7 files changed, 71 insertions(+), 47 deletions(-)

diff --git a/README.md b/README.md
index 3072730..2dd6c3f 100644
--- a/README.md
+++ b/README.md
@@ -205,7 +205,7 @@ bundles are collected in an _Apache Maven repository_ compliant directory, all o
 
 ```
 $ tree bundles/
-bundles/
+artifacts/
 └── org
     └── apache
         ├── felix
@@ -265,8 +265,10 @@ new ContentPackage2FeatureModelConverter()
             .setMergeConfigurations(mergeConfigurations)
             // users can decide which is the bundles start order, declared in the generated Apache Sling Feature(s)
             .setBundlesStartOrder(bundlesStartOrder)
-            // a valid directory where the outputs will be generated (it will created, if not existing already)
-            .setOutputDirectory(outputDirectory)
+            // a valid directory where the artifacts will be deployed (it will created, if not existing already)
+            .setArtifactsOutputDirectory(outputDirectory)
+            // a valid directory where the Feature Models will be generated (it will created, if not existing already)
+            .setFeatureModelsOutputDirectory(sameOrDifferentOutputDirectory)
             // an existing and valid content-package file
             .convert(contentPackage);
 ```
@@ -348,9 +350,14 @@ once the package is decompressed, open the shell and type:
 
 ```
 $ ./bin/cp2sf -h
-Usage: cp2fm [-hmqsvX] [-b=<bundlesStartOrder>] -c=<contentPackage>
-             -o=<outputDirectory> [-f=<filteringPatterns>]...
+Missing required options [--content-package=<contentPackage>, --artifacts-output-directory=<artifactsOutputDirectory>, --features-output-directory=<featureModelsOutputDirectory>]
+Usage: cp2fm [-hmqsvX] -a=<artifactsOutputDirectory> [-b=<bundlesStartOrder>]
+             -c=<contentPackage> -o=<featureModelsOutputDirectory>
+             [-f=<filteringPatterns>]...
 Apache Sling Content Package to Sling Feature converter
+  -a, --artifacts-output-directory=<artifactsOutputDirectory>
+                            The output directory where the artifacts will be
+                              deployed.
   -b, --bundles-start-order=<bundlesStartOrder>
                             The order to start detected bundles.
   -c, --content-package=<contentPackage>
@@ -362,21 +369,22 @@ Apache Sling Content Package to Sling Feature converter
   -m, --merge-configurations
                             Flag to mark OSGi configurations with same PID will be
                               merged, the tool will fail otherwise.
-  -o, --output-directory=<outputDirectory>
-                            The output directory where the Feature File and the
-                              bundles will be deployed.
+  -o, --features-output-directory=<featureModelsOutputDirectory>
+                            The output directory where the Feature File will be
+                              generated.
   -q, --quiet               Log errors only.
   -s, --strict-validation   Flag to mark the content-package input file being strict
                               validated.
   -v, --version             Display version information.
   -X, --verbose             Produce execution debug output.
 Copyright(c) 2019 The Apache Software Foundation.
+
 ```
 
 to see all the available options; a sample execution could look like:
 
 ```
-$ ./bin/cp2sf -v -b 20 -c /content-package-2-feature-model/src/test/resources/org/apache/sling/cp2fm/test-content-package.zip -o /tmp
+$ ./bin/cp2sf -v -b 20 -c /content-package-2-feature-model/src/test/resources/org/apache/sling/cp2fm/test-content-package.zip -a /cache -o /tmp
 ```
 
 Argument Files for Long Command Lines:
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java b/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
index 71ffa8e..e0e1c32 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
@@ -84,7 +84,9 @@ public class ContentPackage2FeatureModelConverter {
 
     private int bundlesStartOrder = 0;
 
-    private File outputDirectory;
+    private File artifactsOutputDirectory;
+
+    private File featureModelsOutputDirectory;
 
     private Feature targetFeature = null;
 
@@ -113,13 +115,18 @@ public class ContentPackage2FeatureModelConverter {
         return this;
     }
 
-    public ContentPackage2FeatureModelConverter setOutputDirectory(File outputDirectory) {
-        this.outputDirectory = outputDirectory;
+    public ContentPackage2FeatureModelConverter setArtifactsOutputDirectory(File artifactsOutputDirectory) {
+        this.artifactsOutputDirectory = artifactsOutputDirectory;
         return this;
     }
 
-    public File getOutputDirectory() {
-        return outputDirectory;
+    public File getArtifactsOutputDirectory() {
+        return artifactsOutputDirectory;
+    }
+
+    public ContentPackage2FeatureModelConverter setFeatureModelsOutputDirectory(File featureModelsOutputDirectory) {
+        this.featureModelsOutputDirectory = featureModelsOutputDirectory;
+        return this;
     }
 
     public Feature getTargetFeature() {
@@ -166,20 +173,24 @@ public class ContentPackage2FeatureModelConverter {
                                             + " does not exist or it is not a valid file.");
         }
 
-        if (outputDirectory == null) {
-            throw new IllegalStateException("Null output directory not supported, it must be set before invoking the convert(File) method.");
+        if (artifactsOutputDirectory == null) {
+            throw new IllegalStateException("Null artifacts output directory not supported, it must be set before invoking the convert(File) method.");
+        }
+
+        if (featureModelsOutputDirectory == null) {
+            throw new IllegalStateException("Null models output directory not supported, it must be set before invoking the convert(File) method.");
         }
 
         Iterator<BundlesDeployer> artifactDeployerLoader = ServiceLoader.load(BundlesDeployer.class).iterator();
         if (!artifactDeployerLoader.hasNext()) {
-            artifactDeployer = new DefaultBundlesDeployer(outputDirectory);
+            artifactDeployer = new DefaultBundlesDeployer(artifactsOutputDirectory);
         } else {
             artifactDeployer = artifactDeployerLoader.next();
         }
 
-        if (!outputDirectory.exists() && !outputDirectory.mkdirs()) {
+        if (!artifactsOutputDirectory.exists() && !artifactsOutputDirectory.mkdirs()) {
             throw new IllegalStateException("output directory "
-                                            + outputDirectory
+                                            + artifactsOutputDirectory
                                             + " does not exist and can not be created, please make sure current user '"
                                             + System.getProperty("user.name")
                                             + " has enough rights to write on the File System.");
@@ -222,7 +233,7 @@ public class ContentPackage2FeatureModelConverter {
 
             // attach all unmatched resources as new content-package
 
-            File contentPackageArchive = mainPackageAssembler.createPackage(outputDirectory);
+            File contentPackageArchive = mainPackageAssembler.createPackage(artifactsOutputDirectory);
 
             // deploy the new zip content-package to the local mvn bundles dir
 
@@ -308,7 +319,7 @@ public class ContentPackage2FeatureModelConverter {
 
         fileName.append(JSON_FILE_EXTENSION);
 
-        File targetFile = new File(outputDirectory, fileName.toString());
+        File targetFile = new File(featureModelsOutputDirectory, fileName.toString());
 
         logger.info("Conversion complete!", targetFile);
         logger.info("Writing resulting Feature File to '{}'...", targetFile);
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/DefaultBundlesDeployer.java b/src/main/java/org/apache/sling/feature/cpconverter/DefaultBundlesDeployer.java
index 780aed8..7921074 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/DefaultBundlesDeployer.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/DefaultBundlesDeployer.java
@@ -29,14 +29,12 @@ import org.slf4j.LoggerFactory;
 
 public final class DefaultBundlesDeployer implements BundlesDeployer {
 
-    private static final String BUNDLES_RIRECTORY_NAME = "bundles";
-
     private final Logger logger = LoggerFactory.getLogger(getClass());
 
     private final File artifactsDirectory;
 
     public DefaultBundlesDeployer(File outputDirectory) {
-        artifactsDirectory = new File(outputDirectory, BUNDLES_RIRECTORY_NAME);
+        artifactsDirectory = outputDirectory;
         if (!artifactsDirectory.exists()) {
             artifactsDirectory.mkdirs();
         }
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/cli/ContentPackage2FeatureModelConverterLauncher.java b/src/main/java/org/apache/sling/feature/cpconverter/cli/ContentPackage2FeatureModelConverterLauncher.java
index 7aa7c50..cba9553 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/cli/ContentPackage2FeatureModelConverterLauncher.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/cli/ContentPackage2FeatureModelConverterLauncher.java
@@ -61,8 +61,11 @@ public final class ContentPackage2FeatureModelConverterLauncher implements Runna
     @Option(names = { "-f", "--filtering-patterns" }, description = "Regex based pattern(s) to reject content-package archive entries.", required = false)
     private String[] filteringPatterns;
 
-    @Option(names = { "-o", "--output-directory" }, description = "The output directory where the Feature File and the bundles will be deployed.", required = true)
-    private File outputDirectory;
+    @Option(names = { "-a", "--artifacts-output-directory" }, description = "The output directory where the artifacts will be deployed.", required = true)
+    private File artifactsOutputDirectory;
+
+    @Option(names = { "-o", "--features-output-directory" }, description = "The output directory where the Feature File will be generated.", required = true)
+    private File featureModelsOutputDirectory;
 
     @Override
     public void run() {
@@ -96,7 +99,8 @@ public final class ContentPackage2FeatureModelConverterLauncher implements Runna
                                                              .setStrictValidation(strictValidation)
                                                              .setMergeConfigurations(mergeConfigurations)
                                                              .setBundlesStartOrder(bundlesStartOrder)
-                                                             .setOutputDirectory(outputDirectory);
+                                                             .setArtifactsOutputDirectory(artifactsOutputDirectory)
+                                                             .setFeatureModelsOutputDirectory(featureModelsOutputDirectory);
 
             if (filteringPatterns != null && filteringPatterns.length > 0) {
                 for (String filteringPattern : filteringPatterns) {
diff --git a/src/test/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverterTest.java b/src/test/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverterTest.java
index 3c16634..6db646b 100644
--- a/src/test/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverterTest.java
+++ b/src/test/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverterTest.java
@@ -121,7 +121,10 @@ public class ContentPackage2FeatureModelConverterTest {
 
         File outputDirectory = new File(System.getProperty("testDirectory"), getClass().getName() + '_' + System.currentTimeMillis());
 
-        converter.setBundlesStartOrder(5).setOutputDirectory(outputDirectory).convert(packageFile);
+        converter.setBundlesStartOrder(5)
+                 .setArtifactsOutputDirectory(outputDirectory)
+                 .setFeatureModelsOutputDirectory(outputDirectory)
+                 .convert(packageFile);
 
         verifyFeatureFile(outputDirectory,
                           "asd.retail.all.json",
@@ -142,7 +145,7 @@ public class ContentPackage2FeatureModelConverterTest {
                           Arrays.asList("org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended-asd-retail"),
                           Collections.emptyList());
 
-        ZipFile zipFile = new ZipFile(new File(outputDirectory, "bundles/asd/sample/asd.retail.all/0.0.1/asd.retail.all-0.0.1-cp2fm-converted-feature.zip"));
+        ZipFile zipFile = new ZipFile(new File(outputDirectory, "asd/sample/asd.retail.all/0.0.1/asd.retail.all-0.0.1-cp2fm-converted-feature.zip"));
         for (String expectedEntry : new String[] {
                 "jcr_root/content/asd/.content.xml",
                 "jcr_root/content/asd/resources.xml",
@@ -164,7 +167,7 @@ public class ContentPackage2FeatureModelConverterTest {
     private void verifyFeatureFile(File outputDirectory,
                                    String name,
                                    String expectedArtifactId,
-                                   List<String> expectedBundles,
+                                   List<String> expectedArtifacts,
                                    List<String> expectedConfigurations,
                                    List<String> expectedContentPackagesExtensions) throws Exception {
         File featureFile = new File(outputDirectory, name);
@@ -175,9 +178,9 @@ public class ContentPackage2FeatureModelConverterTest {
 
             assertEquals(expectedArtifactId, feature.getId().toMvnId());
 
-            for (String expectedBundle : expectedBundles) {
-                assertTrue(expectedBundle + " not found in Feature " + expectedArtifactId, feature.getBundles().containsExact(ArtifactId.fromMvnId(expectedBundle)));
-                verifyInstalledBundle(outputDirectory, expectedBundle);
+            for (String expectedArtifact : expectedArtifacts) {
+                assertTrue(expectedArtifact + " not found in Feature " + expectedArtifactId, feature.getBundles().containsExact(ArtifactId.fromMvnId(expectedArtifact)));
+                verifyInstalledArtifact(outputDirectory, expectedArtifact);
             }
 
             for (String expectedConfiguration : expectedConfigurations) {
@@ -187,23 +190,21 @@ public class ContentPackage2FeatureModelConverterTest {
             for (String expectedContentPackagesExtension : expectedContentPackagesExtensions) {
                 assertTrue(expectedContentPackagesExtension + " not found in Feature " + expectedArtifactId,
                            feature.getExtensions().getByName("content-packages").getArtifacts().containsExact(ArtifactId.fromMvnId(expectedContentPackagesExtension)));
-                verifyInstalledBundle(outputDirectory, expectedContentPackagesExtension);
+                verifyInstalledArtifact(outputDirectory, expectedContentPackagesExtension);
             }
         }
     }
 
-    private void verifyInstalledBundle(File outputDirectory, String coordinates) {
+    private void verifyInstalledArtifact(File outputDirectory, String coordinates) {
         ArtifactId bundleId = ArtifactId.fromMvnId(coordinates);
 
-        File bundleDirectory = new File(outputDirectory, "bundles");
-
         StringTokenizer tokenizer = new StringTokenizer(bundleId.getGroupId(), ".");
         while (tokenizer.hasMoreTokens()) {
-            bundleDirectory = new File(bundleDirectory, tokenizer.nextToken());
+            outputDirectory = new File(outputDirectory, tokenizer.nextToken());
         }
 
-        bundleDirectory = new File(bundleDirectory, bundleId.getArtifactId());
-        bundleDirectory = new File(bundleDirectory, bundleId.getVersion());
+        outputDirectory = new File(outputDirectory, bundleId.getArtifactId());
+        outputDirectory = new File(outputDirectory, bundleId.getVersion());
 
         StringBuilder bundleFileName = new StringBuilder()
                                        .append(bundleId.getArtifactId())
@@ -214,10 +215,10 @@ public class ContentPackage2FeatureModelConverterTest {
         }
         bundleFileName.append('.').append(bundleId.getType());
 
-        File bundleFile = new File(bundleDirectory, bundleFileName.toString());
+        File bundleFile = new File(outputDirectory, bundleFileName.toString());
         assertTrue("Bundle " + bundleFile + " does not exist", bundleFile.exists());
 
-        File pomFile = new File(bundleDirectory, String.format("%s-%s.pom", bundleId.getArtifactId(), bundleId.getVersion()));
+        File pomFile = new File(outputDirectory, String.format("%s-%s.pom", bundleId.getArtifactId(), bundleId.getVersion()));
         assertTrue("POM file " + pomFile + " does not exist", pomFile.exists());
     }
 
@@ -230,7 +231,10 @@ public class ContentPackage2FeatureModelConverterTest {
 
         File outputDirectory = new File(System.getProperty("testDirectory"), getClass().getName() + '_' + System.currentTimeMillis());
 
-        converter.setBundlesStartOrder(5).setOutputDirectory(outputDirectory).convert(packageFile);
+        converter.setBundlesStartOrder(5)
+                 .setArtifactsOutputDirectory(outputDirectory)
+                 .setFeatureModelsOutputDirectory(outputDirectory)
+                 .convert(packageFile);
     }
 
 }
diff --git a/src/test/java/org/apache/sling/feature/cpconverter/DefaultBundlesDeployerTest.java b/src/test/java/org/apache/sling/feature/cpconverter/DefaultBundlesDeployerTest.java
index 1fe0429..477f02b 100644
--- a/src/test/java/org/apache/sling/feature/cpconverter/DefaultBundlesDeployerTest.java
+++ b/src/test/java/org/apache/sling/feature/cpconverter/DefaultBundlesDeployerTest.java
@@ -49,7 +49,6 @@ public class DefaultBundlesDeployerTest {
         assertNotNull(bundlesDirectory);
         assertTrue(bundlesDirectory.exists());
         assertTrue(bundlesDirectory.isDirectory());
-        assertEquals("bundles", bundlesDirectory.getName());
     }
 
     @Test(expected = NullPointerException.class)
diff --git a/src/test/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandlerTest.java b/src/test/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandlerTest.java
index 8753d61..b6db188 100644
--- a/src/test/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandlerTest.java
+++ b/src/test/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandlerTest.java
@@ -84,7 +84,7 @@ public final class BundleEntryHandlerTest {
         ContentPackage2FeatureModelConverter converter = spy(ContentPackage2FeatureModelConverter.class);
 
         File testDirectory = new File(System.getProperty("testDirectory"), getClass().getName() + '_' + System.currentTimeMillis());
-        when(converter.getOutputDirectory()).thenReturn(testDirectory);
+        when(converter.getArtifactsOutputDirectory()).thenReturn(testDirectory);
 
         doCallRealMethod().when(converter).attach(anyString(), anyString(), anyString(), anyString(), anyString(), anyString());
         when(converter.getArtifactDeployer()).thenReturn(new DefaultBundlesDeployer(testDirectory));
@@ -95,8 +95,8 @@ public final class BundleEntryHandlerTest {
 
         bundleEntryHandler.handle(bundleLocation, archive, entry, converter);
 
-        assertTrue(new File(testDirectory, "bundles/org/apache/felix/org.apache.felix.framework/6.0.1/org.apache.felix.framework-6.0.1.pom").exists());
-        assertTrue(new File(testDirectory, "bundles/org/apache/felix/org.apache.felix.framework/6.0.1/org.apache.felix.framework-6.0.1.jar").exists());
+        assertTrue(new File(testDirectory, "org/apache/felix/org.apache.felix.framework/6.0.1/org.apache.felix.framework-6.0.1.pom").exists());
+        assertTrue(new File(testDirectory, "org/apache/felix/org.apache.felix.framework/6.0.1/org.apache.felix.framework-6.0.1.jar").exists());
 
         assertFalse(converter.getTargetFeature().getBundles().isEmpty());
         assertEquals(1, feature.getBundles().size());


[sling-org-apache-sling-feature-cpconverter] 26/29: SLING-8381 - Converter produces broken hull subpackages

Posted by si...@apache.org.
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-org-apache-sling-feature-cpconverter.git

commit b85d162afa7e62adc52d2e56e387f437b14b769d
Author: stripodi <st...@simos-mbp>
AuthorDate: Tue Apr 30 12:34:32 2019 +0200

    SLING-8381 - Converter produces broken hull subpackages
    
    initial fix implementation
---
 .../cpconverter/vltpkg/VaultPackageAssembler.java  |  9 ++++++-
 .../vltpkg/VaultPackageAssemblerTest.java          | 28 ++++++++++++++++++----
 2 files changed, 32 insertions(+), 5 deletions(-)

diff --git a/src/main/java/org/apache/sling/feature/cpconverter/vltpkg/VaultPackageAssembler.java b/src/main/java/org/apache/sling/feature/cpconverter/vltpkg/VaultPackageAssembler.java
index 08d4e81..941bf27 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/vltpkg/VaultPackageAssembler.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/vltpkg/VaultPackageAssembler.java
@@ -28,6 +28,7 @@ import java.util.Properties;
 import java.util.regex.Pattern;
 
 import org.apache.commons.io.IOUtils;
+import org.apache.jackrabbit.vault.fs.api.ImportMode;
 import org.apache.jackrabbit.vault.fs.api.PathFilterSet;
 import org.apache.jackrabbit.vault.fs.api.WorkspaceFilter;
 import org.apache.jackrabbit.vault.fs.config.DefaultWorkspaceFilter;
@@ -43,6 +44,8 @@ import org.codehaus.plexus.archiver.util.DefaultFileSet;
 
 public final class VaultPackageAssembler implements EntryHandler {
 
+    private static final String JCR_ROOT_DIR_NAME = "jcr_root";
+
     private static final String META_INF_VAULT_DIRECTORY = "META-INF/vault/";
 
     private static final String VAULT_PROPERTIES_FILE = META_INF_VAULT_DIRECTORY + "properties.xml";
@@ -59,7 +62,9 @@ public final class VaultPackageAssembler implements EntryHandler {
 
     public static VaultPackageAssembler create(VaultPackage vaultPackage) {
         File storingDirectory = new File(TMP_DIR, vaultPackage.getFile().getName() + "-deflated");
-        storingDirectory.mkdirs();
+        // avoid any possible Stream is not a content package. Missing 'jcr_root' error
+        File jcrRootDirectory = new File(storingDirectory, JCR_ROOT_DIR_NAME);
+        jcrRootDirectory.mkdirs();
 
         PackageProperties packageProperties = vaultPackage.getProperties();
 
@@ -119,6 +124,8 @@ public final class VaultPackageAssembler implements EntryHandler {
     public void mergeFilters(WorkspaceFilter filter) {
         for (PathFilterSet pathFilterSet : filter.getFilterSets()) {
             if (!OSGI_BUNDLE_PATTERN.matcher(pathFilterSet.getRoot()).matches()) {
+                // make sure (empty sub-)content-packages override existing content
+                pathFilterSet.setImportMode(ImportMode.MERGE);
                 this.filter.add(pathFilterSet);
             }
         }
diff --git a/src/test/java/org/apache/sling/feature/cpconverter/vltpkg/VaultPackageAssemblerTest.java b/src/test/java/org/apache/sling/feature/cpconverter/vltpkg/VaultPackageAssemblerTest.java
index 011a516..9d613b5 100644
--- a/src/test/java/org/apache/sling/feature/cpconverter/vltpkg/VaultPackageAssemblerTest.java
+++ b/src/test/java/org/apache/sling/feature/cpconverter/vltpkg/VaultPackageAssemblerTest.java
@@ -16,6 +16,7 @@
  */
 package org.apache.sling.feature.cpconverter.vltpkg;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 
 import java.io.File;
@@ -26,9 +27,11 @@ import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
 
 import org.apache.commons.io.FileUtils;
+import org.apache.jackrabbit.vault.fs.api.ImportMode;
+import org.apache.jackrabbit.vault.fs.api.PathFilterSet;
+import org.apache.jackrabbit.vault.fs.config.DefaultWorkspaceFilter;
 import org.apache.jackrabbit.vault.packaging.VaultPackage;
 import org.apache.jackrabbit.vault.packaging.impl.PackageManagerImpl;
-import org.apache.sling.feature.cpconverter.vltpkg.VaultPackageAssembler;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -61,12 +64,28 @@ public class VaultPackageAssemblerTest {
 
     @Test
     public void packageResource() throws Exception {
-        assembler.addEntry(resourceLocation, getClass().getResourceAsStream("../handlers/" + resourceLocation));
+        if (resourceLocation != null) {
+            assembler.addEntry(resourceLocation, getClass().getResourceAsStream("../handlers/" + resourceLocation));
+        }
         File contentPackage = assembler.createPackage(testDirectory);
 
         ZipFile zipFile = new ZipFile(contentPackage);
-        ZipEntry entry = zipFile.getEntry(resourceLocation);
-        assertNotNull(entry);
+        ZipEntry resourceEntry;
+        if (resourceLocation != null) {
+            resourceEntry = zipFile.getEntry(resourceLocation);
+        } else {
+            resourceEntry = zipFile.getEntry("jcr_root");
+        }
+        assertNotNull(resourceEntry);
+
+        DefaultWorkspaceFilter filter = new DefaultWorkspaceFilter();
+        ZipEntry filtersEntry = zipFile.getEntry("META-INF/vault/filter.xml");
+        filter.load(zipFile.getInputStream(filtersEntry));
+
+        for (PathFilterSet filterSet : filter.getFilterSets()) {
+            assertEquals(ImportMode.MERGE, filterSet.getImportMode());
+        }
+
         zipFile.close();
     }
 
@@ -79,6 +98,7 @@ public class VaultPackageAssemblerTest {
         VaultPackageAssembler assembler = VaultPackageAssembler.create(vaultPackage);
 
         return Arrays.asList(new Object[][] {
+            { null, assembler },
             { "jcr_root/.content.xml", assembler },
             { "jcr_root/asd/.content.xml", assembler },
             { "jcr_root/asd/public/license.txt", assembler }


[sling-org-apache-sling-feature-cpconverter] 20/29: Adjust Converter to support bundles in /libs as well

Posted by si...@apache.org.
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-org-apache-sling-feature-cpconverter.git

commit 44b0e0231fb3f7079f3d2984f15b58e4c46ca8c6
Author: stripodi <st...@192.168.1.111>
AuthorDate: Fri Apr 26 14:55:00 2019 +0200

    Adjust Converter to support bundles in /libs as well
---
 .../sling/feature/cpconverter/handlers/BundleEntryHandler.java       | 2 +-
 .../sling/feature/cpconverter/handlers/BundleEntryHandlerTest.java   | 5 +++++
 2 files changed, 6 insertions(+), 1 deletion(-)

diff --git a/src/main/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandler.java b/src/main/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandler.java
index 860a1a4..cc2c447 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandler.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandler.java
@@ -46,7 +46,7 @@ public final class BundleEntryHandler extends AbstractRegexEntryHandler {
     private final Pattern pomPropertiesPattern = Pattern.compile("META-INF/maven/[^/]+/[^/]+/pom.properties");
 
     public BundleEntryHandler() {
-        super("(jcr_root)?/apps/[^/]+/install(\\.([^/]+))?/.+\\.jar");
+        super("(jcr_root)?/(apps|libs)/[^/]+/install(\\.([^/]+))?/.+\\.jar");
     }
 
     @Override
diff --git a/src/test/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandlerTest.java b/src/test/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandlerTest.java
index b6db188..fcd5162 100644
--- a/src/test/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandlerTest.java
+++ b/src/test/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandlerTest.java
@@ -67,6 +67,11 @@ public final class BundleEntryHandlerTest {
     }
 
     @Test
+    public void matchesOnLibsDir() {
+        assertTrue(bundleEntryHandler.matches(bundleLocation.replace("/apps/", "/libs/")));
+    }
+
+    @Test
     public void deployBundle() throws Exception {
         Archive archive = mock(Archive.class);
         Entry entry = mock(Entry.class);


[sling-org-apache-sling-feature-cpconverter] 16/29: always supply the pom file, the extracted one could contain informations than can not be retrieved from the repository

Posted by si...@apache.org.
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-org-apache-sling-feature-cpconverter.git

commit b191061b4e43acea5d203ec93cc862f918ac5afe
Author: stripodi <st...@192.168.1.111>
AuthorDate: Fri Apr 26 11:53:07 2019 +0200

    always supply the pom file, the extracted one could contain informations
    than can not be retrieved from the repository
---
 .../ContentPackage2FeatureModelConverter.java      | 36 +++++++---------------
 .../cpconverter/DefaultBundlesDeployer.java        | 36 ++++++++++++++--------
 .../cpconverter/handlers/BundleEntryHandler.java   | 27 ++--------------
 3 files changed, 36 insertions(+), 63 deletions(-)

diff --git a/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java b/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
index 378ac20..d4df35c 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
@@ -25,7 +25,6 @@ import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
-import java.util.Objects;
 import java.util.ServiceLoader;
 
 import org.apache.jackrabbit.vault.fs.io.Archive;
@@ -46,7 +45,6 @@ import org.apache.sling.feature.cpconverter.spi.BundlesDeployer;
 import org.apache.sling.feature.cpconverter.spi.EntryHandler;
 import org.apache.sling.feature.cpconverter.vltpkg.VaultPackageAssembler;
 import org.apache.sling.feature.cpconverter.writers.FileArtifactWriter;
-import org.apache.sling.feature.cpconverter.writers.MavenPomSupplierWriter;
 import org.apache.sling.feature.io.json.FeatureJSONWriter;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -55,8 +53,6 @@ public class ContentPackage2FeatureModelConverter {
 
     private static final String CONTENT_PACKAGES = "content-packages";
 
-    public static final String POM_TYPE = "pom";
-
     public static final String ZIP_TYPE = "zip";
 
     public static final String PACKAGE_CLASSIFIER = "cp2fm-converted";
@@ -141,7 +137,7 @@ public class ContentPackage2FeatureModelConverter {
     }
 
     public void addFilteringPattern(String filteringPattern) {
-        Objects.requireNonNull(filteringPattern, "Null pattern to filter resources out is not a valid filtering pattern");
+        requireNonNull(filteringPattern, "Null pattern to filter resources out is not a valid filtering pattern");
         if (filteringPattern.isEmpty()) {
             throw new IllegalArgumentException("Empty pattern to filter resources out is not a valid filtering pattern");
         }
@@ -214,7 +210,7 @@ public class ContentPackage2FeatureModelConverter {
     }
 
     public void convert(File contentPackage) throws Exception {
-        Objects.requireNonNull(contentPackage , "Null content-package can not be converted.");
+        requireNonNull(contentPackage , "Null content-package can not be converted.");
 
         if (!contentPackage.exists() || !contentPackage.isFile()) {
             throw new IllegalArgumentException("Content-package "
@@ -284,21 +280,11 @@ public class ContentPackage2FeatureModelConverter {
             // 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(),
-                                                           PACKAGE_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);
+                                    PACKAGE_CLASSIFIER,
+                                    ZIP_TYPE);
 
             attach(null,
                    targetFeature.getId().getGroupId(),
@@ -386,8 +372,8 @@ public class ContentPackage2FeatureModelConverter {
     }
 
     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");
+        requireNonNull(path, "Impossible to process a null vault package");
+        requireNonNull(contentPackage, "Impossible to process a null vault package");
 
         try (VaultPackage vaultPackage = packageManager.open(contentPackage, strictValidation)) {
             // scan the detected package, first
@@ -403,7 +389,7 @@ public class ContentPackage2FeatureModelConverter {
     }
 
     private void process(VaultPackage vaultPackage) throws Exception {
-        Objects.requireNonNull(vaultPackage, "Impossible to process a null vault package");
+        requireNonNull(vaultPackage, "Impossible to process a null vault package");
 
         if (getTargetFeature() == null) {
             throw new IllegalStateException("Target Feature not initialized yet, please make sure convert() method was invoked first.");
@@ -473,10 +459,10 @@ public class ContentPackage2FeatureModelConverter {
                        String version,
                        String classifier,
                        String type) {
-        Objects.requireNonNull(groupId, "Artifact can not be attached to a feature without specifying a valid 'groupId'.");
-        Objects.requireNonNull(artifactId, "Artifact can not be attached to a feature without specifying a valid 'artifactId'.");
-        Objects.requireNonNull(version, "Artifact can not be attached to a feature without specifying a valid 'version'.");
-        Objects.requireNonNull(type, "Artifact can not be attached to a feature without specifying a valid 'type'.");
+        requireNonNull(groupId, "Artifact can not be attached to a feature without specifying a valid 'groupId'.");
+        requireNonNull(artifactId, "Artifact can not be attached to a feature without specifying a valid 'artifactId'.");
+        requireNonNull(version, "Artifact can not be attached to a feature without specifying a valid 'version'.");
+        requireNonNull(type, "Artifact can not be attached to a feature without specifying a valid 'type'.");
 
         Artifact artifact = new Artifact(new ArtifactId(groupId, artifactId, version, classifier, type));
 
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/DefaultBundlesDeployer.java b/src/main/java/org/apache/sling/feature/cpconverter/DefaultBundlesDeployer.java
index 7921074..8d86b5a 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/DefaultBundlesDeployer.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/DefaultBundlesDeployer.java
@@ -16,14 +16,16 @@
  */
 package org.apache.sling.feature.cpconverter;
 
+import static java.util.Objects.requireNonNull;
+
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
-import java.util.Objects;
 import java.util.StringTokenizer;
 
 import org.apache.sling.feature.cpconverter.spi.ArtifactWriter;
 import org.apache.sling.feature.cpconverter.spi.BundlesDeployer;
+import org.apache.sling.feature.cpconverter.writers.MavenPomSupplierWriter;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -47,16 +49,16 @@ public final class DefaultBundlesDeployer implements BundlesDeployer {
 
     @Override
     public void deploy(ArtifactWriter artifactWriter,
-                              String groupId,
-                              String artifactId,
-                              String version,
-                              String classifier,
-                              String type) throws IOException {
-        Objects.requireNonNull(artifactWriter, "Null ArtifactWriter can not install an artifact to a Maven repository.");
-        Objects.requireNonNull(groupId, "Bundle can not be installed to a Maven repository without specifying a valid 'groupId'.");
-        Objects.requireNonNull(artifactId, "Bundle can not be installed to a Maven repository without specifying a valid 'artifactId'.");
-        Objects.requireNonNull(version, "Bundle can not be installed to a Maven repository without specifying a valid 'version'.");
-        Objects.requireNonNull(type, "Bundle can not be installed to a Maven repository without specifying a valid 'type'.");
+                       String groupId,
+                       String artifactId,
+                       String version,
+                       String classifier,
+                       String type) throws IOException {
+        requireNonNull(artifactWriter, "Null ArtifactWriter can not install an artifact to a Maven repository.");
+        requireNonNull(groupId, "Bundle can not be installed to a Maven repository without specifying a valid 'groupId'.");
+        requireNonNull(artifactId, "Bundle can not be installed to a Maven repository without specifying a valid 'artifactId'.");
+        requireNonNull(version, "Bundle can not be installed to a Maven repository without specifying a valid 'version'.");
+        requireNonNull(type, "Bundle can not be installed to a Maven repository without specifying a valid 'type'.");
 
         File targetDir = artifactsDirectory;
 
@@ -67,11 +69,11 @@ public final class DefaultBundlesDeployer implements BundlesDeployer {
         }
 
         targetDir = new File(targetDir, artifactId);
-
         targetDir = new File(targetDir, version);
-
         targetDir.mkdirs();
 
+        // deploy the main artifact
+
         StringBuilder nameBuilder = new StringBuilder()
                                     .append(artifactId)
                                     .append('-')
@@ -92,6 +94,14 @@ public final class DefaultBundlesDeployer implements BundlesDeployer {
         }
 
         logger.info("Data successfully written to {}.", targetFile);
+
+        // automatically deploy the supplied POM file
+
+        targetFile = new File(targetDir, String.format("%s-%s.pom", artifactId, version));
+
+        try (FileOutputStream targetStream = new FileOutputStream(targetFile)) {
+            new MavenPomSupplierWriter(groupId, artifactId, version, type).write(targetStream);
+        }
     }
 
 }
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandler.java b/src/main/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandler.java
index 283cf12..8412eb6 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandler.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandler.java
@@ -16,26 +16,21 @@
  */
 package org.apache.sling.feature.cpconverter.handlers;
 
+import static java.util.Objects.requireNonNull;
 import static org.apache.jackrabbit.vault.packaging.PackageProperties.NAME_VERSION;
-import static org.apache.sling.feature.cpconverter.ContentPackage2FeatureModelConverter.POM_TYPE;
 
-import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.InputStream;
-import java.util.Objects;
 import java.util.Properties;
 import java.util.jar.JarEntry;
 import java.util.jar.JarInputStream;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-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.feature.cpconverter.ContentPackage2FeatureModelConverter;
-import org.apache.sling.feature.cpconverter.spi.ArtifactWriter;
 import org.apache.sling.feature.cpconverter.writers.InputStreamArtifactWriter;
-import org.apache.sling.feature.cpconverter.writers.MavenPomSupplierWriter;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -51,8 +46,6 @@ public final class BundleEntryHandler extends AbstractRegexEntryHandler {
 
     private final Pattern pomPropertiesPattern = Pattern.compile("META-INF/maven/[^/]+/[^/]+/pom.properties");
 
-    private final Pattern pomXmlPattern = Pattern.compile("META-INF/maven/[^/]+/[^/]+/pom.xml");
-
     public BundleEntryHandler() {
         super("(jcr_root)?/apps/[^/]+/install(\\.([^/]+))?/.+\\.jar");
     }
@@ -62,7 +55,6 @@ public final class BundleEntryHandler extends AbstractRegexEntryHandler {
         logger.info("Processing bundle {}...", entry.getName());
 
         Properties properties = new Properties();
-        byte[] pomXml = null;
 
         try (JarInputStream jarInput = new JarInputStream(archive.openInputStream(entry));
                 ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
@@ -74,11 +66,6 @@ public final class BundleEntryHandler extends AbstractRegexEntryHandler {
                     logger.info("Reading '{}' bundle GAV from {}...", entry.getName(), entryName);
 
                     properties.load(jarInput);
-                } else if (pomXmlPattern.matcher(entryName).matches()) {
-                    logger.info("Reading '{}' POM file from {}...", entry.getName(), entryName);
-
-                    IOUtils.copy(jarInput, baos);
-                    pomXml = baos.toByteArray();
                 }
             }
         }
@@ -116,23 +103,13 @@ public final class BundleEntryHandler extends AbstractRegexEntryHandler {
                              null,
                              JAR_TYPE);
         }
-
-        ArtifactWriter pomWriter;
-        if (pomXml == null) {
-            pomWriter = new MavenPomSupplierWriter(groupId, artifactId, version, JAR_TYPE);
-        } else {
-            pomWriter = new InputStreamArtifactWriter(new ByteArrayInputStream(pomXml));
-        }
-
-        converter.getArtifactDeployer().deploy(pomWriter, groupId, artifactId, version, null, POM_TYPE);
     }
 
     private static String getCheckedProperty(Properties properties, String name) {
         String property = properties.getProperty(name).trim();
-        Objects.requireNonNull(property, "Bundle can not be defined as a valid Maven artifact without specifying a valid '"
+        return requireNonNull(property, "Bundle can not be defined as a valid Maven artifact without specifying a valid '"
                                          + name
                                          + "' property.");
-        return property;
     }
 
 }


[sling-org-apache-sling-feature-cpconverter] 29/29: include RunmodeMapper generated file verifications

Posted by si...@apache.org.
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-org-apache-sling-feature-cpconverter.git

commit 411aeb25cf9ca013a454cd6d2cff157b6fe47d0a
Author: stripodi <st...@simos-mbp>
AuthorDate: Tue Apr 30 23:35:49 2019 +0200

    include RunmodeMapper generated file verifications
---
 .../ContentPackage2FeatureModelConverterTest.java       | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/src/test/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverterTest.java b/src/test/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverterTest.java
index 116ab7d..5eae872 100644
--- a/src/test/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverterTest.java
+++ b/src/test/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverterTest.java
@@ -17,11 +17,13 @@
 package org.apache.sling.feature.cpconverter;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.mock;
 
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.FileReader;
 import java.io.Reader;
 import java.net.URL;
@@ -29,6 +31,7 @@ import java.util.Arrays;
 import java.util.Collections;
 import java.util.Hashtable;
 import java.util.List;
+import java.util.Properties;
 import java.util.StringTokenizer;
 import java.util.zip.ZipFile;
 
@@ -146,6 +149,20 @@ public class ContentPackage2FeatureModelConverterTest {
                           Arrays.asList("org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended-asd-retail"),
                           Collections.emptyList());
 
+        // verify the runmode.mapper integrity
+        File runmodeMapperFile = new File(outputDirectory, "runmode.mapping");
+        assertTrue(runmodeMapperFile.exists());
+        assertTrue(runmodeMapperFile.isFile());
+        Properties runModes = new Properties();
+        try (FileInputStream input = new FileInputStream(runmodeMapperFile)) {
+            runModes.load(input);
+        }
+        assertFalse(runModes.isEmpty());
+        assertTrue(runModes.containsKey("(default)"));
+        assertEquals("asd.retail.all.json", runModes.getProperty("(default)"));
+        assertEquals("asd.retail.all-author.json", runModes.getProperty("author"));
+        assertEquals("asd.retail.all-publish.json", runModes.getProperty("publish"));
+
         ZipFile zipFile = new ZipFile(new File(outputDirectory, "asd/sample/asd.retail.all/0.0.1/asd.retail.all-0.0.1-cp2fm-converted.zip"));
         for (String expectedEntry : new String[] {
                 "jcr_root/content/asd/.content.xml",


[sling-org-apache-sling-feature-cpconverter] 22/29: Adjust Converter to support bundles and configuration in /libs as well

Posted by si...@apache.org.
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-org-apache-sling-feature-cpconverter.git

commit 9903beae183791ea1d7107e124743edb566a2d8e
Author: stripodi <st...@192.168.1.111>
AuthorDate: Fri Apr 26 17:45:17 2019 +0200

    Adjust Converter to support bundles and configuration in /libs as well
---
 .../feature/cpconverter/handlers/AbstractConfigurationEntryHandler.java | 2 +-
 .../apache/sling/feature/cpconverter/handlers/BundleEntryHandler.java   | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/main/java/org/apache/sling/feature/cpconverter/handlers/AbstractConfigurationEntryHandler.java b/src/main/java/org/apache/sling/feature/cpconverter/handlers/AbstractConfigurationEntryHandler.java
index 99ea954..6d6ed3b 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/handlers/AbstractConfigurationEntryHandler.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/handlers/AbstractConfigurationEntryHandler.java
@@ -27,7 +27,7 @@ import org.apache.sling.feature.cpconverter.ContentPackage2FeatureModelConverter
 abstract class AbstractConfigurationEntryHandler extends AbstractRegexEntryHandler {
 
     public AbstractConfigurationEntryHandler(String extension) {
-        super("(jcr_root)?/apps/[^/]+/config(\\.([^/]+))?/.+\\." + extension);
+        super("(jcr_root)?/(?:apps|libs)/.+/config(\\.([^/]+))?/.+\\." + extension);
     }
 
     @Override
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandler.java b/src/main/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandler.java
index 098aec0..7a370b7 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandler.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandler.java
@@ -46,7 +46,7 @@ public final class BundleEntryHandler extends AbstractRegexEntryHandler {
     private final Pattern pomPropertiesPattern = Pattern.compile("META-INF/maven/[^/]+/[^/]+/pom.properties");
 
     public BundleEntryHandler() {
-        super("(jcr_root)?/(?:apps|libs)/[^/]+/install(\\.([^/]+))?/.+\\.jar");
+        super("(jcr_root)?/(?:apps|libs)/.+/install(\\.([^/]+))?/.+\\.jar");
     }
 
     @Override


[sling-org-apache-sling-feature-cpconverter] 19/29: removed unused stream

Posted by si...@apache.org.
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-org-apache-sling-feature-cpconverter.git

commit 99c75045a65db3e73aea22686a01825c5b7526ad
Author: stripodi <st...@192.168.1.111>
AuthorDate: Fri Apr 26 14:30:44 2019 +0200

    removed unused stream
---
 .../apache/sling/feature/cpconverter/handlers/BundleEntryHandler.java | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/src/main/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandler.java b/src/main/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandler.java
index 8412eb6..860a1a4 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandler.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandler.java
@@ -19,7 +19,6 @@ package org.apache.sling.feature.cpconverter.handlers;
 import static java.util.Objects.requireNonNull;
 import static org.apache.jackrabbit.vault.packaging.PackageProperties.NAME_VERSION;
 
-import java.io.ByteArrayOutputStream;
 import java.io.InputStream;
 import java.util.Properties;
 import java.util.jar.JarEntry;
@@ -56,8 +55,7 @@ public final class BundleEntryHandler extends AbstractRegexEntryHandler {
 
         Properties properties = new Properties();
 
-        try (JarInputStream jarInput = new JarInputStream(archive.openInputStream(entry));
-                ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+        try (JarInputStream jarInput = new JarInputStream(archive.openInputStream(entry))) {
             JarEntry jarEntry;
             while ((jarEntry = jarInput.getNextJarEntry()) != null) {
                 String entryName = jarEntry.getName();


[sling-org-apache-sling-feature-cpconverter] 01/29: initial checkin, moved from whiteboard

Posted by si...@apache.org.
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-org-apache-sling-feature-cpconverter.git

commit f039042ac8bdfd5016474abf823a03a7312b51a5
Author: Simo Tripodi <st...@adobe.com>
AuthorDate: Wed Mar 27 23:56:54 2019 +0100

    initial checkin, moved from whiteboard
---
 CODE_OF_CONDUCT.md                                 |  22 ++
 CONTRIBUTING.md                                    |  24 ++
 LICENSE                                            | 201 ++++++++++
 README.md                                          | 398 +++++++++++++++++++
 pom.xml                                            | 322 +++++++++++++++
 src/main/assembly/README.md                        | 149 +++++++
 src/main/assembly/bin.xml                          |  71 ++++
 .../ContentPackage2FeatureModelConverter.java      | 431 +++++++++++++++++++++
 .../cpconverter/DefaultBundlesDeployer.java        |  99 +++++
 .../cpconverter/RegexBasedResourceFilter.java      |  54 +++
 ...ntentPackage2FeatureModelConverterLauncher.java | 186 +++++++++
 .../feature/cpconverter/cli/ShutDownHook.java      |  81 ++++
 .../feature/cpconverter/cli/package-info.java      |  21 +
 .../AbstractConfigurationEntryHandler.java         |  68 ++++
 .../handlers/AbstractRegexEntryHandler.java        |  44 +++
 .../cpconverter/handlers/BundleEntryHandler.java   | 136 +++++++
 .../handlers/ConfigurationEntryHandler.java        |  36 ++
 .../handlers/ContentPackageEntryHandler.java       |  49 +++
 .../handlers/JsonConfigurationEntryHandler.java    |  75 ++++
 .../PropertiesConfigurationEntryHandler.java       |  60 +++
 .../handlers/XmlConfigurationEntryHandler.java     |  92 +++++
 .../feature/cpconverter/handlers/package-info.java |  23 ++
 .../sling/feature/cpconverter/package-info.java    |  21 +
 .../feature/cpconverter/spi/ArtifactWriter.java    |  26 ++
 .../feature/cpconverter/spi/BundlesDeployer.java   |  33 ++
 .../feature/cpconverter/spi/EntryHandler.java      |  29 ++
 .../feature/cpconverter/spi/package-info.java      |  21 +
 .../cpconverter/vltpkg/VaultPackageAssembler.java  | 172 ++++++++
 .../feature/cpconverter/vltpkg/package-info.java   |  21 +
 .../cpconverter/writers/FileArtifactWriter.java    |  42 ++
 .../writers/InputStreamArtifactWriter.java         |  39 ++
 .../writers/MavenPomSupplierWriter.java            |  54 +++
 .../feature/cpconverter/writers/package-info.java  |  21 +
 src/main/legal/LICENSE-with-deps                   | 233 +++++++++++
 src/main/legal/NOTICE-with-deps                    |   5 +
 src/main/legal/license-header                      |  14 +
 ...ache.sling.feature.cpconverter.spi.EntryHandler |   6 +
 .../sling/feature/cpconverter/vltpkg/config.xml    |  95 +++++
 .../cpconverter/vltpkg/definition/.content.xml     |  19 +
 .../sling/feature/cpconverter/vltpkg/settings.xml  |  22 ++
 .../ContentPackage2FeatureModelConverterTest.java  | 235 +++++++++++
 .../cpconverter/DefaultBundlesDeployerTest.java    |  80 ++++
 .../cpconverter/RegexBasedResourceFilterTest.java  |  69 ++++
 .../handlers/BundleEntryHandlerTest.java           | 118 ++++++
 .../handlers/ConfigurationEntryHandlerTest.java    | 127 ++++++
 .../handlers/ContentPackageEntryHandlerTest.java   |  51 +++
 .../JsonConfigurationEntryHandlerTest.java         |  46 +++
 .../vltpkg/VaultPackageAssemblerTest.java          |  89 +++++
 .../cpconverter/handlers/jcr_root/.content.xml     |  22 ++
 ...ceusermapping.impl.ServiceUserMapperImpl.config |  17 +
 ...ceusermapping.impl.ServiceUserMapperImpl.config |  17 +
 ...ing.impl.ServiceUserMapperImpl.INVALID.cfg.json |   1 +
 ...rviceusermapping.impl.ServiceUserMapperImpl.cfg |  17 +
 ...usermapping.impl.ServiceUserMapperImpl.cfg.json |   7 +
 ...ceusermapping.impl.ServiceUserMapperImpl.config |  17 +
 ...sermapping.impl.ServiceUserMapperImpl.empty.cfg |  14 +
 ...pping.impl.ServiceUserMapperImpl.empty.cfg.json |   3 +
 ...mapping.impl.ServiceUserMapperImpl.empty.config |  14 +
 ...sermapping.impl.ServiceUserMapperImpl.empty.xml |  19 +
 ...apping.impl.ServiceUserMapperImpl.empty.xml.cfg |  19 +
 ...rviceusermapping.impl.ServiceUserMapperImpl.xml |  21 +
 ...eusermapping.impl.ServiceUserMapperImpl.xml.cfg |  22 ++
 .../apps/asd/install.author/test-framework.jar     | Bin 0 -> 13288 bytes
 .../apps/asd/install.publish/test-framework.jar    | Bin 0 -> 13288 bytes
 .../apps/asd/install/test-framework-no-pom.jar     | Bin 0 -> 10769 bytes
 .../jcr_root/apps/asd/install/test-framework.jar   | Bin 0 -> 13288 bytes
 .../cpconverter/handlers/jcr_root/asd/.content.xml |  20 +
 .../handlers/jcr_root/asd/public/.content.xml      |  19 +
 .../handlers/jcr_root/asd/public/_rep_policy.xml   |  24 ++
 .../handlers/jcr_root/asd/public/license.txt       |  14 +
 .../test-content-package-unacceptable.zip          | Bin 0 -> 15017 bytes
 .../feature/cpconverter/test-content-package.zip   | Bin 0 -> 46393 bytes
 72 files changed, 4617 insertions(+)

diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..0fa18e5
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,22 @@
+<!--/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+  ~ 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.
+  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/-->
+Apache Software Foundation Code of Conduct
+====
+
+Being an Apache project, Apache Sling adheres to the Apache Software Foundation's [Code of Conduct](https://www.apache.org/foundation/policies/conduct.html).
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..ac82a1a
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,24 @@
+<!--/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+  ~ 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.
+  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/-->
+Contributing
+====
+
+Thanks for choosing to contribute!
+
+You will find all the necessary details about how you can do this at https://sling.apache.org/contributing.html.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..261eeb9
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed 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/README.md b/README.md
new file mode 100644
index 0000000..3072730
--- /dev/null
+++ b/README.md
@@ -0,0 +1,398 @@
+[<img src="http://sling.apache.org/res/logos/sling.png"/>](http://sling.apache.org)
+
+# Apache Sling Content-Package to Feature Model converter
+
+This tool aims to provide to Apache Sling users an easy-to-use conversion tool which is able to convert `content-package` archives to the new _Sling Feature Model_.
+
+## Introduction
+
+`content-package`s are zipped archives containing OSGi bundles, OSGi configurations and resources (and nested `content-package`s as well), aside metadata, that can be used to install content into a _JCR_ repository using the [Apache Jackrabbit FileVault](http://jackrabbit.apache.org/filevault/) packaging runtime.
+
+OTOH, [Apache Sling Feature](https://github.com/apache/sling-org-apache-sling-feature) allows users to describe an entire OSGi-based application based on reusable components and includes everything related to this application, including bundles, configuration, framework properties, capabilities, requirements and custom artifacts.
+
+The _Apache Sling Content Package to Feature Model converter_ (referred as _cp2fm_) is a tool able to extract OSGI bundles, OSGi configurations, resources and iteratively scan nested `content-package`s from an input `content-package` and create one (or more) _Apache Sling Feature_ model files and deploy the extracted OSGi bundles in a directory which structure is compliant the _Apache Maven_ repository conventions.
+
+## Understanding the Input
+
+As exposed above, `content-package`s are archives, compressed with the ZIP algorithm, which contain:
+
+ * OSGi bundles, conventionally found under the `jcr_root/apps/<application>/install(.runMode)/<bundle>.jar` path; typically, OSGi bundles are also valid _Apache Maven_ artifacts, that means that they contain _Apache Maven_ metadata files such as `META-INF/maven/<groupId>/<artifactId>/pom.(xml|properties)`;
+ * OSGi configurations, conventionally found under the `jcr_root/apps/<application>/config(.runMode)/<configuration>.<extension>` path;
+ * nested `content-package`s, conventionally found under the `jcr_root/etc/packages/<package-name>.zip` path;
+ * Metadata files, under the `META-INF/` directory;
+ * any other kind of resource.
+
+### a content-package sample
+
+We can have a look at what's inside a `test-content-package.zip` test `content-package` included in the `cp2fm` test resources:
+
+```
+$ unzip -l ./content-package-2-feature-model/src/test/resources/org/apache/sling/cp2fm/test-content-package.zip 
+Archive:  content-package-2-feature-model/src/test/resources/org/apache/sling/cp2fm/test-content-package.zip
+  Length      Date    Time    Name
+---------  ---------- -----   ----
+        0  03-12-2019 17:31   META-INF/
+       69  03-12-2019 17:31   META-INF/MANIFEST.MF
+        0  03-12-2019 17:06   jcr_root/
+        0  03-12-2019 17:06   jcr_root/etc/
+        0  03-12-2019 17:06   jcr_root/etc/packages/
+        0  03-12-2019 17:30   jcr_root/etc/packages/asd/
+    34493  03-12-2019 17:30   jcr_root/etc/packages/asd/test-bundles.zip
+     8333  03-12-2019 17:09   jcr_root/etc/packages/asd/test-content.zip
+     7235  03-12-2019 17:08   jcr_root/etc/packages/asd/test-configurations.zip
+        0  03-12-2019 15:28   META-INF/maven/
+        0  03-12-2019 15:29   META-INF/maven/org.apache.sling/
+        0  02-28-2019 14:27   META-INF/maven/org.apache.sling/org.apache.sling.feature.cpconverter.all/
+     1231  03-12-2019 15:30   META-INF/maven/org.apache.sling/org.apache.sling.feature.cpconverter.all/pom.xml
+      127  03-12-2019 15:30   META-INF/maven/org.apache.sling/org.apache.sling.feature.cpconverter.all/pom.properties
+        0  03-12-2019 17:06   META-INF/vault/
+      892  03-12-2019 15:32   META-INF/vault/settings.xml
+      840  03-12-2019 15:47   META-INF/vault/properties.xml
+     3579  03-12-2019 15:33   META-INF/vault/config.xml
+      267  03-12-2019 15:50   META-INF/vault/filter.xml
+---------                     -------
+    63214                     20 files
+```
+
+Where the `test-bundles.zip` is a nested `content-package` wrapping OSGi bundles:
+
+```
+$ unzip -l test-bundles.zip 
+Archive:  test-bundles.zip
+  Length      Date    Time    Name
+---------  ---------- -----   ----
+        0  03-12-2019 17:30   META-INF/
+       69  03-12-2019 17:30   META-INF/MANIFEST.MF
+        0  03-11-2019 23:39   jcr_root/
+        0  03-11-2019 23:31   jcr_root/apps/
+        0  03-12-2019 17:26   jcr_root/apps/asd/
+        0  03-11-2019 23:32   jcr_root/apps/asd/install/
+    13288  12-06-2018 12:30   jcr_root/apps/asd/install/test-framework.jar
+        0  03-12-2019 17:16   jcr_root/apps/asd/install.publish/
+     7210  03-12-2019 17:15   jcr_root/apps/asd/install.publish/test-api.jar
+        0  03-12-2019 17:18   jcr_root/apps/asd/install.author/
+     7735  03-12-2019 17:17   jcr_root/apps/asd/install.author/test-api.jar
+        0  03-11-2019 23:42   META-INF/maven/
+        0  03-11-2019 23:43   META-INF/maven/org.apache.sling/
+        0  02-28-2019 14:26   META-INF/maven/org.apache.sling/org.apache.sling.feature.cpconverter.bundles/
+     1229  03-12-2019 10:22   META-INF/maven/org.apache.sling/org.apache.sling.feature.cpconverter.bundles/pom.xml
+      131  03-12-2019 00:26   META-INF/maven/org.apache.sling/org.apache.sling.feature.cpconverter.bundles/pom.properties
+        0  03-12-2019 12:41   META-INF/vault/
+      888  03-12-2019 00:28   META-INF/vault/settings.xml
+      954  03-12-2019 15:33   META-INF/vault/properties.xml
+     3571  03-12-2019 00:27   META-INF/vault/config.xml
+      891  03-12-2019 00:28   META-INF/vault/filter.xml
+      842  03-12-2019 00:27   META-INF/vault/filter-plugin-generated.xml
+---------                     -------
+    79844                     29 files
+```
+
+the `test-configurations.zip` contains OSGi configurations:
+
+```
+$ unzip -l test-configurations.zip 
+Archive:  test-configurations.zip
+  Length      Date    Time    Name
+---------  ---------- -----   ----
+        0  03-12-2019 17:08   META-INF/
+       69  03-12-2019 17:08   META-INF/MANIFEST.MF
+        0  03-12-2019 10:21   META-INF/maven/
+        0  03-12-2019 10:21   META-INF/maven/org.apache.sling/
+        0  02-28-2019 14:25   META-INF/maven/org.apache.sling/org.apache.sling.feature.cpconverter.config/
+     1228  03-12-2019 10:24   META-INF/maven/org.apache.sling/org.apache.sling.feature.cpconverter.config/pom.xml
+      129  03-12-2019 10:22   META-INF/maven/org.apache.sling/org.apache.sling.feature.cpconverter.config/pom.properties
+        0  03-12-2019 13:23   META-INF/vault/
+       94  02-28-2019 14:25   META-INF/vault/settings.xml
+      664  03-12-2019 15:13   META-INF/vault/properties.xml
+     3579  02-28-2019 14:25   META-INF/vault/config.xml
+      175  03-12-2019 10:37   META-INF/vault/filter.xml
+        0  02-28-2019 14:25   jcr_root/
+        0  03-12-2019 10:17   jcr_root/apps/
+        0  02-28-2019 14:25   jcr_root/apps/asd/
+        0  03-12-2019 10:17   jcr_root/apps/asd/config/
+      438  02-28-2019 14:25   jcr_root/apps/asd/config/org.apache.sling.commons.log.LogManager.factory.config-asd-retail.xml
+        0  03-12-2019 10:18   jcr_root/apps/asd/config.publish/
+      377  02-28-2019 14:25   jcr_root/apps/asd/config.publish/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended-asd-retail.xml
+      244  02-28-2019 14:25   jcr_root/apps/.content.xml
+---------                     -------
+    25441                     23 files
+```
+
+and the `test-content.zip` package includes resources of various nature:
+
+```
+$ unzip -l test-content.zip 
+Archive:  test-content.zip
+  Length      Date    Time    Name
+---------  ---------- -----   ----
+        0  03-12-2019 17:09   META-INF/
+       69  03-12-2019 17:09   META-INF/MANIFEST.MF
+        0  03-12-2019 11:31   META-INF/maven/
+        0  03-12-2019 11:31   META-INF/maven/org.apache.sling/
+        0  02-28-2019 14:26   META-INF/maven/org.apache.sling/org.apache.sling.feature.cpconverter.content/
+     1229  03-12-2019 11:32   META-INF/maven/org.apache.sling/org.apache.sling.feature.cpconverter.content/pom.xml
+      131  03-12-2019 11:32   META-INF/maven/org.apache.sling/org.apache.sling.feature.cpconverter.content/pom.properties
+        0  03-12-2019 12:40   META-INF/vault/
+      118  02-28-2019 14:26   META-INF/vault/settings.xml
+      859  03-12-2019 15:12   META-INF/vault/properties.xml
+     3571  03-12-2019 12:42   META-INF/vault/config.xml
+      895  03-12-2019 12:57   META-INF/vault/filter.xml
+       72  02-28-2019 14:26   META-INF/vault/filter-plugin-generated.xml
+        0  03-12-2019 12:30   jcr_root/
+        0  03-12-2019 12:31   jcr_root/content/
+        0  03-12-2019 12:31   jcr_root/content/asd/
+     1021  02-28-2019 14:26   jcr_root/content/asd/.content.xml
+     6924  02-28-2019 14:26   jcr_root/content/asd/resources.xml
+---------                     -------
+    39481                     22 files
+```
+
+## Mapping and the Output
+
+All metadata are mainly collected inside one or more, depending by declared run modes in the installation and configuration paths, _Feature_ model files:
+
+```json
+$ cat asd.retail.all.json 
+{
+  "id":"org.apache.sling:asd.retail.all:slingosgifeature:cp2fm-converted-feature:0.0.1",
+  "description":"Combined package for asd.Retail",
+  "bundles":[
+    {
+      "id":"org.apache.felix:org.apache.felix.framework:6.0.1",
+      "start-order":"5"
+    }
+  ],
+  "configurations":{
+    "org.apache.sling.commons.log.LogManager.factory.config-asd-retail":{
+      "org.apache.sling.commons.log.pattern":"{0,date,yyyy-MM-dd HH:mm:ss.SSS} {4} [{3}] {5}",
+      "org.apache.sling.commons.log.names":[
+        "we.retail"
+      ],
+      "org.apache.sling.commons.log.level":"info",
+      "org.apache.sling.commons.log.file":"logs/project-we-retail.log"
+    }
+  },
+  "content-packages:ARTIFACTS|true":[
+    "org.apache.sling:asd.retail.all:zip:cp2fm-converted-feature:0.0.1"
+  ]
+}
+```
+
+the `publish` run mode leads the tool to generate a separated _Apache Sling Feature_ model file:
+
+```json
+$ cat asd.retail.all-publish.json 
+{
+  "id":"org.apache.sling:asd.retail.all:slingosgifeature:cp2fm-converted-feature-publish:0.0.1",
+  "bundles":[
+    {
+      "id":"org.apache.sling:org.apache.sling.models.api:1.3.8",
+      "start-order":"5"
+    }
+  ],
+  "configurations":{
+    "org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended-asd-retail":{
+      "user.mapping":[
+        "com.asd.sample.we.retail.core:orders=[commerce-orders-service]",
+        "com.asd.sample.we.retail.core:frontend=[content-reader-service]"
+      ]
+    }
+  }
+}
+```
+
+bundles are collected in an _Apache Maven repository_ compliant directory, all other resources are collected in a new `content-package` created while scanning the packages:
+
+```
+$ tree bundles/
+bundles/
+└── org
+    └── apache
+        ├── felix
+        │   └── org.apache.felix.framework
+        │       └── 6.0.1
+        │           ├── org.apache.felix.framework-6.0.1.jar
+        │           └── org.apache.felix.framework-6.0.1.pom
+        └── sling
+            ├── asd.retail.all
+            │   └── 0.0.1
+            │       ├── asd.retail.all-0.0.1-cp2fm-converted-feature.zip
+            │       └── asd.retail.all-0.0.1.pom
+            ├── org.apache.sling.api
+            │   └── 2.20.0
+            │       ├── org.apache.sling.api-2.20.0.jar
+            │       └── org.apache.sling.api-2.20.0.pom
+            └── org.apache.sling.models.api
+                └── 1.3.8
+                    ├── org.apache.sling.models.api-1.3.8.jar
+                    └── org.apache.sling.models.api-1.3.8.pom
+
+12 directories, 8 files
+```
+
+_Apache Maven GAVs_ are extracted from nested bundles metadata and are renamed according to the _Apache Maven_ conventions.
+
+### Supported configurations
+
+All OSGi configuration formats are supported:
+
+ * _Property_ files, which extensions are `.properties` or `.cfg`, see the related [documentation](https://sling.apache.org/documentation/bundles/configuration-installer-factory.html#property-files-cfg);
+ * Configuration Files, which extension is `.config`, see the related [documentation](https://sling.apache.org/documentation/bundles/configuration-installer-factory.html#configuration-files-config);
+ * JSON format, which extension is `.cfg.json`, see the related [documentation](https://blog.osgi.org/2018/06/osgi-r7-highlights-configuration-admin.html)
+ * `sling:OsgiConfig` content nodes, typically `.xml` files.
+
+During the conversion process, all these formats will be parsed and then added in the `configuration` section of the _Sling Feature Model_ file.
+
+### Run Modes
+
+As shown above, run modes in the path lead the tool to create a dedicated _Apache Sling Feature_ model file containing all interested OSGi configurations/bundles.
+
+### Known limitations
+
+Multiple Run Modes are not supported yet.
+
+## Sample APIs
+
+```java
+import org.apache.sling.feature.cpconverter.ContentPackage2FeatureModelConverter;
+
+...
+
+new ContentPackage2FeatureModelConverter()
+            // content-package validation, when opening them
+            .setStrictValidation(strictValidation)
+            // (don't) allow different OSGi configurations file have the same PID
+            .setMergeConfigurations(mergeConfigurations)
+            // users can decide which is the bundles start order, declared in the generated Apache Sling Feature(s)
+            .setBundlesStartOrder(bundlesStartOrder)
+            // a valid directory where the outputs will be generated (it will created, if not existing already)
+            .setOutputDirectory(outputDirectory)
+            // an existing and valid content-package file
+            .convert(contentPackage);
+```
+
+### Handler Services
+
+In order to make the tool extensible, the [org.apache.sling.feature.cpconverter.spi.EntryHandler](./src/main/java/org/apache/sling/feature/cpconverter/spi/EntryHandler.java) interface is declared to handle different kind of resources, have a look at the [org.apache.sling.feature.cpconverter.handlers](src/main/java/org/apache/sling/feature/cpconverter/handlers) package to see the default implementations.
+
+If users want to handle special resource type, all they have to do is providing their `org.apache.sling.feature.cpconverter.spi.EntryHandler` service implementation and declaring them in the `META-INF/services/org.apache.sling.feature.cpconverter.spi.EntryHandler` classpath resource file, on order to let the `ServiceLoader` including it in the `content-package` scan.
+
+### Bundles deployer
+
+The [org.apache.sling.feature.cpconverter.spi.BundlesDeployer](./src/main/java/org/apache/sling/cp2fm/spi/BundlesDeployer) service is designed to let the conversion tool be integrated in external services, i.e. _Apache Maven_.
+
+The [default implementation](src/main/java/org/apache/sling/cp2fm/DefaultBundlesDeployer.java) just copies bundles in the target output directory, according to the _Apache Maven_ repository layout.
+
+## The CLI Tool
+
+The tool is distributed with a commodity package containing all is needed in order to launch the `ContentPackage2FeatureModelConverter` form the shell:
+
+```
+$ unzip -l org.apache.sling.feature.cpconverter-0.0.1-SNAPSHOT.zip 
+Archive:  org.apache.sling.feature.cpconverter-0.0.1-SNAPSHOT.zip
+  Length      Date    Time    Name
+---------  ---------- -----   ----
+        0  03-13-2019 15:58   org.apache.sling.feature.cpconverter-0.0.1-SNAPSHOT/
+        0  03-13-2019 15:58   org.apache.sling.feature.cpconverter-0.0.1-SNAPSHOT/bin/
+        0  03-13-2019 15:58   org.apache.sling.feature.cpconverter-0.0.1-SNAPSHOT/lib/
+     4605  02-27-2019 16:30   org.apache.sling.feature.cpconverter-0.0.1-SNAPSHOT/README.md
+   801904  02-28-2019 14:55   org.apache.sling.feature.cpconverter-0.0.1-SNAPSHOT/lib/jackrabbit-spi-commons-2.19.1.jar
+    14744  02-11-2019 15:44   org.apache.sling.feature.cpconverter-0.0.1-SNAPSHOT/lib/osgi.annotation-6.0.1.jar
+    35919  02-11-2019 15:44   org.apache.sling.feature.cpconverter-0.0.1-SNAPSHOT/lib/org.osgi.service.component.annotations-1.3.0.jar
+    23575  02-11-2019 15:44   org.apache.sling.feature.cpconverter-0.0.1-SNAPSHOT/lib/org.osgi.service.metatype.annotations-1.3.0.jar
+    34518  02-27-2019 15:28   org.apache.sling.feature.cpconverter-0.0.1-SNAPSHOT/lib/org.apache.felix.scr.annotations-1.11.0.jar
+    45199  03-13-2019 15:58   org.apache.sling.feature.cpconverter-0.0.1-SNAPSHOT/lib/org.apache.sling.feature.cpconverter-0.0.1-SNAPSHOT.jar
+    17489  03-13-2019 15:58   org.apache.sling.feature.cpconverter-0.0.1-SNAPSHOT/LICENSE
+   588337  02-11-2019 12:49   org.apache.sling.feature.cpconverter-0.0.1-SNAPSHOT/lib/commons-collections-3.2.2.jar
+   108555  02-11-2019 15:45   org.apache.sling.feature.cpconverter-0.0.1-SNAPSHOT/lib/xz-1.8.jar
+    52873  03-05-2019 17:31   org.apache.sling.feature.cpconverter-0.0.1-SNAPSHOT/lib/plexus-classworlds-2.6.0.jar
+   165965  03-05-2019 18:02   org.apache.sling.feature.cpconverter-0.0.1-SNAPSHOT/lib/maven-model-3.6.0.jar
+      178  02-27-2019 15:56   org.apache.sling.feature.cpconverter-0.0.1-SNAPSHOT/NOTICE
+   745712  02-28-2019 10:02   org.apache.sling.feature.cpconverter-0.0.1-SNAPSHOT/lib/org.apache.jackrabbit.vault-3.2.6.jar
+  2374421  02-27-2019 15:28   org.apache.sling.feature.cpconverter-0.0.1-SNAPSHOT/lib/biz.aQute.bndlib-3.2.0.jar
+     3263  03-13-2019 15:58   org.apache.sling.feature.cpconverter-0.0.1-SNAPSHOT/bin/cp2sf.bat
+    69246  02-11-2019 12:49   org.apache.sling.feature.cpconverter-0.0.1-SNAPSHOT/lib/jcr-2.0.jar
+   113508  02-11-2019 12:36   org.apache.sling.feature.cpconverter-0.0.1-SNAPSHOT/lib/org.apache.felix.converter-1.0.0.jar
+    12548  02-11-2019 12:36   org.apache.sling.feature.cpconverter-0.0.1-SNAPSHOT/lib/org.osgi.util.function-1.0.0.jar
+   176142  02-11-2019 12:35   org.apache.sling.feature.cpconverter-0.0.1-SNAPSHOT/lib/org.apache.felix.utils-1.11.0.jar
+   155618  03-04-2019 00:12   org.apache.sling.feature.cpconverter-0.0.1-SNAPSHOT/lib/org.apache.felix.configadmin-1.9.12.jar
+    75443  03-05-2019 14:58   org.apache.sling.feature.cpconverter-0.0.1-SNAPSHOT/lib/plexus-io-3.1.1.jar
+    57954  02-11-2019 12:39   org.apache.sling.feature.cpconverter-0.0.1-SNAPSHOT/lib/snappy-0.4.jar
+   148098  02-11-2019 12:39   org.apache.sling.feature.cpconverter-0.0.1-SNAPSHOT/lib/xbean-reflect-3.7.jar
+     3808  03-13-2019 15:58   org.apache.sling.feature.cpconverter-0.0.1-SNAPSHOT/bin/cp2sf
+   214788  02-11-2019 15:44   org.apache.sling.feature.cpconverter-0.0.1-SNAPSHOT/lib/commons-io-2.6.jar
+    26081  02-11-2019 12:36   org.apache.sling.feature.cpconverter-0.0.1-SNAPSHOT/lib/geronimo-json_1.0_spec-1.0-alpha-1.jar
+    90358  02-11-2019 12:35   org.apache.sling.feature.cpconverter-0.0.1-SNAPSHOT/lib/johnzon-core-1.0.0.jar
+    14769  02-11-2019 12:35   org.apache.sling.feature.cpconverter-0.0.1-SNAPSHOT/lib/org.osgi.annotation.versioning-1.0.0.jar
+   475256  02-11-2019 12:35   org.apache.sling.feature.cpconverter-0.0.1-SNAPSHOT/lib/osgi.core-6.0.0.jar
+    28688  02-11-2019 12:48   org.apache.sling.feature.cpconverter-0.0.1-SNAPSHOT/lib/slf4j-api-1.7.6.jar
+    28561  02-28-2019 14:55   org.apache.sling.feature.cpconverter-0.0.1-SNAPSHOT/lib/jackrabbit-spi-2.19.1.jar
+   403186  02-28-2019 14:55   org.apache.sling.feature.cpconverter-0.0.1-SNAPSHOT/lib/jackrabbit-jcr-commons-2.19.1.jar
+    49017  03-04-2019 15:12   org.apache.sling.feature.cpconverter-0.0.1-SNAPSHOT/lib/jackrabbit-api-2.19.1.jar
+   260371  03-05-2019 14:58   org.apache.sling.feature.cpconverter-0.0.1-SNAPSHOT/lib/plexus-utils-3.1.1.jar
+   639592  02-11-2019 12:39   org.apache.sling.feature.cpconverter-0.0.1-SNAPSHOT/lib/google-collections-1.0.jar
+    10684  02-11-2019 12:48   org.apache.sling.feature.cpconverter-0.0.1-SNAPSHOT/lib/slf4j-simple-1.7.6.jar
+   164159  02-11-2019 12:48   org.apache.sling.feature.cpconverter-0.0.1-SNAPSHOT/lib/org.apache.sling.feature.io-1.0.0.jar
+   289040  02-11-2019 12:36   org.apache.sling.feature.cpconverter-0.0.1-SNAPSHOT/lib/org.apache.felix.configurator-1.0.4.jar
+   591748  02-11-2019 15:45   org.apache.sling.feature.cpconverter-0.0.1-SNAPSHOT/lib/commons-compress-1.18.jar
+   242435  02-27-2019 15:58   org.apache.sling.feature.cpconverter-0.0.1-SNAPSHOT/lib/picocli-3.6.0.jar
+   115238  02-11-2019 12:48   org.apache.sling.feature.cpconverter-0.0.1-SNAPSHOT/lib/org.apache.sling.feature-1.0.0.jar
+    18587  02-11-2019 15:46   org.apache.sling.feature.cpconverter-0.0.1-SNAPSHOT/lib/annotations-16.0.3.jar
+   191914  03-05-2019 14:58   org.apache.sling.feature.cpconverter-0.0.1-SNAPSHOT/lib/plexus-archiver-4.1.0.jar
+   229982  03-05-2019 17:31   org.apache.sling.feature.cpconverter-0.0.1-SNAPSHOT/lib/plexus-container-default-2.0.0.jar
+---------                     -------
+  9914076                     48 files
+```
+
+once the package is decompressed, open the shell and type:
+
+```
+$ ./bin/cp2sf -h
+Usage: cp2fm [-hmqsvX] [-b=<bundlesStartOrder>] -c=<contentPackage>
+             -o=<outputDirectory> [-f=<filteringPatterns>]...
+Apache Sling Content Package to Sling Feature converter
+  -b, --bundles-start-order=<bundlesStartOrder>
+                            The order to start detected bundles.
+  -c, --content-package=<contentPackage>
+                            The content-package input file.
+  -f, --filtering-patterns=<filteringPatterns>
+                            Regex based pattern(s) to reject content-package archive
+                              entries.
+  -h, --help                Display the usage message.
+  -m, --merge-configurations
+                            Flag to mark OSGi configurations with same PID will be
+                              merged, the tool will fail otherwise.
+  -o, --output-directory=<outputDirectory>
+                            The output directory where the Feature File and the
+                              bundles will be deployed.
+  -q, --quiet               Log errors only.
+  -s, --strict-validation   Flag to mark the content-package input file being strict
+                              validated.
+  -v, --version             Display version information.
+  -X, --verbose             Produce execution debug output.
+Copyright(c) 2019 The Apache Software Foundation.
+```
+
+to see all the available options; a sample execution could look like:
+
+```
+$ ./bin/cp2sf -v -b 20 -c /content-package-2-feature-model/src/test/resources/org/apache/sling/cp2fm/test-content-package.zip -o /tmp
+```
+
+Argument Files for Long Command Lines:
+
+```
+# argfile
+# comments are supported
+
+-v
+-b 20
+-c /content-package-2-feature-model/src/test/resources/org/apache/sling/cp2fm/test-content-package.zip
+-o /tmp
+```
+
+then execute the command
+
+```
+$ ./bin/cp2sf @arfile
+````
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..b7f697c
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,322 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+ 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>
+
+  <parent>
+    <groupId>org.apache.sling</groupId>
+    <artifactId>sling</artifactId>
+    <version>34</version>
+    <relativePath />
+  </parent>
+
+  <artifactId>org.apache.sling.feature.cpconverter</artifactId>
+  <version>0.0.1-SNAPSHOT</version>
+
+  <name>Apache Sling Content Package to Feature Model converter</name>
+  <description>Content Package to Feature Model converter tools for Apache Sling</description>
+
+  <properties>
+    <sling.java.version>8</sling.java.version>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+  </properties>
+
+  <dependencies>
+    <!--
+     | Logging
+    -->
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-api</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-simple</artifactId>
+      <scope>provided</scope>
+    </dependency>
+
+    <!--
+     | CLI
+    -->
+    <dependency>
+      <groupId>info.picocli</groupId>
+      <artifactId>picocli</artifactId>
+      <version>3.6.0</version>
+      <scope>provided</scope>
+    </dependency>
+
+    <!--
+     | Content-Package
+    -->
+    <dependency>
+      <groupId>org.apache.jackrabbit.vault</groupId>
+      <artifactId>org.apache.jackrabbit.vault</artifactId>
+      <version>3.2.6</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.jackrabbit</groupId>
+      <artifactId>jackrabbit-spi-commons</artifactId>
+      <version>2.19.1</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>javax.jcr</groupId>
+      <artifactId>jcr</artifactId>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>commons-io</groupId>
+      <artifactId>commons-io</artifactId>
+      <version>2.6</version>
+      <scope>provided</scope>
+    </dependency>
+
+    <!--
+     | Sling Feature Model libraries
+    -->
+    <dependency>
+      <groupId>org.apache.sling</groupId>
+      <artifactId>org.apache.sling.feature.io</artifactId>
+      <version>1.0.0</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.felix</groupId>
+      <artifactId>org.apache.felix.converter</artifactId>
+      <version>1.0.0</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.felix</groupId>
+      <artifactId>org.apache.felix.configurator</artifactId>
+      <version>1.0.4</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.felix</groupId>
+      <artifactId>org.apache.felix.utils</artifactId>
+      <version>1.11.0</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.geronimo.specs</groupId>
+      <artifactId>geronimo-json_1.0_spec</artifactId>
+      <version>1.0-alpha-1</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.johnzon</groupId>
+      <artifactId>johnzon-core</artifactId>
+      <version>1.0.0</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.sling</groupId>
+      <artifactId>org.apache.sling.feature</artifactId>
+      <version>1.0.0</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.osgi</groupId>
+      <artifactId>org.osgi.annotation.versioning</artifactId>
+      <version>1.0.0</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.osgi</groupId>
+      <artifactId>osgi.core</artifactId>
+      <scope>provided</scope>
+    </dependency>
+
+    <!--
+     | Handle .config files
+    -->
+    <dependency>
+      <groupId>org.apache.felix</groupId>
+      <artifactId>org.apache.felix.configadmin</artifactId>
+      <version>1.9.12</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.jackrabbit</groupId>
+      <artifactId>jackrabbit-api</artifactId>
+      <version>2.19.1</version>
+      <scope>provided</scope>
+    </dependency>
+
+    <!--
+     | repackage
+    -->
+    <dependency>
+      <groupId>org.codehaus.plexus</groupId>
+      <artifactId>plexus-archiver</artifactId>
+      <version>4.1.0</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.codehaus.plexus</groupId>
+      <artifactId>plexus-utils</artifactId>
+      <version>3.1.1</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.codehaus.plexus</groupId>
+      <artifactId>plexus-io</artifactId>
+      <version>3.1.1</version>
+      <scope>provided</scope>
+    </dependency>
+    <!--
+     | avoid classloading issue
+    -->
+    <dependency>
+      <groupId>org.codehaus.plexus</groupId>
+      <artifactId>plexus-container-default</artifactId>
+      <version>2.0.0</version>
+      <scope>provided</scope>
+    </dependency>
+
+    <!--
+     | POM model
+    -->
+    <dependency>
+      <groupId>org.apache.maven</groupId>
+      <artifactId>maven-model</artifactId>
+      <version>3.6.0</version>
+      <scope>provided</scope>
+    </dependency>
+
+    <!--
+     | Test only dependencies
+    -->
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-core</artifactId>
+      <version>2.25.0</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.rat</groupId>
+        <artifactId>apache-rat-plugin</artifactId>
+          <configuration>
+          <excludes>
+            <exclude>src/main/resources/META-INF/services/org.apache.sling.feature.cpconverter.spi.EntryHandler</exclude>
+            <exclude>src/main/legal/NOTICE-with-deps</exclude>
+          </excludes>
+        </configuration>
+      </plugin>
+
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-surefire-plugin</artifactId>
+        <configuration>
+          <systemPropertyVariables>
+            <testDirectory>${project.build.directory}/unit-tests</testDirectory>
+          </systemPropertyVariables>
+        </configuration>
+      </plugin>
+
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>license-maven-plugin</artifactId>
+        <version>1.16</version>
+        <executions>
+          <execution>
+            <id>add-third-party</id>
+            <phase>generate-resources</phase>
+            <goals>
+              <goal>add-third-party</goal>
+            </goals>
+            <configuration>
+              <fileTemplate>${basedir}/src/main/legal/LICENSE-with-deps</fileTemplate>
+              <thirdPartyFilename>LICENSE-with-deps</thirdPartyFilename>
+              <sortArtifactByName>true</sortArtifactByName>
+              <excludedScopes>test</excludedScopes>
+              <licenseMerges>
+                <licenseMerge>The Apache Software License, Version 2.0|Apache License, Version 2.0|Apache Public License 2.0</licenseMerge>
+              </licenseMerges>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>appassembler-maven-plugin</artifactId>
+        <version>2.0.0</version>
+        <executions>
+          <execution>
+            <phase>package</phase>
+            <goals>
+              <goal>assemble</goal>
+            </goals>
+          </execution>
+        </executions>
+        <configuration>
+          <repositoryLayout>flat</repositoryLayout>
+          <repositoryName>lib</repositoryName>
+          <useWildcardClassPath>true</useWildcardClassPath>
+          <extraJvmArguments>-Dproject.artifactId=${project.artifactId} -Dproject.version=${project.version} -Dbuild.timestamp=${maven.build.timestamp} -Duser.timezone=UTC -Dfile.encoding=UTF-8</extraJvmArguments>
+          <defaultJvmSettings>-Xms500m -Xmx500m -XX:PermSize=128m -XX:-UseGCOverheadLimit</defaultJvmSettings>
+          <licenseHeaderFile>${basedir}/src/main/legal/license-header</licenseHeaderFile>
+          <programs>
+            <program>
+              <mainClass>org.apache.sling.feature.cpconverter.cli.ContentPackage2FeatureModelConverterLauncher</mainClass>
+              <name>cp2sf</name>
+            </program>
+          </programs>
+        </configuration>
+      </plugin>
+
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-assembly-plugin</artifactId>
+        <version>3.1.0</version>
+        <executions>
+          <execution>
+            <id>assembly</id>
+            <phase>package</phase>
+            <goals>
+              <goal>single</goal>
+            </goals>
+          </execution>
+        </executions>
+        <configuration>
+          <finalName>${project.build.finalName}</finalName>
+          <tarLongFileMode>gnu</tarLongFileMode>
+          <appendAssemblyId>false</appendAssemblyId>
+          <descriptors>
+            <descriptor>${basedir}/src/main/assembly/bin.xml</descriptor>
+          </descriptors>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+
+</project>
diff --git a/src/main/assembly/README.md b/src/main/assembly/README.md
new file mode 100644
index 0000000..4f743d5
--- /dev/null
+++ b/src/main/assembly/README.md
@@ -0,0 +1,149 @@
+```
+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.name} - ${project.inceptionYear}
+
+## What is it?
+
+  ${project.description}
+
+## Licensing
+
+  Please see the files called LICENSE and NOTICE
+
+## Documentation
+
+  The most up-to-date documentation can be found at ${project.url}.
+
+## Useful URLs
+
+  Home Page:          ${project.url}/
+  Source Code:        ${project.scm.url}
+  Issue Tracking:     ${project.issueManagement.url}
+
+## System Requirements
+
+### JDK
+
+    ${sling.java.version} or above. (see http://www.oracle.com/technetwork/java/)
+
+### Memory
+
+    No minimum requirement.
+
+### Disk
+
+    No minimum requirement.
+
+###  Operating System
+
+    No minimum requirement. On Windows, Windows NT and above or Cygwin is required for
+    the startup scripts. Tested on Windows XP, Fedora Core and Mac OS X.
+
+---
+
+## Installation
+
+### Windows 2000/XP
+
+  1) Unzip the distribution archive, i.e. `${project.build.finalName}.zip` to the directory you wish to install `${project.name} ${project.version}`.
+These instructions assume you chose `C:\Program Files`.
+The subdirectory `${project.build.finalName}` will be created from the archive.
+
+  2) Add the `SFA_HOME` environment variable by opening up the system properties (WinKey + Pause), selecting the "Advanced" tab, and the "Environment Variables" button, then adding the `SFA_HOME` variable in the user variables with the value `C:\Program Files\${project.build.finalName}`.
+
+  3) In the same dialog, add the SFA environment variable in the user variables with the value `%SFA_HOME%\bin`.
+
+  4) In the same dialog, update/create the _Path_ environment variable in the user variables and prepend the value `%SFA%` to add `${project.name}` available in the command line.
+
+  5) In the same dialog, make sure that `JAVA_HOME` exists in your user variables or in the system variables and it is set to the location of your JDK, e.g. `C:\Program Files\Java\1.8.0_152` and that `%JAVA_HOME%\bin` is in your _Path_ environment variable.
+
+  6) Open a new command prompt (Winkey + R then type cmd) and run `sfa --version` to verify that it is correctly installed.
+
+## Unix-based Operating Systems (Linux, Solaris and Mac OS X)
+
+  1) Extract the distribution archive, i.e. `${project.build.finalName}.tar.gz` to the directory you wish to install `${project.name} ${project.version}`.
+These instructions assume you chose `/usr/local`.
+The subdirectory `${project.build.finalName}` will be created from the archive.
+
+  2) In a command terminal, add the `SFA_HOME` environment variable, e.g.
+        `export SFA_HOME=/usr/local/${project.build.finalName}`.
+
+  3) Add the `SFA` environment variable, e.g. `export SFA=$SFA_HOME/bin`.
+
+  4) Add `SFA` environment variable to your path, e.g. `export PATH=$SFA:$PATH`.
+
+  5) Make sure that `JAVA_HOME` is set to the location of your JDK, e.g. `export JAVA_HOME=/usr/java/1.8.0_152` and that `$JAVA_HOME/bin` is in your `PATH` environment variable.
+
+  6) Run `sfa --version` to verify that it is correctly installed.
+
+---
+
+## Execution
+
+  Open the shell and type `cp2sf -h` to see the available commands:
+
+```
+$ ./cp2sf -h
+Usage: cp2fm [-hmqsvX] [-b=<bundlesStartOrder>] -c=<contentPackage>
+             -o=<outputDirectory> [-f=<filteringPatterns>]...
+Apache Sling Content Package to Sling Feature converter
+  -b, --bundles-start-order=<bundlesStartOrder>
+                            The order to start detected bundles.
+  -c, --content-package=<contentPackage>
+                            The content-package input file.
+  -f, --filtering-patterns=<filteringPatterns>
+                            Regex based pattern(s) to reject content-package archive
+                              entries.
+  -h, --help                Display the usage message.
+  -m, --merge-configurations
+                            Flag to mark OSGi configurations with same PID will be
+                              merged, the tool will fail otherwise.
+  -o, --output-directory=<outputDirectory>
+                            The output directory where the Feature File and the
+                              bundles will be deployed.
+  -q, --quiet               Log errors only.
+  -s, --strict-validation   Flag to mark the content-package input file being strict
+                              validated.
+  -v, --version             Display version information.
+  -X, --verbose             Produce execution debug output.
+Copyright(c) 2019 The Apache Software Foundation.
+```
+
+a sample execution could look like:
+
+```
+$ ./bin/cp2sf -v -b 20 -c /content-package-2-feature-model/src/test/resources/org/apache/sling/cp2fm/test-content-package.zip -o /tmp
+```
+
+### Argument Files for Long Command Lines
+
+```
+# argfile
+# comments are supported
+
+-v
+-b 20
+-c /content-package-2-feature-model/src/test/resources/org/apache/sling/cp2fm/test-content-package.zip
+-o /tmp
+```
+
+then execute the command
+
+```
+$ ./bin/cp2sf @arfile
+````
diff --git a/src/main/assembly/bin.xml b/src/main/assembly/bin.xml
new file mode 100644
index 0000000..482f4cd
--- /dev/null
+++ b/src/main/assembly/bin.xml
@@ -0,0 +1,71 @@
+<?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.
+-->
+<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.1 http://maven.apache.org/xsd/assembly-1.1.1.xsd">
+
+  <id>${project.build.finalName}</id>
+  <formats>
+    <format>tar.gz</format>
+    <format>zip</format>
+  </formats>
+  <includeBaseDirectory>true</includeBaseDirectory>
+  <baseDirectory>${project.build.finalName}</baseDirectory>
+
+  <files>
+    <file>
+      <source>${basedir}/src/main/assembly/README.md</source>
+      <filtered>true</filtered>
+      <outputDirectory>/</outputDirectory>
+      <fileMode>666</fileMode>
+    </file>
+
+    <!--
+     | Use the N&L files which apply to the included dependencies
+    -->
+    <file>
+      <source>${project.build.directory}/generated-sources/license/LICENSE-with-deps</source>
+      <destName>LICENSE</destName>
+    </file>
+    <file>
+      <source>${basedir}/src/main/legal/NOTICE-with-deps</source>
+      <destName>NOTICE</destName>
+    </file>
+  </files>
+
+  <fileSets>
+    <!--
+     | shell scripts
+    -->
+    <fileSet>
+      <directory>${project.build.directory}/appassembler/bin/</directory>
+      <outputDirectory>/bin</outputDirectory>
+      <fileMode>755</fileMode>
+      <directoryMode>755</directoryMode>
+    </fileSet>
+  </fileSets>
+
+  <dependencySets>
+    <dependencySet>
+      <outputDirectory>/lib</outputDirectory>
+      <scope>provided</scope>
+      <useTransitiveDependencies>true</useTransitiveDependencies>
+      <useProjectArtifact>true</useProjectArtifact>
+    </dependencySet>
+  </dependencySets>
+
+</assembly>
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java b/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
new file mode 100644
index 0000000..32c0197
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
@@ -0,0 +1,431 @@
+/*
+ * 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.feature.cpconverter;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Objects;
+import java.util.ServiceLoader;
+import java.util.Set;
+
+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.VaultPackage;
+import org.apache.jackrabbit.vault.packaging.impl.PackageManagerImpl;
+import org.apache.sling.feature.Artifact;
+import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.Configuration;
+import org.apache.sling.feature.Extension;
+import org.apache.sling.feature.ExtensionType;
+import org.apache.sling.feature.Extensions;
+import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.cpconverter.spi.BundlesDeployer;
+import org.apache.sling.feature.cpconverter.spi.EntryHandler;
+import org.apache.sling.feature.cpconverter.vltpkg.VaultPackageAssembler;
+import org.apache.sling.feature.cpconverter.writers.FileArtifactWriter;
+import org.apache.sling.feature.cpconverter.writers.MavenPomSupplierWriter;
+import org.apache.sling.feature.io.json.FeatureJSONWriter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ContentPackage2FeatureModelConverter {
+
+    private static final String CONTENT_PACKAGES = "content-packages";
+
+    public static final String POM_TYPE = "pom";
+
+    public static final String ZIP_TYPE = "zip";
+
+    public static final String NAME_GROUP_ID = "groupId";
+
+    public static final String NAME_ARTIFACT_ID = "artifactId";
+
+    public static final String FEATURE_CLASSIFIER = "cp2fm-converted-feature";
+
+    private static final String SLING_OSGI_FEATURE_TILE_TYPE = "slingosgifeature";
+
+    private static final String JSON_FILE_EXTENSION = ".json";
+
+    private final Logger logger = LoggerFactory.getLogger(getClass());
+
+    private final PackageManager packageManager = new PackageManagerImpl();
+
+    private final ServiceLoader<EntryHandler> entryHandlers = ServiceLoader.load(EntryHandler.class);
+
+    private final Map<String, Feature> runModes = new HashMap<>();
+
+    private final Set<String> dependencies = new HashSet<>();
+
+    private final RegexBasedResourceFilter filter = new RegexBasedResourceFilter();
+
+    private BundlesDeployer artifactDeployer;
+
+    private boolean strictValidation = false;
+
+    private boolean mergeConfigurations = false;
+
+    private int bundlesStartOrder = 0;
+
+    private File outputDirectory;
+
+    private Feature targetFeature = null;
+
+    private VaultPackageAssembler mainPackageAssembler = null;
+
+    public ContentPackage2FeatureModelConverter setStrictValidation(boolean strictValidation) {
+        this.strictValidation = strictValidation;
+        return this;
+    }
+
+    public boolean isStrictValidation() {
+        return strictValidation;
+    }
+
+    public boolean isMergeConfigurations() {
+        return mergeConfigurations;
+    }
+
+    public ContentPackage2FeatureModelConverter setMergeConfigurations(boolean mergeConfigurations) {
+        this.mergeConfigurations = mergeConfigurations;
+        return this;
+    }
+
+    public ContentPackage2FeatureModelConverter setBundlesStartOrder(int bundlesStartOrder) {
+        this.bundlesStartOrder = bundlesStartOrder;
+        return this;
+    }
+
+    public ContentPackage2FeatureModelConverter setOutputDirectory(File outputDirectory) {
+        this.outputDirectory = outputDirectory;
+        return this;
+    }
+
+    public File getOutputDirectory() {
+        return outputDirectory;
+    }
+
+    public Feature getTargetFeature() {
+        return targetFeature;
+    }
+
+    public void addFilteringPattern(String filteringPattern) {
+        Objects.requireNonNull(filteringPattern, "Null pattern to filter resources out is not a valid filtering pattern");
+        if (filteringPattern.isEmpty()) {
+            throw new IllegalArgumentException("Empty pattern to filter resources out is not a valid filtering pattern");
+        }
+
+        filter.addFilteringPattern(filteringPattern);
+    }
+
+    public Feature getRunMode(String runMode) {
+        if (getTargetFeature() == null) {
+            throw new IllegalStateException("Target Feature not initialized yet, please make sure convert() method was invoked first.");
+        }
+
+        if (runMode == null) {
+            return getTargetFeature();
+        }
+
+        ArtifactId id = getTargetFeature().getId();
+
+        return runModes.computeIfAbsent(runMode, k -> new Feature(new ArtifactId(id.getGroupId(),
+                                                                                 id.getArtifactId(),
+                                                                                 id.getVersion(),
+                                                                                 id.getClassifier() + '-' + runMode,
+                                                                                 id.getType())));
+    }
+
+    public BundlesDeployer getArtifactDeployer() {
+        return artifactDeployer;
+    }
+
+    public void convert(File contentPackage) throws Exception {
+        Objects.requireNonNull(contentPackage , "Null content-package can not be converted.");
+
+        if (!contentPackage.exists() || !contentPackage.isFile()) {
+            throw new IllegalArgumentException("Content-package "
+                                            + contentPackage
+                                            + " does not exist or it is not a valid file.");
+        }
+
+        if (outputDirectory == null) {
+            throw new IllegalStateException("Null output directory not supported, it must be set before invoking the convert(File) method.");
+        }
+
+        Iterator<BundlesDeployer> artifactDeployerLoader = ServiceLoader.load(BundlesDeployer.class).iterator();
+        if (!artifactDeployerLoader.hasNext()) {
+            artifactDeployer = new DefaultBundlesDeployer(outputDirectory);
+        } else {
+            artifactDeployer = artifactDeployerLoader.next();
+        }
+
+        if (!outputDirectory.exists() && !outputDirectory.mkdirs()) {
+            throw new IllegalStateException("output directory "
+                                            + outputDirectory
+                                            + " does not exist and can not be created, please make sure current user '"
+                                            + System.getProperty("user.name")
+                                            + " has enough rights to write on the File System.");
+        }
+
+        logger.info("Reading content-package '{}'...", contentPackage);
+
+        try (VaultPackage vaultPackage = packageManager.open(contentPackage, strictValidation)) {
+            logger.info("content-package '{}' successfully read!", contentPackage);
+
+            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);
+
+            targetFeature = new Feature(new ArtifactId(groupId,
+                                                       artifactId,
+                                                       version,
+                                                       FEATURE_CLASSIFIER,
+                                                       SLING_OSGI_FEATURE_TILE_TYPE));
+
+            targetFeature.setDescription(packageProperties.getDescription());
+
+            logger.info("Converting content-package '{}' to Feature File '{}'...", vaultPackage.getId(), targetFeature.getId());
+
+            process(vaultPackage);
+
+            // attach all unmatched resources as new content-package
+
+            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)
+
+            seralize(targetFeature);
+
+            if (!runModes.isEmpty()) {
+                for (Feature runMode : runModes.values()) {
+                    seralize(runMode);
+                }
+            }
+        }
+    }
+
+    public void addConfiguration(String runMode, String pid, Dictionary<String, Object> configurationProperties) {
+        if (!mergeConfigurations) {
+            checkConfigurationExist(getTargetFeature(), pid);
+
+            for (Feature runModeFeature : runModes.values()) {
+                checkConfigurationExist(runModeFeature, pid);
+            }
+        }
+
+        Feature feature = getRunMode(runMode);
+        Configuration configuration = feature.getConfigurations().getConfiguration(pid);
+
+        if (configuration == null) {
+            configuration = new Configuration(pid);
+            feature.getConfigurations().add(configuration);
+        }
+
+        Enumeration<String> keys = configurationProperties.keys();
+        while (keys.hasMoreElements()) {
+            String key = keys.nextElement();
+            Object value = configurationProperties.get(key);
+            configuration.getProperties().put(key, value);
+        }
+    }
+
+    private static void checkConfigurationExist(Feature feature, String pid) {
+        if (feature != null) {
+            if (feature.getConfigurations().getConfiguration(pid) != null) {
+                throw new IllegalStateException("Cinfiguration '"
+                                                + pid
+                                                + "' already defined in Feature Model '"
+                                                + feature.getId().toMvnId()
+                                                + "', can not be added");
+            }
+        }
+    }
+
+    private void seralize(Feature feature) throws Exception {
+        StringBuilder fileName = new StringBuilder().append(feature.getId().getArtifactId());
+
+        if (!FEATURE_CLASSIFIER.equals(feature.getId().getClassifier())) {
+            fileName.append(feature.getId().getClassifier().substring(FEATURE_CLASSIFIER.length()));
+        }
+
+        fileName.append(JSON_FILE_EXTENSION);
+
+        File targetFile = new File(outputDirectory, fileName.toString());
+
+        logger.info("Conversion complete!", targetFile);
+        logger.info("Writing resulting Feature File to '{}'...", targetFile);
+
+        try (FileWriter targetWriter = new FileWriter(targetFile)) {
+            FeatureJSONWriter.write(targetWriter, feature);
+
+            logger.info("'{}' Feature File successfully written!", targetFile);
+        }
+    }
+
+    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);
+        }
+    }
+
+    private void process(VaultPackage vaultPackage) throws Exception {
+        Objects.requireNonNull(vaultPackage, "Impossible to process a null vault package");
+
+        if (getTargetFeature() == null) {
+            throw new IllegalStateException("Target Feature not initialized yet, please make sure convert() method was invoked first.");
+        }
+
+        dependencies.remove(vaultPackage.getId().toString());
+
+        for (Dependency dependency : vaultPackage.getDependencies()) {
+            dependencies.add(dependency.toString());
+        }
+
+        Archive archive = vaultPackage.getArchive();
+        try {
+            archive.open(strictValidation);
+
+            Entry jcrRoot = archive.getJcrRoot();
+            traverse(null, archive, jcrRoot);
+        } finally {
+            archive.close();
+        }
+    }
+
+    private void traverse(String path, Archive archive, Entry entry) throws Exception {
+        String entryPath = newPath(path, entry.getName());
+
+        if (entry.isDirectory()) {
+            for (Entry child : entry.getChildren()) {
+                traverse(entryPath, archive, child);
+            }
+
+            return;
+        }
+
+        logger.info("Processing entry {}...", entryPath);
+
+        if (filter.isFilteredOut(entryPath)) {
+            throw new IllegalArgumentException("Path '"
+                                               + entryPath
+                                               + "' in archive "
+                                               + archive.getMetaInf().getProperties()
+                                               + " not allowed by user configuration, please check configured filtering patterns");
+        }
+
+        getEntryHandlerByEntryPath(entryPath).handle(entryPath, archive, entry, this);
+
+        logger.info("Entry {} successfully processed.", entryPath);
+    }
+
+    private static String newPath(String path, String entryName) {
+        if (path == null) {
+            return entryName;
+        }
+
+        return path + '/' + entryName;
+    }
+
+    private EntryHandler getEntryHandlerByEntryPath(String path) {
+        Iterator<EntryHandler> entryHandlersIterator = entryHandlers.iterator();
+        while (entryHandlersIterator.hasNext()) {
+            EntryHandler entryHandler = entryHandlersIterator.next();
+
+            if (entryHandler.matches(path)) {
+                return entryHandler;
+            }
+        }
+
+        return mainPackageAssembler;
+    }
+
+    public void attach(String runMode,
+                       String groupId,
+                       String artifactId,
+                       String version,
+                       String classifier,
+                       String type) {
+        Objects.requireNonNull(groupId, "Artifact can not be attached to a feature without specifying a valid 'groupId'.");
+        Objects.requireNonNull(artifactId, "Artifact can not be attached to a feature without specifying a valid 'artifactId'.");
+        Objects.requireNonNull(version, "Artifact can not be attached to a feature without specifying a valid 'version'.");
+        Objects.requireNonNull(type, "Artifact can not be attached to a feature without specifying a valid 'type'.");
+
+        Artifact artifact = new Artifact(new ArtifactId(groupId, artifactId, version, classifier, type));
+
+        Feature targetFeature = getRunMode(runMode);
+
+        if (ZIP_TYPE.equals(type) ) {
+            Extensions extensions = targetFeature.getExtensions();
+            Extension extension = extensions.getByName(CONTENT_PACKAGES);
+
+            if (extension == null) {
+                extension = new Extension(ExtensionType.ARTIFACTS, CONTENT_PACKAGES, true);
+                extensions.add(extension);
+            }
+
+            extension.getArtifacts().add(artifact);
+        } else {
+            artifact.setStartOrder(bundlesStartOrder);
+            targetFeature.getBundles().add(artifact);
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/DefaultBundlesDeployer.java b/src/main/java/org/apache/sling/feature/cpconverter/DefaultBundlesDeployer.java
new file mode 100644
index 0000000..780aed8
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/cpconverter/DefaultBundlesDeployer.java
@@ -0,0 +1,99 @@
+/*
+ * 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.feature.cpconverter;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Objects;
+import java.util.StringTokenizer;
+
+import org.apache.sling.feature.cpconverter.spi.ArtifactWriter;
+import org.apache.sling.feature.cpconverter.spi.BundlesDeployer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public final class DefaultBundlesDeployer implements BundlesDeployer {
+
+    private static final String BUNDLES_RIRECTORY_NAME = "bundles";
+
+    private final Logger logger = LoggerFactory.getLogger(getClass());
+
+    private final File artifactsDirectory;
+
+    public DefaultBundlesDeployer(File outputDirectory) {
+        artifactsDirectory = new File(outputDirectory, BUNDLES_RIRECTORY_NAME);
+        if (!artifactsDirectory.exists()) {
+            artifactsDirectory.mkdirs();
+        }
+    }
+
+    @Override
+    public File getBundlesDirectory() {
+        return artifactsDirectory;
+    }
+
+    @Override
+    public void deploy(ArtifactWriter artifactWriter,
+                              String groupId,
+                              String artifactId,
+                              String version,
+                              String classifier,
+                              String type) throws IOException {
+        Objects.requireNonNull(artifactWriter, "Null ArtifactWriter can not install an artifact to a Maven repository.");
+        Objects.requireNonNull(groupId, "Bundle can not be installed to a Maven repository without specifying a valid 'groupId'.");
+        Objects.requireNonNull(artifactId, "Bundle can not be installed to a Maven repository without specifying a valid 'artifactId'.");
+        Objects.requireNonNull(version, "Bundle can not be installed to a Maven repository without specifying a valid 'version'.");
+        Objects.requireNonNull(type, "Bundle can not be installed to a Maven repository without specifying a valid 'type'.");
+
+        File targetDir = artifactsDirectory;
+
+        StringTokenizer tokenizer = new StringTokenizer(groupId, ".");
+        while (tokenizer.hasMoreTokens()) {
+            String current = tokenizer.nextToken();
+            targetDir = new File(targetDir, current);
+        }
+
+        targetDir = new File(targetDir, artifactId);
+
+        targetDir = new File(targetDir, version);
+
+        targetDir.mkdirs();
+
+        StringBuilder nameBuilder = new StringBuilder()
+                                    .append(artifactId)
+                                    .append('-')
+                                    .append(version);
+
+        if (classifier != null) {
+            nameBuilder.append('-').append(classifier);
+        }
+
+        nameBuilder.append('.').append(type);
+
+        File targetFile = new File(targetDir, nameBuilder.toString());
+
+        logger.info("Writing data to {}...", targetFile);
+
+        try (FileOutputStream targetStream = new FileOutputStream(targetFile)) {
+            artifactWriter.write(targetStream);
+        }
+
+        logger.info("Data successfully written to {}.", targetFile);
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/RegexBasedResourceFilter.java b/src/main/java/org/apache/sling/feature/cpconverter/RegexBasedResourceFilter.java
new file mode 100644
index 0000000..9535acf
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/cpconverter/RegexBasedResourceFilter.java
@@ -0,0 +1,54 @@
+/*
+ * 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.feature.cpconverter;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+final class RegexBasedResourceFilter {
+
+    private final Logger logger = LoggerFactory.getLogger(getClass());
+
+    private final List<Pattern> patterns = new LinkedList<>();
+
+    public void addFilteringPattern(String filteringPattern) {
+        patterns.add(Pattern.compile(filteringPattern));
+    }
+
+    public boolean isFilteredOut(String path) {
+        for (Pattern pattern : patterns) {
+            logger.debug("Checking if path '{}' matches against '{}' pattern...", path, pattern);
+
+            if (pattern.matcher(path).matches()) {
+                logger.debug("Path '{}' matches against '{}' pattern.", path, pattern);
+
+                return true;
+            } else {
+                logger.debug("Path '{}' does not matches against '{}' pattern.", path, pattern);
+            }
+        }
+
+        logger.debug("Path '{}' does not match against any configured pattern.", path);
+
+        return false;
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/cli/ContentPackage2FeatureModelConverterLauncher.java b/src/main/java/org/apache/sling/feature/cpconverter/cli/ContentPackage2FeatureModelConverterLauncher.java
new file mode 100644
index 0000000..7aa7c50
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/cpconverter/cli/ContentPackage2FeatureModelConverterLauncher.java
@@ -0,0 +1,186 @@
+/*
+ * 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.feature.cpconverter.cli;
+
+import java.io.File;
+import java.util.TimeZone;
+
+import org.apache.sling.feature.cpconverter.ContentPackage2FeatureModelConverter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import picocli.CommandLine;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Option;
+
+@Command(
+    name = "cp2fm",
+    description = "Apache Sling Content Package to Sling Feature converter",
+    footer = "Copyright(c) 2019 The Apache Software Foundation."
+)
+public final class ContentPackage2FeatureModelConverterLauncher implements Runnable {
+
+    @Option(names = { "-h", "--help" }, usageHelp = true, description = "Display the usage message.")
+    private boolean helpRequested;
+
+    @Option(names = { "-X", "--verbose" }, description = "Produce execution debug output.")
+    private boolean debug;
+
+    @Option(names = { "-q", "--quiet" }, description = "Log errors only.")
+    private boolean quiet;
+
+    @Option(names = { "-v", "--version" }, description = "Display version information.")
+    private boolean printVersion;
+
+    @Option(names = { "-c", "--content-package" }, description = "The content-package input file.", required = true)
+    private File contentPackage;
+
+    @Option(names = { "-s", "--strict-validation" }, description = "Flag to mark the content-package input file being strict validated.", required = false, defaultValue = "false")
+    private boolean strictValidation = false;
+
+    @Option(names = { "-m", "--merge-configurations" }, description = "Flag to mark OSGi configurations with same PID will be merged, the tool will fail otherwise.", required = false, defaultValue = "false")
+    private boolean mergeConfigurations = false;
+
+    @Option(names = { "-b", "--bundles-start-order" }, description = "The order to start detected bundles.", required = false)
+    private int bundlesStartOrder = 0;
+
+    @Option(names = { "-f", "--filtering-patterns" }, description = "Regex based pattern(s) to reject content-package archive entries.", required = false)
+    private String[] filteringPatterns;
+
+    @Option(names = { "-o", "--output-directory" }, description = "The output directory where the Feature File and the bundles will be deployed.", required = true)
+    private File outputDirectory;
+
+    @Override
+    public void run() {
+        if (quiet) {
+            System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "error");
+        } else if (debug) {
+            System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "debug");
+        } else {
+            System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "info");
+        }
+        System.setProperty("org.slf4j.simpleLogger.showThreadName", "false");
+        System.setProperty("org.slf4j.simpleLogger.levelInBrackets", "true");
+        System.setProperty("org.slf4j.simpleLogger.showLogName", "false");
+
+        String appName = getClass().getAnnotation(Command.class).description()[0];
+        final Logger logger = LoggerFactory.getLogger(appName);
+
+        // Add the Shutdown Hook to the Java virtual machine
+        // in order to destroy all the allocated resources
+        Runtime.getRuntime().addShutdownHook(new ShutDownHook(logger));
+
+        if (printVersion) {
+            printVersion(logger);
+        }
+
+        logger.info(appName);
+        logger.info("");
+
+        try {
+            ContentPackage2FeatureModelConverter converter = new ContentPackage2FeatureModelConverter()
+                                                             .setStrictValidation(strictValidation)
+                                                             .setMergeConfigurations(mergeConfigurations)
+                                                             .setBundlesStartOrder(bundlesStartOrder)
+                                                             .setOutputDirectory(outputDirectory);
+
+            if (filteringPatterns != null && filteringPatterns.length > 0) {
+                for (String filteringPattern : filteringPatterns) {
+                    converter.addFilteringPattern(filteringPattern);
+                }
+            }
+
+            converter.convert(contentPackage);
+
+            logger.info( "+-----------------------------------------------------+" );
+            logger.info("{} SUCCESS", appName);
+        } catch (Throwable t) {
+            logger.info( "+-----------------------------------------------------+" );
+            logger.info("{} FAILURE", appName);
+            logger.info( "+-----------------------------------------------------+" );
+
+            if (debug) {
+                logger.error("Unable to convert content-package {}:", contentPackage, t);
+            } else {
+                logger.error("Unable to convert content-package {}: {}", contentPackage, t.getMessage());
+            }
+
+            logger.info( "" );
+
+            System.exit(1);
+        }
+
+        logger.info( "+-----------------------------------------------------+" );
+    }
+
+    private static void printVersion(final Logger logger) {
+        logger.info("{} v{} (built on {})",
+                System.getProperty("project.artifactId"),
+                System.getProperty("project.version"),
+                System.getProperty("build.timestamp"));
+        logger.info("Java version: {}, vendor: {}",
+                System.getProperty("java.version"),
+                System.getProperty("java.vendor"));
+        logger.info("Java home: {}", System.getProperty("java.home"));
+        logger.info("Default locale: {}_{}, platform encoding: {}",
+                System.getProperty("user.language"),
+                System.getProperty("user.country"),
+                System.getProperty("sun.jnu.encoding"));
+        logger.info("Default Time Zone: {}", TimeZone.getDefault().getDisplayName());
+        logger.info("OS name: \"{}\", version: \"{}\", arch: \"{}\", family: \"{}\"",
+                System.getProperty("os.name"),
+                System.getProperty("os.version"),
+                System.getProperty("os.arch"),
+                getOsFamily());
+        logger.info("+-----------------------------------------------------+");
+    }
+
+    private static final String getOsFamily() {
+        String osName = System.getProperty("os.name").toLowerCase();
+        String pathSep = System.getProperty("path.separator");
+
+        if (osName.indexOf("windows") != -1) {
+            return "windows";
+        } else if (osName.indexOf("os/2") != -1) {
+            return "os/2";
+        } else if (osName.indexOf("z/os") != -1 || osName.indexOf("os/390") != -1) {
+            return "z/os";
+        } else if (osName.indexOf("os/400") != -1) {
+            return "os/400";
+        } else if (pathSep.equals(";")) {
+            return "dos";
+        } else if (osName.indexOf("mac") != -1) {
+            if (osName.endsWith("x")) {
+                return "mac"; // MACOSX
+            }
+            return "unix";
+        } else if (osName.indexOf("nonstop_kernel") != -1) {
+            return "tandem";
+        } else if (osName.indexOf("openvms") != -1) {
+            return "openvms";
+        } else if (pathSep.equals(":")) {
+            return "unix";
+        }
+
+        return "undefined";
+    }
+
+    public static void main(String[] args) {
+        CommandLine.run(new ContentPackage2FeatureModelConverterLauncher(), args);
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/cli/ShutDownHook.java b/src/main/java/org/apache/sling/feature/cpconverter/cli/ShutDownHook.java
new file mode 100644
index 0000000..0c5d2fc
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/cpconverter/cli/ShutDownHook.java
@@ -0,0 +1,81 @@
+/*
+ * 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.feature.cpconverter.cli;
+
+import java.util.Date;
+import java.util.Formatter;
+
+import org.slf4j.Logger;
+
+final class ShutDownHook extends Thread {
+
+    private final long start = System.currentTimeMillis();
+
+    private final Logger logger;
+
+    public ShutDownHook(Logger logger) {
+        this.logger = logger;
+    }
+
+    @Override
+    public void run() {
+        logger.info("+-----------------------------------------------------+");
+        logger.info("");
+
+        // format the uptime string
+        Formatter uptimeFormatter = new Formatter();
+        uptimeFormatter.format("Total time:");
+
+        long uptime = System.currentTimeMillis() - start;
+        if (uptime < 1000) {
+            uptimeFormatter.format(" %s millisecond%s", uptime, (uptime > 1 ? "s" : ""));
+        } else {
+            long uptimeInSeconds = (uptime) / 1000;
+            final long hours = uptimeInSeconds / 3600;
+
+            if (hours > 0) {
+                uptimeFormatter.format(" %s hour%s", hours, (hours > 1 ? "s" : ""));
+            }
+
+            uptimeInSeconds = uptimeInSeconds - (hours * 3600);
+            final long minutes = uptimeInSeconds / 60;
+
+            if (minutes > 0) {
+                uptimeFormatter.format(" %s minute%s", minutes, (minutes > 1 ? "s" : ""));
+            }
+
+            uptimeInSeconds = uptimeInSeconds - (minutes * 60);
+
+            if (uptimeInSeconds > 0) {
+                uptimeFormatter.format(" %s second%s", uptimeInSeconds, (uptimeInSeconds > 1 ? "s" : ""));
+            }
+        }
+        logger.info(uptimeFormatter.toString());
+
+        uptimeFormatter.close();
+
+        logger.info("Finished at: {}", new Date());
+
+        final Runtime runtime = Runtime.getRuntime();
+        final int megaUnit = 1024 * 1024;
+
+        logger.info("Final Memory: {}M/{}M",
+                    (runtime.totalMemory() - runtime.freeMemory()) / megaUnit,
+                    runtime.totalMemory() / megaUnit);
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/cli/package-info.java b/src/main/java/org/apache/sling/feature/cpconverter/cli/package-info.java
new file mode 100644
index 0000000..63be681
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/cpconverter/cli/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.
+ */
+
+/**
+ * CLI implementation for the converter.
+ */
+package org.apache.sling.feature.cpconverter.cli;
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/handlers/AbstractConfigurationEntryHandler.java b/src/main/java/org/apache/sling/feature/cpconverter/handlers/AbstractConfigurationEntryHandler.java
new file mode 100644
index 0000000..99ea954
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/cpconverter/handlers/AbstractConfigurationEntryHandler.java
@@ -0,0 +1,68 @@
+/*
+ * 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.feature.cpconverter.handlers;
+
+import java.io.InputStream;
+import java.util.Dictionary;
+import java.util.regex.Matcher;
+
+import org.apache.jackrabbit.vault.fs.io.Archive;
+import org.apache.jackrabbit.vault.fs.io.Archive.Entry;
+import org.apache.sling.feature.cpconverter.ContentPackage2FeatureModelConverter;
+
+abstract class AbstractConfigurationEntryHandler extends AbstractRegexEntryHandler {
+
+    public AbstractConfigurationEntryHandler(String extension) {
+        super("(jcr_root)?/apps/[^/]+/config(\\.([^/]+))?/.+\\." + extension);
+    }
+
+    @Override
+    public final void handle(String path, Archive archive, Entry entry, ContentPackage2FeatureModelConverter converter) throws Exception {
+        String pid = entry.getName().substring(0, entry.getName().lastIndexOf('.'));
+
+        logger.info("Processing configuration '{}'.", pid);
+
+        Dictionary<String, Object> configurationProperties;
+        try (InputStream input = archive.openInputStream(entry)) {
+            configurationProperties = parseConfiguration(pid, input);
+        }
+
+        if (configurationProperties.isEmpty()) {
+            logger.info("No configuration properties found for configuration {}", path);
+            return;
+        }
+
+        Matcher matcher = getPattern().matcher(path);
+        String runMode = null;
+        // we are pretty sure it matches, here
+        if (matcher.matches()) {
+            // there is a specified RunMode
+            runMode = matcher.group(3);
+        } else {
+            throw new IllegalStateException("Something went terribly wrong: pattern '"
+                                            + getPattern().pattern()
+                                            + "' should have matched already with path '"
+                                            + path
+                                            + "' but it does not, currently");
+        }
+
+        converter.addConfiguration(runMode, pid, configurationProperties);
+    }
+
+    protected abstract Dictionary<String, Object> parseConfiguration(String name, InputStream input) throws Exception;
+
+}
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/handlers/AbstractRegexEntryHandler.java b/src/main/java/org/apache/sling/feature/cpconverter/handlers/AbstractRegexEntryHandler.java
new file mode 100644
index 0000000..f34d866
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/cpconverter/handlers/AbstractRegexEntryHandler.java
@@ -0,0 +1,44 @@
+/*
+ * 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.feature.cpconverter.handlers;
+
+import java.util.regex.Pattern;
+
+import org.apache.sling.feature.cpconverter.spi.EntryHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+abstract class AbstractRegexEntryHandler implements EntryHandler {
+
+    protected final Logger logger = LoggerFactory.getLogger(getClass());
+
+    private final Pattern pattern;
+
+    public AbstractRegexEntryHandler(String regex) {
+        pattern = Pattern.compile(regex);
+    }
+
+    @Override
+    public final boolean matches(String path) {
+        return pattern.matcher(path).matches();
+    }
+
+    protected final Pattern getPattern() {
+        return pattern;
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandler.java b/src/main/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandler.java
new file mode 100644
index 0000000..884bf06
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandler.java
@@ -0,0 +1,136 @@
+/*
+ * 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.feature.cpconverter.handlers;
+
+import static org.apache.jackrabbit.vault.packaging.PackageProperties.NAME_VERSION;
+import static org.apache.sling.feature.cpconverter.ContentPackage2FeatureModelConverter.NAME_ARTIFACT_ID;
+import static org.apache.sling.feature.cpconverter.ContentPackage2FeatureModelConverter.NAME_GROUP_ID;
+import static org.apache.sling.feature.cpconverter.ContentPackage2FeatureModelConverter.POM_TYPE;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.util.Objects;
+import java.util.Properties;
+import java.util.jar.JarEntry;
+import java.util.jar.JarInputStream;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+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.feature.cpconverter.ContentPackage2FeatureModelConverter;
+import org.apache.sling.feature.cpconverter.spi.ArtifactWriter;
+import org.apache.sling.feature.cpconverter.writers.InputStreamArtifactWriter;
+import org.apache.sling.feature.cpconverter.writers.MavenPomSupplierWriter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public final class BundleEntryHandler extends AbstractRegexEntryHandler {
+
+    private static final String JAR_TYPE = "jar";
+
+    private final Logger logger = LoggerFactory.getLogger(getClass());
+
+    private final Pattern pomPropertiesPattern = Pattern.compile("META-INF/maven/[^/]+/[^/]+/pom.properties");
+
+    private final Pattern pomXmlPattern = Pattern.compile("META-INF/maven/[^/]+/[^/]+/pom.xml");
+
+    public BundleEntryHandler() {
+        super("(jcr_root)?/apps/[^/]+/install(\\.([^/]+))?/.+\\.jar");
+    }
+
+    @Override
+    public void handle(String path, Archive archive, Entry entry, ContentPackage2FeatureModelConverter converter) throws Exception {
+        logger.info("Processing bundle {}...", entry.getName());
+
+        Properties properties = new Properties();
+        byte[] pomXml = null;
+
+        try (JarInputStream jarInput = new JarInputStream(archive.openInputStream(entry));
+                ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+            JarEntry jarEntry;
+            while ((jarEntry = jarInput.getNextJarEntry()) != null) {
+                String entryName = jarEntry.getName();
+
+                if (pomPropertiesPattern.matcher(entryName).matches()) {
+                    logger.info("Reading '{}' bundle GAV from {}...", entry.getName(), entryName);
+
+                    properties.load(jarInput);
+                } else if (pomXmlPattern.matcher(entryName).matches()) {
+                    logger.info("Reading '{}' POM file from {}...", entry.getName(), entryName);
+
+                    IOUtils.copy(jarInput, baos);
+                    pomXml = baos.toByteArray();
+                }
+            }
+        }
+
+        String groupId = getCheckedProperty(properties, NAME_GROUP_ID);
+        String artifactId = getCheckedProperty(properties, NAME_ARTIFACT_ID);
+        String version = getCheckedProperty(properties, NAME_VERSION);
+
+        Matcher matcher = getPattern().matcher(path);
+        String runMode = null;
+        // we are pretty sure it matches, here
+        if (matcher.matches()) {
+            // there is a specified RunMode
+            runMode = matcher.group(3);
+        } else {
+            throw new IllegalStateException("Something went terribly wrong: pattern '"
+                                            + getPattern().pattern()
+                                            + "' should have matched already with path '"
+                                            + path
+                                            + "' but it does not, currently");
+        }
+
+        try (InputStream input = archive.openInputStream(entry)) {
+            converter.getArtifactDeployer().deploy(new InputStreamArtifactWriter(input),
+                                                   groupId,
+                                                   artifactId,
+                                                   version,
+                                                   null,
+                                                   JAR_TYPE);
+
+            converter.attach(runMode,
+                             groupId,
+                             artifactId,
+                             version,
+                             null,
+                             JAR_TYPE);
+        }
+
+        ArtifactWriter pomWriter;
+        if (pomXml == null) {
+            pomWriter = new MavenPomSupplierWriter(groupId, artifactId, version, JAR_TYPE);
+        } else {
+            pomWriter = new InputStreamArtifactWriter(new ByteArrayInputStream(pomXml));
+        }
+
+        converter.getArtifactDeployer().deploy(pomWriter, groupId, artifactId, version, null, POM_TYPE);
+    }
+
+    private static String getCheckedProperty(Properties properties, String name) {
+        String property = properties.getProperty(name).trim();
+        Objects.requireNonNull(property, "Bundle can not be defined as a valid Maven artifact without specifying a valid '"
+                                         + name
+                                         + "' property.");
+        return property;
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/handlers/ConfigurationEntryHandler.java b/src/main/java/org/apache/sling/feature/cpconverter/handlers/ConfigurationEntryHandler.java
new file mode 100644
index 0000000..64c8647
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/cpconverter/handlers/ConfigurationEntryHandler.java
@@ -0,0 +1,36 @@
+/*
+ * 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.feature.cpconverter.handlers;
+
+import java.io.InputStream;
+import java.util.Dictionary;
+
+import org.apache.felix.cm.file.ConfigurationHandler;
+
+public final class ConfigurationEntryHandler extends AbstractConfigurationEntryHandler {
+
+    public ConfigurationEntryHandler() {
+        super("config");
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    protected Dictionary<String, Object> parseConfiguration(String name, InputStream input) throws Exception {
+        return ConfigurationHandler.read(input);
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/handlers/ContentPackageEntryHandler.java b/src/main/java/org/apache/sling/feature/cpconverter/handlers/ContentPackageEntryHandler.java
new file mode 100644
index 0000000..c385e05
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/cpconverter/handlers/ContentPackageEntryHandler.java
@@ -0,0 +1,49 @@
+/*
+ * 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.feature.cpconverter.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.feature.cpconverter.ContentPackage2FeatureModelConverter;
+
+public final class ContentPackageEntryHandler extends AbstractRegexEntryHandler {
+
+    public ContentPackageEntryHandler() {
+        super("(?:jcr_root)?/etc/packages/.+\\.zip");
+    }
+
+    @Override
+    public void handle(String path, Archive archive, Entry entry, ContentPackage2FeatureModelConverter converter) throws Exception {
+        logger.info("Processing sub-content package '{}'...", entry.getName());
+
+        File temporaryContentPackage = File.createTempFile("content-package", entry.getName());
+
+        try (InputStream input = archive.openInputStream(entry);
+                OutputStream output = new FileOutputStream(temporaryContentPackage)) {
+            IOUtils.copy(input, output);
+        }
+
+        converter.processSubPackage(path, temporaryContentPackage);
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/handlers/JsonConfigurationEntryHandler.java b/src/main/java/org/apache/sling/feature/cpconverter/handlers/JsonConfigurationEntryHandler.java
new file mode 100644
index 0000000..7ddaf4f
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/cpconverter/handlers/JsonConfigurationEntryHandler.java
@@ -0,0 +1,75 @@
+/*
+ * 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.feature.cpconverter.handlers;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.StringWriter;
+import java.net.URL;
+import java.util.Dictionary;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.felix.configurator.impl.json.JSONUtil;
+import org.apache.felix.configurator.impl.json.TypeConverter;
+import org.apache.felix.configurator.impl.model.ConfigurationFile;
+
+public final class JsonConfigurationEntryHandler extends AbstractConfigurationEntryHandler {
+
+    public JsonConfigurationEntryHandler() {
+        super("cfg\\.json");
+    }
+
+    @Override
+    protected Dictionary<String, Object> parseConfiguration(String name, InputStream input) throws Exception {
+        StringBuilder content = new StringBuilder()
+                                .append("{ \"")
+                                .append(name)
+                                .append("\" : ");
+        try (Reader reader = new InputStreamReader(input); StringWriter writer = new StringWriter()) {
+            IOUtils.copy(reader, writer);
+            content.append(writer.toString());
+        }
+        content.append("}");
+
+        JSONUtil.Report report = new JSONUtil.Report();
+        ConfigurationFile configuration = JSONUtil.readJSON(new TypeConverter(null),
+                                                            name,
+                                                            new URL("file://content-package/" + name),
+                                                            0,
+                                                            content.toString(),
+                                                            report);
+
+        if (!report.errors.isEmpty() || !report.warnings.isEmpty()) {
+            final StringBuilder builder = new StringBuilder();
+            builder.append("Errors in configuration:");
+            for (final String w : report.warnings) {
+                builder.append("\n");
+                builder.append(w);
+            }
+            for (final String e : report.errors) {
+                builder.append("\n");
+                builder.append(e);
+            }
+            throw new IOException(builder.toString());
+        }
+
+        return configuration.getConfigurations().get(0).getProperties();
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/handlers/PropertiesConfigurationEntryHandler.java b/src/main/java/org/apache/sling/feature/cpconverter/handlers/PropertiesConfigurationEntryHandler.java
new file mode 100644
index 0000000..faa0dac
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/cpconverter/handlers/PropertiesConfigurationEntryHandler.java
@@ -0,0 +1,60 @@
+/*
+ * 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.feature.cpconverter.handlers;
+
+import java.io.BufferedInputStream;
+import java.io.InputStream;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Properties;
+
+public final class PropertiesConfigurationEntryHandler extends AbstractConfigurationEntryHandler {
+
+    public PropertiesConfigurationEntryHandler() {
+        super("(cfg|properties)");
+    }
+
+    @Override
+    protected Dictionary<String, Object> parseConfiguration(String name, InputStream input) throws Exception {
+        final Properties properties = new Properties();
+
+        try (final BufferedInputStream in = new BufferedInputStream(input)) {
+            in.mark(1);
+
+            boolean isXml = '<' == in.read();
+
+            in.reset();
+
+            if (isXml) {
+                properties.loadFromXML(in);
+            } else {
+                properties.load(in);
+            }
+        }
+
+        Dictionary<String, Object> configuration = new Hashtable<>();
+        final Enumeration<Object> i = properties.keys();
+        while (i.hasMoreElements()) {
+            final Object key = i.nextElement();
+            configuration.put(key.toString(), properties.get(key));
+        }
+
+        return configuration;
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/handlers/XmlConfigurationEntryHandler.java b/src/main/java/org/apache/sling/feature/cpconverter/handlers/XmlConfigurationEntryHandler.java
new file mode 100644
index 0000000..ed6eabe
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/cpconverter/handlers/XmlConfigurationEntryHandler.java
@@ -0,0 +1,92 @@
+/*
+ * 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.feature.cpconverter.handlers;
+
+import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE;
+
+import java.io.InputStream;
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import org.apache.jackrabbit.vault.util.DocViewProperty;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+public final class XmlConfigurationEntryHandler extends AbstractConfigurationEntryHandler {
+
+    private static final String JCR_ROOT = "jcr:root";
+
+    private static final String SLING_OSGICONFIG = "sling:OsgiConfig";
+
+    private final SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
+
+    public XmlConfigurationEntryHandler() {
+        super("xml");
+    }
+
+    @Override
+    protected Dictionary<String, Object> parseConfiguration(String name, InputStream input) throws Exception {
+        SAXParser saxParser = saxParserFactory.newSAXParser();
+        JcrConfigurationHandler configurationHandler = new JcrConfigurationHandler();
+        saxParser.parse(input, configurationHandler);
+        return configurationHandler.getConfiguration();
+    }
+
+    private static final class JcrConfigurationHandler extends DefaultHandler {
+
+        private final Dictionary<String, Object> configuration = new Hashtable<>();
+
+        public Dictionary<String, Object> getConfiguration() {
+            return configuration;
+        }
+
+        @Override
+        public void startElement(String uri, String localName, String qName, Attributes attributes)
+                throws SAXException {
+            String primaryType = attributes.getValue(JCR_PRIMARYTYPE);
+
+            if (JCR_ROOT.equals(qName) && SLING_OSGICONFIG.equals(primaryType)) {
+                for (int i = 0; i < attributes.getLength(); i++) {
+                    String attributeQName = attributes.getQName(i);
+
+                    // ignore jcr: and similar properties
+                    if (attributeQName.indexOf(':') == -1) {
+                        String attributeValue = attributes.getValue(i);
+
+                        if (attributeValue != null && !attributeValue.isEmpty()) {
+                            DocViewProperty property = DocViewProperty.parse(attributeQName, attributeValue);
+
+                            if (property.values.length > 0) {
+                                if (property.isMulti) {
+                                    configuration.put(attributeQName, property.values);
+                                } else {
+                                    configuration.put(attributeQName, property.values[0]);
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/handlers/package-info.java b/src/main/java/org/apache/sling/feature/cpconverter/handlers/package-info.java
new file mode 100644
index 0000000..85df1d5
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/cpconverter/handlers/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+
+/**
+ * Default implementations of the <i>org.apache.sling.feature.cpconverter.spi.EntryHandler</i> service
+ * to handle resources while scanning Apache Jackrabbit Vault packages,
+ * such as OSGi bundles &amp; configurations and nested content packages.
+ */
+package org.apache.sling.feature.cpconverter.handlers;
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/package-info.java b/src/main/java/org/apache/sling/feature/cpconverter/package-info.java
new file mode 100644
index 0000000..1af70af
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/cpconverter/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.
+ */
+
+/**
+ * Content Package to Feature Model converter tool for Apache Sling.
+ */
+package org.apache.sling.feature.cpconverter;
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/spi/ArtifactWriter.java b/src/main/java/org/apache/sling/feature/cpconverter/spi/ArtifactWriter.java
new file mode 100644
index 0000000..6df9c20
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/cpconverter/spi/ArtifactWriter.java
@@ -0,0 +1,26 @@
+/*
+ * 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.feature.cpconverter.spi;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+public interface ArtifactWriter {
+
+    void write(OutputStream output) throws IOException;
+
+}
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/spi/BundlesDeployer.java b/src/main/java/org/apache/sling/feature/cpconverter/spi/BundlesDeployer.java
new file mode 100644
index 0000000..ca64299
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/cpconverter/spi/BundlesDeployer.java
@@ -0,0 +1,33 @@
+/*
+ * 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.feature.cpconverter.spi;
+
+import java.io.File;
+import java.io.IOException;
+
+public interface BundlesDeployer {
+
+    File getBundlesDirectory();
+
+    void deploy(ArtifactWriter artifactWriter,
+                String groupId,
+                String artifactId,
+                String version,
+                String classifier,
+                String type) throws IOException;
+
+}
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/spi/EntryHandler.java b/src/main/java/org/apache/sling/feature/cpconverter/spi/EntryHandler.java
new file mode 100644
index 0000000..51e3835
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/cpconverter/spi/EntryHandler.java
@@ -0,0 +1,29 @@
+/*
+ * 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.feature.cpconverter.spi;
+
+import org.apache.jackrabbit.vault.fs.io.Archive;
+import org.apache.jackrabbit.vault.fs.io.Archive.Entry;
+import org.apache.sling.feature.cpconverter.ContentPackage2FeatureModelConverter;
+
+public interface EntryHandler {
+
+    boolean matches(String path);
+
+    void handle(String path, Archive archive, Entry entry, ContentPackage2FeatureModelConverter converter) throws Exception;
+
+}
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/spi/package-info.java b/src/main/java/org/apache/sling/feature/cpconverter/spi/package-info.java
new file mode 100644
index 0000000..4d66b84
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/cpconverter/spi/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.
+ */
+
+/**
+ * Definition of services that can be plugged at runtime to extend the converter tool.
+ */
+package org.apache.sling.feature.cpconverter.spi;
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/vltpkg/VaultPackageAssembler.java b/src/main/java/org/apache/sling/feature/cpconverter/vltpkg/VaultPackageAssembler.java
new file mode 100644
index 0000000..d5ec573
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/cpconverter/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.feature.cpconverter.vltpkg;
+
+import static org.apache.sling.feature.cpconverter.ContentPackage2FeatureModelConverter.FEATURE_CLASSIFIER;
+import static org.apache.sling.feature.cpconverter.ContentPackage2FeatureModelConverter.NAME_ARTIFACT_ID;
+import static org.apache.sling.feature.cpconverter.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.feature.cpconverter.ContentPackage2FeatureModelConverter;
+import org.apache.sling.feature.cpconverter.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/src/main/java/org/apache/sling/feature/cpconverter/vltpkg/package-info.java b/src/main/java/org/apache/sling/feature/cpconverter/vltpkg/package-info.java
new file mode 100644
index 0000000..eab6ead
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/cpconverter/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.feature.cpconverter.vltpkg;
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/writers/FileArtifactWriter.java b/src/main/java/org/apache/sling/feature/cpconverter/writers/FileArtifactWriter.java
new file mode 100644
index 0000000..c70bc8e
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/cpconverter/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.feature.cpconverter.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.feature.cpconverter.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/src/main/java/org/apache/sling/feature/cpconverter/writers/InputStreamArtifactWriter.java b/src/main/java/org/apache/sling/feature/cpconverter/writers/InputStreamArtifactWriter.java
new file mode 100644
index 0000000..3092c0f
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/cpconverter/writers/InputStreamArtifactWriter.java
@@ -0,0 +1,39 @@
+/*
+ * 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.feature.cpconverter.writers;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.sling.feature.cpconverter.spi.ArtifactWriter;
+
+public class InputStreamArtifactWriter implements ArtifactWriter {
+
+    private final InputStream input;
+
+    public InputStreamArtifactWriter(InputStream input) {
+        this.input = input;
+    }
+
+    @Override
+    public void write(OutputStream output) throws IOException {
+        IOUtils.copy(input, output);
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/writers/MavenPomSupplierWriter.java b/src/main/java/org/apache/sling/feature/cpconverter/writers/MavenPomSupplierWriter.java
new file mode 100644
index 0000000..69f8f9b
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/cpconverter/writers/MavenPomSupplierWriter.java
@@ -0,0 +1,54 @@
+/*
+ * 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.feature.cpconverter.writers;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.apache.maven.model.Model;
+import org.apache.maven.model.io.xpp3.MavenXpp3Writer;
+import org.apache.sling.feature.cpconverter.spi.ArtifactWriter;
+
+public final class MavenPomSupplierWriter implements ArtifactWriter {
+
+    private final String groupId;
+
+    private final String artifactId;
+
+    private final String version;
+
+    private final String type;
+
+    public MavenPomSupplierWriter(String groupId, String artifactId, String version, String type) {
+        this.groupId = groupId;
+        this.artifactId = artifactId;
+        this.version = version;
+        this.type = type;
+    }
+
+    @Override
+    public void write(OutputStream outputStream) throws IOException {
+        Model model = new Model();
+        model.setGroupId(groupId);
+        model.setArtifactId(artifactId);
+        model.setVersion(version);
+        model.setPackaging(type);
+
+        new MavenXpp3Writer().write(outputStream, model);
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/writers/package-info.java b/src/main/java/org/apache/sling/feature/cpconverter/writers/package-info.java
new file mode 100644
index 0000000..103359d
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/cpconverter/writers/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.
+ */
+
+/**
+ * Default implementations of the <i>org.apache.sling.feature.cpconverter.spi.ArtifactWriter</i>.
+ */
+package org.apache.sling.feature.cpconverter.writers;
diff --git a/src/main/legal/LICENSE-with-deps b/src/main/legal/LICENSE-with-deps
new file mode 100644
index 0000000..75545a9
--- /dev/null
+++ b/src/main/legal/LICENSE-with-deps
@@ -0,0 +1,233 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed 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.
+
+<#function licenseFormat licenses>
+    <#assign result = ""/>
+    <#list licenses as license>
+        <#if result?length &gt; 0>
+            <#assign result = result + ", " />
+        </#if>
+        <#assign result = result + license />
+    </#list>
+    <#return result>
+</#function>
+
+<#function artifactFormat p>
+    <#if p.name?index_of('Unnamed') &gt; -1>
+        <#return p.groupId + ":" + p.artifactId + ":" + p.version + " (" + (p.url!"no url defined") + ")">
+    <#else>
+        <#return p.name + " v" + p.version + " (" + (p.url!"no url defined") + ")">
+    </#if>
+</#function>
+
+<#if dependencyMap?size == 0>
+The project has no dependencies.
+<#else>
+    <#list dependencyMap as e>
+        <#assign project = e.getKey()/>
+        <#assign licenses = e.getValue()/>
+For the ${artifactFormat(project)} component
+This is licensed under ${licenseFormat(licenses)}
+
+    </#list>
+</#if>
diff --git a/src/main/legal/NOTICE-with-deps b/src/main/legal/NOTICE-with-deps
new file mode 100644
index 0000000..e7f7780
--- /dev/null
+++ b/src/main/legal/NOTICE-with-deps
@@ -0,0 +1,5 @@
+Apache Sling Feature Analyser
+Copyright 2018 The Apache Software Foundation
+
+This product includes software developed by
+The Apache Software Foundation (http://www.apache.org/).
diff --git a/src/main/legal/license-header b/src/main/legal/license-header
new file mode 100644
index 0000000..d323a71
--- /dev/null
+++ b/src/main/legal/license-header
@@ -0,0 +1,14 @@
+  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/main/resources/META-INF/services/org.apache.sling.feature.cpconverter.spi.EntryHandler b/src/main/resources/META-INF/services/org.apache.sling.feature.cpconverter.spi.EntryHandler
new file mode 100644
index 0000000..dfa616c
--- /dev/null
+++ b/src/main/resources/META-INF/services/org.apache.sling.feature.cpconverter.spi.EntryHandler
@@ -0,0 +1,6 @@
+org.apache.sling.feature.cpconverter.handlers.BundleEntryHandler
+org.apache.sling.feature.cpconverter.handlers.ConfigurationEntryHandler
+org.apache.sling.feature.cpconverter.handlers.ContentPackageEntryHandler
+org.apache.sling.feature.cpconverter.handlers.JsonConfigurationEntryHandler
+org.apache.sling.feature.cpconverter.handlers.PropertiesConfigurationEntryHandler
+org.apache.sling.feature.cpconverter.handlers.XmlConfigurationEntryHandler
diff --git a/src/main/resources/org/apache/sling/feature/cpconverter/vltpkg/config.xml b/src/main/resources/org/apache/sling/feature/cpconverter/vltpkg/config.xml
new file mode 100644
index 0000000..54a0a38
--- /dev/null
+++ b/src/main/resources/org/apache/sling/feature/cpconverter/vltpkg/config.xml
@@ -0,0 +1,95 @@
+<?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.
+-->
+<vaultfs version="1.1">
+  <!--
+    Defines the content aggregation. The order of the defined aggregates
+    is important for finding the correct aggregator.
+  -->
+  <aggregates>
+    <!--
+      Defines an aggregate that handles nt:file and nt:resource nodes.
+    -->
+    <aggregate type="file" title="File Aggregate"/>
+
+    <!--
+      Defines an aggregate that handles file/folder like nodes. It matches
+      all nt:hierarchyNode nodes that have or define a jcr:content
+      child node and excludes child nodes that are nt:hierarchyNodes.
+    -->
+    <aggregate type="filefolder" title="File/Folder Aggregate"/>
+
+    <!--
+      Defines an aggregate that handles nt:nodeType nodes and serializes
+      them into .cnd notation.
+    -->
+    <aggregate type="nodetype" title="Node Type Aggregate" />
+
+    <!--
+      Defines an aggregate that defines full coverage for certain node
+      types that cannot be covered by the default aggregator.
+    -->
+    <aggregate type="full" title="Full Coverage Aggregate">
+      <matches>
+        <include nodeType="rep:AccessControl" respectSupertype="true" />
+        <include nodeType="cq:Widget" respectSupertype="true" />
+        <include nodeType="cq:WidgetCollection" respectSupertype="true" />
+        <include nodeType="cq:EditConfig" respectSupertype="true" />
+        <include nodeType="cq:WorkflowModel" respectSupertype="true" />
+        <include nodeType="vlt:FullCoverage" respectSupertype="true" />
+        <include nodeType="mix:language" respectSupertype="true" />
+        <include nodeType="sling:OsgiConfig" respectSupertype="true" />
+      </matches>
+    </aggregate>
+
+    <!--
+      Defines an aggregate that handles nt:folder like nodes.
+    -->
+    <aggregate type="generic" title="Folder Aggregate">
+      <matches>
+        <include nodeType="nt:folder" respectSupertype="true" />
+      </matches>
+      <contains>
+        <exclude isNode="true" />
+      </contains>
+    </aggregate>
+
+    <!--
+      Defines the default aggregate
+    -->
+    <aggregate type="generic" title="Default Aggregator" isDefault="true">
+      <contains>
+        <exclude nodeType="nt:hierarchyNode" respectSupertype="true" />
+      </contains>
+      <matches>
+        <!-- all -->
+      </matches>
+    </aggregate>
+
+  </aggregates>
+
+  <!--
+    defines the input handlers
+  -->
+  <handlers>
+    <handler type="folder"/>
+    <handler type="file"/>
+    <handler type="nodetype"/>
+    <handler type="generic"/>
+  </handlers>
+
+</vaultfs>
diff --git a/src/main/resources/org/apache/sling/feature/cpconverter/vltpkg/definition/.content.xml b/src/main/resources/org/apache/sling/feature/cpconverter/vltpkg/definition/.content.xml
new file mode 100644
index 0000000..abf67f1
--- /dev/null
+++ b/src/main/resources/org/apache/sling/feature/cpconverter/vltpkg/definition/.content.xml
@@ -0,0 +1,19 @@
+<?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:vlt="http://www.day.com/jcr/vault/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0"
+  jcr:primaryType="vlt:PackageDefinition" />
diff --git a/src/main/resources/org/apache/sling/feature/cpconverter/vltpkg/settings.xml b/src/main/resources/org/apache/sling/feature/cpconverter/vltpkg/settings.xml
new file mode 100644
index 0000000..679e6b1
--- /dev/null
+++ b/src/main/resources/org/apache/sling/feature/cpconverter/vltpkg/settings.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.
+  -->
+<vault version="1.0">
+  <ignore name=".git" />
+  <ignore name=".svn" />
+  <ignore name=".DS_Store" />
+</vault>
\ No newline at end of file
diff --git a/src/test/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverterTest.java b/src/test/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverterTest.java
new file mode 100644
index 0000000..0514504
--- /dev/null
+++ b/src/test/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverterTest.java
@@ -0,0 +1,235 @@
+/*
+ * 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.feature.cpconverter;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.Reader;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.StringTokenizer;
+import java.util.zip.ZipFile;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.io.json.FeatureJSONReader;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class ContentPackage2FeatureModelConverterTest {
+
+    private ContentPackage2FeatureModelConverter converter;
+
+    @Before
+    public void setUp() {
+        converter = new ContentPackage2FeatureModelConverter();
+    }
+
+    @After
+    public void tearDowd() {
+        converter = null;
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void convertRequiresNonNullPackage() throws Exception {
+        converter.convert(null);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void convertRequiresExistingFile() throws Exception {
+        converter.convert(new File("this/file/does/not/exist.zip"));
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void convertRequiresNotDirectoryFile() throws Exception {
+        File testDirectory = new File(System.getProperty("user.dir"));
+        converter.convert(testDirectory);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void getRunModeRequiresConvertInvoked() {
+        converter.getRunMode(null);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void addConfigurationRequiresConvertInvoked() {
+        converter.setMergeConfigurations(true).getRunMode(null);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void processRequiresNotNullPackage() throws Exception {
+        converter.processSubPackage("", null);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void processRequiresConvertInvoked() throws Exception {
+        converter.processSubPackage("", mock(File.class));
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void deployLocallyAndAttachRequiresNonNullInput() throws Exception {
+        converter.attach(null, null, null, null, null, null);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void deployLocallyAndAttachRequiresNonNullGroupId() throws Exception {
+        converter.attach(null, null, null, null, null, null);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void deployLocallyAndAttachRequiresNonNullArtifactId() throws Exception {
+        converter.attach(null, "org.apache.sling", null, null, null, null);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void deployLocallyAndAttachRequiresNonNullVersion() throws Exception {
+        converter.attach(null, "org.apache.sling", "org.apache.sling.cm2fm", null, null, null);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void deployLocallyAndAttachRequiresNonNullType() throws Exception {
+        converter.attach(null, "org.apache.sling", "org.apache.sling.cm2fm", "0.0.1", null, null);
+    }
+
+    @Test
+    public void convertContentPackage() throws Exception {
+        URL packageUrl = getClass().getResource("test-content-package.zip");
+        File packageFile = FileUtils.toFile(packageUrl);
+
+        File outputDirectory = new File(System.getProperty("testDirectory"), getClass().getName() + '_' + System.currentTimeMillis());
+
+        converter.setBundlesStartOrder(5).setOutputDirectory(outputDirectory).convert(packageFile);
+
+        verifyFeatureFile(outputDirectory,
+                          "asd.retail.all.json",
+                          "org.apache.sling:asd.retail.all:slingosgifeature:cp2fm-converted-feature:0.0.1",
+                          Arrays.asList("org.apache.felix:org.apache.felix.framework:6.0.1"),
+                          Arrays.asList("org.apache.sling.commons.log.LogManager.factory.config-asd-retail"),
+                          Arrays.asList("org.apache.sling:asd.retail.all:zip:cp2fm-converted-feature:0.0.1"));
+        verifyFeatureFile(outputDirectory,
+                          "asd.retail.all-author.json",
+                          "org.apache.sling:asd.retail.all:slingosgifeature:cp2fm-converted-feature-author:0.0.1",
+                          Arrays.asList("org.apache.sling:org.apache.sling.api:2.20.0"),
+                          Collections.emptyList(),
+                          Collections.emptyList());
+        verifyFeatureFile(outputDirectory,
+                          "asd.retail.all-publish.json",
+                          "org.apache.sling:asd.retail.all:slingosgifeature:cp2fm-converted-feature-publish:0.0.1",
+                          Arrays.asList("org.apache.sling:org.apache.sling.models.api:1.3.8"),
+                          Arrays.asList("org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended-asd-retail"),
+                          Collections.emptyList());
+
+        ZipFile zipFile = new ZipFile(new File(outputDirectory, "bundles/org/apache/sling/asd.retail.all/0.0.1/asd.retail.all-0.0.1-cp2fm-converted-feature.zip"));
+        for (String expectedEntry : new String[] {
+                "jcr_root/content/asd/.content.xml",
+                "jcr_root/content/asd/resources.xml",
+                "jcr_root/apps/.content.xml",
+                "META-INF/vault/properties.xml",
+                "META-INF/vault/config.xml",
+                "META-INF/vault/settings.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));
+        }
+        zipFile.close();
+    }
+
+    private void verifyFeatureFile(File outputDirectory,
+                                   String name,
+                                   String expectedArtifactId,
+                                   List<String> expectedBundles,
+                                   List<String> expectedConfigurations,
+                                   List<String> expectedContentPackagesExtensions) throws Exception {
+        File featureFile = new File(outputDirectory, name);
+        assertTrue(featureFile + " was not correctly created", featureFile.exists());
+
+        try (Reader reader = new FileReader(featureFile)) {
+            Feature feature = FeatureJSONReader.read(reader, featureFile.getAbsolutePath());
+
+            assertEquals(expectedArtifactId, feature.getId().toMvnId());
+
+            for (String expectedBundle : expectedBundles) {
+                assertTrue(expectedBundle + " not found in Feature " + expectedArtifactId, feature.getBundles().containsExact(ArtifactId.fromMvnId(expectedBundle)));
+                verifyInstalledBundle(outputDirectory, expectedBundle);
+            }
+
+            for (String expectedConfiguration : expectedConfigurations) {
+                assertNotNull(expectedConfiguration + " not found in Feature " + expectedArtifactId, feature.getConfigurations().getConfiguration(expectedConfiguration));
+            }
+
+            for (String expectedContentPackagesExtension : expectedContentPackagesExtensions) {
+                assertTrue(expectedContentPackagesExtension + " not found in Feature " + expectedArtifactId,
+                           feature.getExtensions().getByName("content-packages").getArtifacts().containsExact(ArtifactId.fromMvnId(expectedContentPackagesExtension)));
+                verifyInstalledBundle(outputDirectory, expectedContentPackagesExtension);
+            }
+        }
+    }
+
+    private void verifyInstalledBundle(File outputDirectory, String coordinates) {
+        ArtifactId bundleId = ArtifactId.fromMvnId(coordinates);
+
+        File bundleDirectory = new File(outputDirectory, "bundles");
+
+        StringTokenizer tokenizer = new StringTokenizer(bundleId.getGroupId(), ".");
+        while (tokenizer.hasMoreTokens()) {
+            bundleDirectory = new File(bundleDirectory, tokenizer.nextToken());
+        }
+
+        bundleDirectory = new File(bundleDirectory, bundleId.getArtifactId());
+        bundleDirectory = new File(bundleDirectory, bundleId.getVersion());
+
+        StringBuilder bundleFileName = new StringBuilder()
+                                       .append(bundleId.getArtifactId())
+                                       .append('-')
+                                       .append(bundleId.getVersion());
+        if (bundleId.getClassifier() != null) {
+            bundleFileName.append('-').append(bundleId.getClassifier());
+        }
+        bundleFileName.append('.').append(bundleId.getType());
+
+        File bundleFile = new File(bundleDirectory, bundleFileName.toString());
+        assertTrue("Bundle " + bundleFile + " does not exist", bundleFile.exists());
+
+        File pomFile = new File(bundleDirectory, String.format("%s-%s.pom", bundleId.getArtifactId(), bundleId.getVersion()));
+        assertTrue("POM file " + pomFile + " does not exist", pomFile.exists());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void verifyFilteringOutUndesiredPackages() throws Exception {
+        converter.addFilteringPattern(".*\\/install(?!(\\.runMode1\\/|\\.runMode2\\/|\\/))(.*)(?=\\.zip$).*");
+
+        URL packageUrl = getClass().getResource("test-content-package-unacceptable.zip");
+        File packageFile = FileUtils.toFile(packageUrl);
+
+        File outputDirectory = new File(System.getProperty("testDirectory"), getClass().getName() + '_' + System.currentTimeMillis());
+
+        converter.setBundlesStartOrder(5).setOutputDirectory(outputDirectory).convert(packageFile);
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/feature/cpconverter/DefaultBundlesDeployerTest.java b/src/test/java/org/apache/sling/feature/cpconverter/DefaultBundlesDeployerTest.java
new file mode 100644
index 0000000..1fe0429
--- /dev/null
+++ b/src/test/java/org/apache/sling/feature/cpconverter/DefaultBundlesDeployerTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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.feature.cpconverter;
+
+import static org.junit.Assert.*;
+
+import static org.mockito.Mockito.mock;
+
+import java.io.File;
+
+import org.apache.sling.feature.cpconverter.spi.ArtifactWriter;
+import org.apache.sling.feature.cpconverter.spi.BundlesDeployer;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class DefaultBundlesDeployerTest {
+
+    private BundlesDeployer artifactDeployer;
+
+    @Before
+    public void setUp() {
+        File outputDirectory = new File(System.getProperty("testDirectory"), getClass().getName() + '_' + System.currentTimeMillis());
+        artifactDeployer = new DefaultBundlesDeployer(outputDirectory);
+    }
+
+    @After
+    public void tearDown() {
+        artifactDeployer = null;
+    }
+
+    @Test
+    public void verifyBundlesDirectory() {
+        File bundlesDirectory = artifactDeployer.getBundlesDirectory();
+        assertNotNull(bundlesDirectory);
+        assertTrue(bundlesDirectory.exists());
+        assertTrue(bundlesDirectory.isDirectory());
+        assertEquals("bundles", bundlesDirectory.getName());
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void deployLocallyAndAttachRequiresNonNullInput() throws Exception {
+        artifactDeployer.deploy(null, null, null, null, null, null);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void deployLocallyAndAttachRequiresNonNullGroupId() throws Exception {
+        artifactDeployer.deploy(mock(ArtifactWriter.class), null, null, null, null, null);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void deployLocallyAndAttachRequiresNonNullArtifactId() throws Exception {
+        artifactDeployer.deploy(mock(ArtifactWriter.class), "org.apache.sling", null, null, null, null);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void deployLocallyAndAttachRequiresNonNullVersion() throws Exception {
+        artifactDeployer.deploy(mock(ArtifactWriter.class), "org.apache.sling", "org.apache.sling.cm2fm", null, null, null);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void deployLocallyAndAttachRequiresNonNullType() throws Exception {
+        artifactDeployer.deploy(mock(ArtifactWriter.class), "org.apache.sling", "org.apache.sling.cm2fm", "0.0.1", null, null);
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/feature/cpconverter/RegexBasedResourceFilterTest.java b/src/test/java/org/apache/sling/feature/cpconverter/RegexBasedResourceFilterTest.java
new file mode 100644
index 0000000..346d4f7
--- /dev/null
+++ b/src/test/java/org/apache/sling/feature/cpconverter/RegexBasedResourceFilterTest.java
@@ -0,0 +1,69 @@
+/*
+ * 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.feature.cpconverter;
+
+import static org.junit.Assert.*;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class RegexBasedResourceFilterTest {
+
+    private RegexBasedResourceFilter filter;
+
+    @Before
+    public void setUp() {
+        filter = new RegexBasedResourceFilter();
+    }
+
+    @After
+    public void tearDown() {
+        filter = null;
+    }
+
+    @Test
+    public void packagesFilteredIn() {
+        filter.addFilteringPattern(".*\\/myEnvironment(?!(\\.runMode1\\/|\\.runMode2\\/|\\/))(.*)(?=\\.zip$).*");
+
+        assertFalse(filter.isFilteredOut("/apps/myapp/myEnvironment/something.zip"));
+        assertFalse(filter.isFilteredOut("/apps/myapp/myEnvironment.runMode1/something.zip"));
+        assertFalse(filter.isFilteredOut("/apps/myapp/myEnvironment.runMode2/something.zip"));
+
+        assertFalse(filter.isFilteredOut("/apps/myEnvironment/asd.zip"));
+    }
+
+    @Test
+    public void configFilteredOut() {
+        filter.addFilteringPattern(".*\\/myEnvironment(?!(\\.runMode1\\/|\\.runMode2\\/|\\/))(.*)(?=\\.config$).*");
+
+        assertTrue(filter.isFilteredOut("/apps/myapp/myEnvironment.subRunMode/something.config"));
+        assertTrue(filter.isFilteredOut("/apps/myapp/myEnvironment.runMode1.subRunMode/something.config"));
+        assertTrue(filter.isFilteredOut("/apps/myapp/myEnvironment.subRunMode.runMode1/something.config"));
+    }
+
+    @Test
+    public void packagesFilteredOut() {
+        filter.addFilteringPattern(".*\\/myEnvironment(?!(\\.runMode1\\/|\\.runMode2\\/|\\/))(.*)(?=\\.zip$).*");
+
+        assertTrue(filter.isFilteredOut("/apps/myapp/myEnvironment.subRunMode/something.zip"));
+        assertTrue(filter.isFilteredOut("/apps/myapp/myEnvironment.runMode1.subRunMode/something.zip"));
+        assertTrue(filter.isFilteredOut("/apps/myapp/myEnvironment.subRunMode.runMode1/something.zip"));
+        assertTrue(filter.isFilteredOut("/apps/myapp/myEnvironment.xyz/something.zip"));
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandlerTest.java b/src/test/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandlerTest.java
new file mode 100644
index 0000000..8753d61
--- /dev/null
+++ b/src/test/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandlerTest.java
@@ -0,0 +1,118 @@
+/*
+ * 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.feature.cpconverter.handlers;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doCallRealMethod;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import java.io.File;
+import java.io.InputStream;
+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.feature.ArtifactId;
+import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.cpconverter.ContentPackage2FeatureModelConverter;
+import org.apache.sling.feature.cpconverter.DefaultBundlesDeployer;
+import org.apache.sling.feature.cpconverter.spi.EntryHandler;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+@RunWith(Parameterized.class)
+public final class BundleEntryHandlerTest {
+
+    private final String bundleLocation;
+
+    private final EntryHandler bundleEntryHandler;
+
+    public BundleEntryHandlerTest(String bundleLocation, EntryHandler bundleEntryHandler) {
+        this.bundleLocation = bundleLocation;
+        this.bundleEntryHandler = bundleEntryHandler;
+    }
+
+    @Test
+    public void doesNotMatch() {
+        assertFalse(bundleEntryHandler.matches("jcr_root/not/a/valid/recognised/bundle.jar"));
+    }
+
+    @Test
+    public void matches() {
+        assertTrue(bundleEntryHandler.matches(bundleLocation));
+    }
+
+    @Test
+    public void deployBundle() throws Exception {
+        Archive archive = mock(Archive.class);
+        Entry entry = mock(Entry.class);
+
+        when(entry.getName()).thenReturn("test-framework.jar");
+        when(archive.openInputStream(entry)).then(new Answer<InputStream>() {
+
+            @Override
+            public InputStream answer(InvocationOnMock invocation) throws Throwable {
+                return getClass().getResourceAsStream(bundleLocation);
+            }
+
+        });
+
+        ContentPackage2FeatureModelConverter converter = spy(ContentPackage2FeatureModelConverter.class);
+
+        File testDirectory = new File(System.getProperty("testDirectory"), getClass().getName() + '_' + System.currentTimeMillis());
+        when(converter.getOutputDirectory()).thenReturn(testDirectory);
+
+        doCallRealMethod().when(converter).attach(anyString(), anyString(), anyString(), anyString(), anyString(), anyString());
+        when(converter.getArtifactDeployer()).thenReturn(new DefaultBundlesDeployer(testDirectory));
+
+        Feature feature = new Feature(new ArtifactId("org.apache.sling", "org.apache.sling.cp2fm", "0.0.1", null, null));
+        when(converter.getTargetFeature()).thenReturn(feature);
+        when(converter.getRunMode(anyString())).thenReturn(feature);
+
+        bundleEntryHandler.handle(bundleLocation, archive, entry, converter);
+
+        assertTrue(new File(testDirectory, "bundles/org/apache/felix/org.apache.felix.framework/6.0.1/org.apache.felix.framework-6.0.1.pom").exists());
+        assertTrue(new File(testDirectory, "bundles/org/apache/felix/org.apache.felix.framework/6.0.1/org.apache.felix.framework-6.0.1.jar").exists());
+
+        assertFalse(converter.getTargetFeature().getBundles().isEmpty());
+        assertEquals(1, feature.getBundles().size());
+        assertEquals("org.apache.felix:org.apache.felix.framework:6.0.1", feature.getBundles().get(0).getId().toMvnId());
+    }
+
+    @Parameters
+    public static Collection<Object[]> data() {
+        final BundleEntryHandler bundleEntryHandler = new BundleEntryHandler();
+
+        return Arrays.asList(new Object[][] {
+            { "jcr_root/apps/asd/install/test-framework-no-pom.jar", bundleEntryHandler },
+            { "jcr_root/apps/asd/install/test-framework.jar", bundleEntryHandler },
+            { "jcr_root/apps/asd/install.author/test-framework.jar", bundleEntryHandler },
+            { "jcr_root/apps/asd/install.publish/test-framework.jar", bundleEntryHandler }
+        });
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/feature/cpconverter/handlers/ConfigurationEntryHandlerTest.java b/src/test/java/org/apache/sling/feature/cpconverter/handlers/ConfigurationEntryHandlerTest.java
new file mode 100644
index 0000000..649a07f
--- /dev/null
+++ b/src/test/java/org/apache/sling/feature/cpconverter/handlers/ConfigurationEntryHandlerTest.java
@@ -0,0 +1,127 @@
+/*
+ * 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.feature.cpconverter.handlers;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doCallRealMethod;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+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.feature.ArtifactId;
+import org.apache.sling.feature.Configuration;
+import org.apache.sling.feature.Configurations;
+import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.cpconverter.ContentPackage2FeatureModelConverter;
+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 ConfigurationEntryHandlerTest {
+
+    private static final String EXPECTED_PID = "org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl";
+
+    private final String resourceConfiguration;
+
+    private final int expectedConfigurationsSize;
+
+    private final AbstractConfigurationEntryHandler configurationEntryHandler;
+
+    public ConfigurationEntryHandlerTest(String resourceConfiguration,
+                                         int expectedConfigurationsSize,
+                                         AbstractConfigurationEntryHandler configurationEntryHandler) {
+        this.resourceConfiguration = resourceConfiguration;
+        this.expectedConfigurationsSize = expectedConfigurationsSize;
+        this.configurationEntryHandler = configurationEntryHandler;
+    }
+
+    @Test
+    public void doesNotMatch() {
+        assertFalse(configurationEntryHandler.matches("/this/is/a/path/not/pointing/to/a/valid/configuration.asd"));
+    }
+
+    @Test
+    public void matches() {
+        assertTrue(resourceConfiguration, configurationEntryHandler.matches(resourceConfiguration));
+    }
+
+    @Test
+    public void parseConfiguration() throws Exception {
+        Archive archive = mock(Archive.class);
+        Entry entry = mock(Entry.class);
+
+        when(entry.getName()).thenReturn(resourceConfiguration.substring(resourceConfiguration.lastIndexOf('/') + 1));
+        when(archive.openInputStream(entry)).thenReturn(getClass().getResourceAsStream(resourceConfiguration));
+
+        Feature feature = new Feature(new ArtifactId("org.apache.sling", "org.apache.sling.cp2fm", "0.0.1", null, null));
+        ContentPackage2FeatureModelConverter converter = spy(ContentPackage2FeatureModelConverter.class);
+        when(converter.getTargetFeature()).thenReturn(feature);
+        doCallRealMethod().when(converter).addConfiguration(anyString(), anyString(), any());
+        when(converter.getRunMode(anyString())).thenReturn(feature);
+
+        configurationEntryHandler.handle(resourceConfiguration, archive, entry, converter);
+
+        Configurations configurations = converter.getTargetFeature().getConfigurations();
+
+        assertEquals(expectedConfigurationsSize, configurations.size());
+
+        if (expectedConfigurationsSize > 0) {
+            Configuration configuration = configurations.get(0);
+
+            assertTrue(configuration.getPid(), configuration.getPid().startsWith(EXPECTED_PID));
+            assertEquals("Unmatching size: " + configuration.getProperties().size(), 2, configuration.getProperties().size());
+        }
+    }
+
+    @Parameters
+    public static Collection<Object[]> data() {
+        String path = "jcr_root/apps/asd/config/";
+
+        return Arrays.asList(new Object[][] {
+            { path + EXPECTED_PID + ".empty.cfg", 0, new PropertiesConfigurationEntryHandler() },
+            { path + EXPECTED_PID + ".cfg", 1, new PropertiesConfigurationEntryHandler() },
+
+            { path + EXPECTED_PID + ".empty.cfg.json", 0, new JsonConfigurationEntryHandler() },
+            { path + EXPECTED_PID + ".cfg.json", 1, new JsonConfigurationEntryHandler() },
+
+            { path + EXPECTED_PID + ".empty.config", 0, new ConfigurationEntryHandler() },
+            { path + EXPECTED_PID + ".config", 1, new ConfigurationEntryHandler() },
+
+            { path + EXPECTED_PID + ".empty.xml", 0, new XmlConfigurationEntryHandler() },
+            { path + EXPECTED_PID + ".xml", 1, new XmlConfigurationEntryHandler() },
+
+            { path + EXPECTED_PID + ".empty.xml.cfg", 0, new PropertiesConfigurationEntryHandler() },
+            { path + EXPECTED_PID + ".xml.cfg", 1, new PropertiesConfigurationEntryHandler() },
+
+            // runmode aware folders
+            { "jcr_root/apps/asd/config.author/" + EXPECTED_PID + ".config", 1, new ConfigurationEntryHandler() },
+            { "jcr_root/apps/asd/config.publish/" + EXPECTED_PID + ".config", 1, new ConfigurationEntryHandler() },
+        });
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/feature/cpconverter/handlers/ContentPackageEntryHandlerTest.java b/src/test/java/org/apache/sling/feature/cpconverter/handlers/ContentPackageEntryHandlerTest.java
new file mode 100644
index 0000000..185bce0
--- /dev/null
+++ b/src/test/java/org/apache/sling/feature/cpconverter/handlers/ContentPackageEntryHandlerTest.java
@@ -0,0 +1,51 @@
+/*
+ * 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.feature.cpconverter.handlers;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.apache.sling.feature.cpconverter.spi.EntryHandler;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public final class ContentPackageEntryHandlerTest {
+
+    private EntryHandler contentPackageEntryhandler;
+
+    @Before
+    public void setUp() {
+        contentPackageEntryhandler = new ContentPackageEntryHandler();
+    }
+
+    @After
+    public void tearDown() {
+        contentPackageEntryhandler = null;
+    }
+
+    @Test
+    public void doesNotMatch() {
+        assertFalse(contentPackageEntryhandler.matches("/this/is/a/path/not/pointing/to/a/valid/configuration.asd"));
+    }
+
+    @Test
+    public void matches() {
+        assertTrue(contentPackageEntryhandler.matches("jcr_root/etc/packages/asd/v6/sample/asd.content-1.0.zip"));
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/feature/cpconverter/handlers/JsonConfigurationEntryHandlerTest.java b/src/test/java/org/apache/sling/feature/cpconverter/handlers/JsonConfigurationEntryHandlerTest.java
new file mode 100644
index 0000000..8289cd5
--- /dev/null
+++ b/src/test/java/org/apache/sling/feature/cpconverter/handlers/JsonConfigurationEntryHandlerTest.java
@@ -0,0 +1,46 @@
+/*
+ * 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.feature.cpconverter.handlers;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+
+import org.apache.jackrabbit.vault.fs.io.Archive;
+import org.apache.jackrabbit.vault.fs.io.Archive.Entry;
+import org.apache.sling.feature.cpconverter.ContentPackage2FeatureModelConverter;
+import org.junit.Test;
+
+public class JsonConfigurationEntryHandlerTest {
+
+    @Test(expected = IOException.class)
+    public void invalidConfigurationThrowsException() throws Exception {
+        String resourceConfiguration = "jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.INVALID.cfg.json";
+
+        Archive archive = mock(Archive.class);
+        Entry entry = mock(Entry.class);
+
+        when(entry.getName()).thenReturn(resourceConfiguration.substring(resourceConfiguration.lastIndexOf('/') + 1));
+        when(archive.openInputStream(entry)).thenReturn(getClass().getResourceAsStream(resourceConfiguration));
+
+        ContentPackage2FeatureModelConverter converter = mock(ContentPackage2FeatureModelConverter.class);
+
+        new JsonConfigurationEntryHandler().handle(resourceConfiguration, archive, entry, converter);
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/feature/cpconverter/vltpkg/VaultPackageAssemblerTest.java b/src/test/java/org/apache/sling/feature/cpconverter/vltpkg/VaultPackageAssemblerTest.java
new file mode 100644
index 0000000..d2aa3cc
--- /dev/null
+++ b/src/test/java/org/apache/sling/feature/cpconverter/vltpkg/VaultPackageAssemblerTest.java
@@ -0,0 +1,89 @@
+/*
+ * 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.feature.cpconverter.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.apache.sling.feature.cpconverter.vltpkg.VaultPackageAssembler;
+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 }
+        });
+    }
+
+}
diff --git a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/.content.xml b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/.content.xml
new file mode 100644
index 0000000..54fbe67
--- /dev/null
+++ b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/.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" xmlns:rep="internal"
+    jcr:mixinTypes="[rep:AccessControllable,rep:RepoAccessControllable]"
+    jcr:primaryType="rep:root"
+    sling:resourceType="sling:redirect"
+    sling:target="/index.html"/>
diff --git a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config.author/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.config b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config.author/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.config
new file mode 100644
index 0000000..4c50ea3
--- /dev/null
+++ b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config.author/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.config
@@ -0,0 +1,17 @@
+# 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.
+
+user.default="admin"
+user.mapping=["com.adobe.acs.acs-aem-samples-bundle\=admin","com.adobe.acs.acs-aem-samples-bundle:sample-service\=oauthservice"]
diff --git a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config.publish/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.config b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config.publish/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.config
new file mode 100644
index 0000000..4c50ea3
--- /dev/null
+++ b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config.publish/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.config
@@ -0,0 +1,17 @@
+# 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.
+
+user.default="admin"
+user.mapping=["com.adobe.acs.acs-aem-samples-bundle\=admin","com.adobe.acs.acs-aem-samples-bundle:sample-service\=oauthservice"]
diff --git a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.INVALID.cfg.json b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.INVALID.cfg.json
new file mode 100644
index 0000000..fe51488
--- /dev/null
+++ b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.INVALID.cfg.json
@@ -0,0 +1 @@
+[]
diff --git a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.cfg b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.cfg
new file mode 100644
index 0000000..bf91183
--- /dev/null
+++ b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.cfg
@@ -0,0 +1,17 @@
+# 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.
+
+user.default=admin
+user.mapping=[com.adobe.acs.acs-aem-samples-bundle=admin,com.adobe.acs.acs-aem-samples-bundle:sample-service=oauthservice]
diff --git a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.cfg.json b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.cfg.json
new file mode 100644
index 0000000..98e506c
--- /dev/null
+++ b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.cfg.json
@@ -0,0 +1,7 @@
+{
+    "user.default":"admin",
+    "user.mapping": [
+        "com.adobe.acs.acs-aem-samples-bundle=admin",
+        "com.adobe.acs.acs-aem-samples-bundle:sample-service=oauthservice"
+    ]
+}
diff --git a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.config b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.config
new file mode 100644
index 0000000..4c50ea3
--- /dev/null
+++ b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.config
@@ -0,0 +1,17 @@
+# 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.
+
+user.default="admin"
+user.mapping=["com.adobe.acs.acs-aem-samples-bundle\=admin","com.adobe.acs.acs-aem-samples-bundle:sample-service\=oauthservice"]
diff --git a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.empty.cfg b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.empty.cfg
new file mode 100644
index 0000000..5db1a19
--- /dev/null
+++ b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.empty.cfg
@@ -0,0 +1,14 @@
+# 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/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.empty.cfg.json b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.empty.cfg.json
new file mode 100644
index 0000000..49d1a20
--- /dev/null
+++ b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.empty.cfg.json
@@ -0,0 +1,3 @@
+{
+    
+}
diff --git a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.empty.config b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.empty.config
new file mode 100644
index 0000000..5db1a19
--- /dev/null
+++ b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.empty.config
@@ -0,0 +1,14 @@
+# 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/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.empty.xml b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.empty.xml
new file mode 100644
index 0000000..6211b85
--- /dev/null
+++ b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.empty.xml
@@ -0,0 +1,19 @@
+<?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:primaryType="sling:OsgiConfig"/>
diff --git a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.empty.xml.cfg b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.empty.xml.cfg
new file mode 100644
index 0000000..87a44e8
--- /dev/null
+++ b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.empty.xml.cfg
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
+<!--
+ 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.
+-->
+<properties />
diff --git a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.xml b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.xml
new file mode 100644
index 0000000..f23afc9
--- /dev/null
+++ b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.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 xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0"
+    jcr:primaryType="sling:OsgiConfig"
+    user.default="admin"
+    user.mapping="[com.adobe.acs.acs-aem-samples-bundle=admin,com.adobe.acs.acs-aem-samples-bundle:sample-service=oauthservice]"/>
diff --git a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.xml.cfg b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.xml.cfg
new file mode 100644
index 0000000..05ce515
--- /dev/null
+++ b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.xml.cfg
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
+<!--
+ 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.
+-->
+<properties>
+  <entry key="user.default">admin</entry>
+  <entry key="user.mapping">[com.adobe.acs.acs-aem-samples-bundle=admin,com.adobe.acs.acs-aem-samples-bundle:sample-service=oauthservice]</entry>
+</properties>
diff --git a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/install.author/test-framework.jar b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/install.author/test-framework.jar
new file mode 100644
index 0000000..9ce022d
Binary files /dev/null and b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/install.author/test-framework.jar differ
diff --git a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/install.publish/test-framework.jar b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/install.publish/test-framework.jar
new file mode 100644
index 0000000..9ce022d
Binary files /dev/null and b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/install.publish/test-framework.jar differ
diff --git a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/install/test-framework-no-pom.jar b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/install/test-framework-no-pom.jar
new file mode 100644
index 0000000..cb3116e
Binary files /dev/null and b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/install/test-framework-no-pom.jar differ
diff --git a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/install/test-framework.jar b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/install/test-framework.jar
new file mode 100644
index 0000000..9ce022d
Binary files /dev/null and b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/install/test-framework.jar differ
diff --git a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/asd/.content.xml b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/asd/.content.xml
new file mode 100644
index 0000000..0d5ab1d
--- /dev/null
+++ b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/asd/.content.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with this
+ work for additional information regarding copyright ownership. The ASF
+ licenses this file to You under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ License for the specific language governing permissions and limitations under
+ the License.
+-->
+<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:rep="internal"
+    jcr:mixinTypes="[rep:AccessControllable]"
+    jcr:primaryType="sling:Folder"/>
diff --git a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/asd/public/.content.xml b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/asd/public/.content.xml
new file mode 100644
index 0000000..0589bcb
--- /dev/null
+++ b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/asd/public/.content.xml
@@ -0,0 +1,19 @@
+<?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:primaryType="sling:Folder"/>
diff --git a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/asd/public/_rep_policy.xml b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/asd/public/_rep_policy.xml
new file mode 100644
index 0000000..2b1119a
--- /dev/null
+++ b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/asd/public/_rep_policy.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with this
+ work for additional information regarding copyright ownership. The ASF
+ licenses this file to You under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ License for the specific language governing permissions and limitations under
+ the License.
+-->
+<jcr:root xmlns:crx="http://www.day.com/crx/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:rep="internal"
+    jcr:primaryType="rep:ACL">
+    <allow
+        jcr:primaryType="rep:GrantACE"
+        rep:principalName="everyone"
+        rep:privileges="{Name}[jcr:read]"/>
+</jcr:root>
\ No newline at end of file
diff --git a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/asd/public/license.txt b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/asd/public/license.txt
new file mode 100644
index 0000000..805f6a4
--- /dev/null
+++ b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/asd/public/license.txt
@@ -0,0 +1,14 @@
+ 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/org/apache/sling/feature/cpconverter/test-content-package-unacceptable.zip b/src/test/resources/org/apache/sling/feature/cpconverter/test-content-package-unacceptable.zip
new file mode 100644
index 0000000..840ee6d
Binary files /dev/null and b/src/test/resources/org/apache/sling/feature/cpconverter/test-content-package-unacceptable.zip differ
diff --git a/src/test/resources/org/apache/sling/feature/cpconverter/test-content-package.zip b/src/test/resources/org/apache/sling/feature/cpconverter/test-content-package.zip
new file mode 100644
index 0000000..cfe6a94
Binary files /dev/null and b/src/test/resources/org/apache/sling/feature/cpconverter/test-content-package.zip differ


[sling-org-apache-sling-feature-cpconverter] 04/29: 'groupId' and 'artifactId' properties not always available in META-INF/vault/properties.xml, replaced with canonical 'name' and 'group'

Posted by si...@apache.org.
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-org-apache-sling-feature-cpconverter.git

commit d69e11d1f7aa52617b60995fc5e781e9d06f9097
Author: stripodi <st...@192.168.1.111>
AuthorDate: Thu Apr 11 16:51:18 2019 +0200

    'groupId' and 'artifactId' properties not always available in
    META-INF/vault/properties.xml, replaced with canonical 'name' and
    'group'
---
 .../ContentPackage2FeatureModelConverter.java      | 27 +++++++++++++++-------
 .../cpconverter/handlers/BundleEntryHandler.java   |  6 +++--
 .../cpconverter/vltpkg/VaultPackageAssembler.java  |  4 ----
 .../ContentPackage2FeatureModelConverterTest.java  | 10 ++++----
 4 files changed, 28 insertions(+), 19 deletions(-)

diff --git a/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java b/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
index 6fde8d4..71ffa8e 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
@@ -16,6 +16,8 @@
  */
 package org.apache.sling.feature.cpconverter;
 
+import static java.util.Objects.requireNonNull;
+
 import java.io.File;
 import java.io.FileWriter;
 import java.util.Dictionary;
@@ -56,16 +58,14 @@ public class ContentPackage2FeatureModelConverter {
 
     public static final String ZIP_TYPE = "zip";
 
-    public static final String NAME_GROUP_ID = "groupId";
-
-    public static final String NAME_ARTIFACT_ID = "artifactId";
-
     public static final String FEATURE_CLASSIFIER = "cp2fm-converted-feature";
 
     private static final String SLING_OSGI_FEATURE_TILE_TYPE = "slingosgifeature";
 
     private static final String JSON_FILE_EXTENSION = ".json";
 
+    private static final String DEFEAULT_VERSION = "0.0.0";
+
     private final Logger logger = LoggerFactory.getLogger(getClass());
 
     private final PackageManager packageManager = new PackageManagerImpl();
@@ -193,12 +193,23 @@ public class ContentPackage2FeatureModelConverter {
             mainPackageAssembler = VaultPackageAssembler.create(vaultPackage);
 
             PackageProperties packageProperties = vaultPackage.getProperties();
-            String groupId = packageProperties.getProperty(NAME_GROUP_ID);
-            String artifactId = packageProperties.getProperty(NAME_ARTIFACT_ID);
+            String group = requireNonNull(packageProperties.getProperty(PackageProperties.NAME_GROUP),
+                                          PackageProperties.NAME_GROUP
+                                          + " property not found in content-package "
+                                          + contentPackage
+                                          + ", please check META-INF/vault/properties.xml");
+            String name = requireNonNull(packageProperties.getProperty(PackageProperties.NAME_NAME),
+                                         PackageProperties.NAME_NAME
+                                         + " property not found in content-package "
+                                         + contentPackage
+                                         + ", please check META-INF/vault/properties.xml");
             String version = packageProperties.getProperty(PackageProperties.NAME_VERSION);
+            if (version == null || version.isEmpty()) {
+                version = DEFEAULT_VERSION;
+            }
 
-            targetFeature = new Feature(new ArtifactId(groupId,
-                                                       artifactId,
+            targetFeature = new Feature(new ArtifactId(group.replace('/', '.'),
+                                                       name,
                                                        version,
                                                        FEATURE_CLASSIFIER,
                                                        SLING_OSGI_FEATURE_TILE_TYPE));
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandler.java b/src/main/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandler.java
index 884bf06..283cf12 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandler.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandler.java
@@ -17,8 +17,6 @@
 package org.apache.sling.feature.cpconverter.handlers;
 
 import static org.apache.jackrabbit.vault.packaging.PackageProperties.NAME_VERSION;
-import static org.apache.sling.feature.cpconverter.ContentPackage2FeatureModelConverter.NAME_ARTIFACT_ID;
-import static org.apache.sling.feature.cpconverter.ContentPackage2FeatureModelConverter.NAME_GROUP_ID;
 import static org.apache.sling.feature.cpconverter.ContentPackage2FeatureModelConverter.POM_TYPE;
 
 import java.io.ByteArrayInputStream;
@@ -43,6 +41,10 @@ import org.slf4j.LoggerFactory;
 
 public final class BundleEntryHandler extends AbstractRegexEntryHandler {
 
+    private static final String NAME_GROUP_ID = "groupId";
+
+    private static final String NAME_ARTIFACT_ID = "artifactId";
+
     private static final String JAR_TYPE = "jar";
 
     private final Logger logger = LoggerFactory.getLogger(getClass());
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/vltpkg/VaultPackageAssembler.java b/src/main/java/org/apache/sling/feature/cpconverter/vltpkg/VaultPackageAssembler.java
index b46ec61..c68f788 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/vltpkg/VaultPackageAssembler.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/vltpkg/VaultPackageAssembler.java
@@ -17,8 +17,6 @@
 package org.apache.sling.feature.cpconverter.vltpkg;
 
 import static org.apache.sling.feature.cpconverter.ContentPackage2FeatureModelConverter.FEATURE_CLASSIFIER;
-import static org.apache.sling.feature.cpconverter.ContentPackage2FeatureModelConverter.NAME_ARTIFACT_ID;
-import static org.apache.sling.feature.cpconverter.ContentPackage2FeatureModelConverter.NAME_GROUP_ID;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -80,8 +78,6 @@ public final class VaultPackageAssembler implements EntryHandler {
                 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);
diff --git a/src/test/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverterTest.java b/src/test/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverterTest.java
index 08ec0b2..3c16634 100644
--- a/src/test/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverterTest.java
+++ b/src/test/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverterTest.java
@@ -125,24 +125,24 @@ public class ContentPackage2FeatureModelConverterTest {
 
         verifyFeatureFile(outputDirectory,
                           "asd.retail.all.json",
-                          "org.apache.sling:asd.retail.all:slingosgifeature:cp2fm-converted-feature:0.0.1",
+                          "asd.sample:asd.retail.all:slingosgifeature:cp2fm-converted-feature:0.0.1",
                           Arrays.asList("org.apache.felix:org.apache.felix.framework:6.0.1"),
                           Arrays.asList("org.apache.sling.commons.log.LogManager.factory.config-asd-retail"),
-                          Arrays.asList("org.apache.sling:asd.retail.all:zip:cp2fm-converted-feature:0.0.1"));
+                          Arrays.asList("asd.sample:asd.retail.all:zip:cp2fm-converted-feature:0.0.1"));
         verifyFeatureFile(outputDirectory,
                           "asd.retail.all-author.json",
-                          "org.apache.sling:asd.retail.all:slingosgifeature:cp2fm-converted-feature-author:0.0.1",
+                          "asd.sample:asd.retail.all:slingosgifeature:cp2fm-converted-feature-author:0.0.1",
                           Arrays.asList("org.apache.sling:org.apache.sling.api:2.20.0"),
                           Collections.emptyList(),
                           Collections.emptyList());
         verifyFeatureFile(outputDirectory,
                           "asd.retail.all-publish.json",
-                          "org.apache.sling:asd.retail.all:slingosgifeature:cp2fm-converted-feature-publish:0.0.1",
+                          "asd.sample:asd.retail.all:slingosgifeature:cp2fm-converted-feature-publish:0.0.1",
                           Arrays.asList("org.apache.sling:org.apache.sling.models.api:1.3.8"),
                           Arrays.asList("org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended-asd-retail"),
                           Collections.emptyList());
 
-        ZipFile zipFile = new ZipFile(new File(outputDirectory, "bundles/org/apache/sling/asd.retail.all/0.0.1/asd.retail.all-0.0.1-cp2fm-converted-feature.zip"));
+        ZipFile zipFile = new ZipFile(new File(outputDirectory, "bundles/asd/sample/asd.retail.all/0.0.1/asd.retail.all-0.0.1-cp2fm-converted-feature.zip"));
         for (String expectedEntry : new String[] {
                 "jcr_root/content/asd/.content.xml",
                 "jcr_root/content/asd/resources.xml",


[sling-org-apache-sling-feature-cpconverter] 15/29: SLING-8371 - [cp2fm] map rep:ACL JCR nodes to repoinit Feature extension

Posted by si...@apache.org.
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-org-apache-sling-feature-cpconverter.git

commit 8e6b9097328d9fe5d9c62beae0460e02a8d4269c
Author: stripodi <st...@192.168.1.111>
AuthorDate: Wed Apr 24 15:51:45 2019 +0200

    SLING-8371 - [cp2fm] map rep:ACL JCR nodes to repoinit Feature extension
    
    initial implementation
    old System User refactored in order to use the new ACL Manager
---
 .../ContentPackage2FeatureModelConverter.java      |  31 ++--
 .../apache/sling/feature/cpconverter/acl/Acl.java  |  59 ++++++++
 .../sling/feature/cpconverter/acl/AclManager.java  |  84 +++++++++++
 .../feature/cpconverter/acl/package-info.java      |  21 +++
 .../handlers/AbstractJcrNodeParser.java            |   6 +-
 .../handlers/RepPolicyEntryHandler.java            | 143 ++++++++++++++++++
 .../handlers/SystemUsersEntryHandler.java          |   9 +-
 .../handlers/XmlConfigurationEntryHandler.java     |  13 +-
 ...ache.sling.feature.cpconverter.spi.EntryHandler |   1 +
 .../handlers/RepPolicyEntryHandlerTest.java        | 161 +++++++++++++++++++++
 .../handlers/SystemUsersEntryHandlerTest.java      |   5 +-
 .../vltpkg/VaultPackageAssemblerTest.java          |   1 -
 .../handlers/jcr_root/asd/public/_rep_policy.xml   |  38 ++++-
 13 files changed, 532 insertions(+), 40 deletions(-)

diff --git a/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java b/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
index 9cf9b1e..378ac20 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
@@ -41,6 +41,7 @@ import org.apache.sling.feature.Extension;
 import org.apache.sling.feature.ExtensionType;
 import org.apache.sling.feature.Extensions;
 import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.cpconverter.acl.AclManager;
 import org.apache.sling.feature.cpconverter.spi.BundlesDeployer;
 import org.apache.sling.feature.cpconverter.spi.EntryHandler;
 import org.apache.sling.feature.cpconverter.vltpkg.VaultPackageAssembler;
@@ -66,8 +67,6 @@ public class ContentPackage2FeatureModelConverter {
 
     private static final String DEFEAULT_VERSION = "0.0.0";
 
-    private static final String REPOINIT = "repoinit";
-
     private final Logger logger = LoggerFactory.getLogger(getClass());
 
     private final PackageManager packageManager = new PackageManagerImpl();
@@ -76,6 +75,8 @@ public class ContentPackage2FeatureModelConverter {
 
     private final Map<String, Feature> runModes = new HashMap<>();
 
+    private final AclManager aclManager = new AclManager();
+
     private final RegexBasedResourceFilter filter = new RegexBasedResourceFilter();
 
     private BundlesDeployer artifactDeployer;
@@ -95,7 +96,7 @@ public class ContentPackage2FeatureModelConverter {
     private VaultPackageAssembler mainPackageAssembler = null;
 
     private String id;
-    
+
     private String idOverride;
 
     public ContentPackage2FeatureModelConverter setStrictValidation(boolean strictValidation) {
@@ -152,12 +153,15 @@ public class ContentPackage2FeatureModelConverter {
         this.id = id;
         return this;
     }
-    
+
     public ContentPackage2FeatureModelConverter setIdOverride(String id) {
         this.idOverride = id;
         return this;
     }
 
+    public AclManager getAclManager() {
+        return aclManager;
+    }
 
     public Feature getRunMode(String runMode) {
         if (getTargetFeature() == null) {
@@ -305,7 +309,9 @@ public class ContentPackage2FeatureModelConverter {
 
             // finally serialize the Feature Model(s) file(s)
 
-            seralize(targetFeature, null);
+            aclManager.addRepoinitExtension(getTargetFeature());
+
+            seralize(getTargetFeature(), null);
 
             if (!runModes.isEmpty()) {
                 for (java.util.Map.Entry<String, Feature> runmodeEntry : runModes.entrySet()) {
@@ -315,21 +321,6 @@ public class ContentPackage2FeatureModelConverter {
         }
     }
 
-    public void addRepoinitStatement(String format, Object...args) {
-        Extension repoInitExtension = getTargetFeature().getExtensions().getByName(REPOINIT);
-        if (repoInitExtension == null) {
-            repoInitExtension = new Extension(ExtensionType.TEXT, REPOINIT, true);
-            getTargetFeature().getExtensions().add(repoInitExtension);
-        }
-
-        String statement = String.format(format, args);
-        if (repoInitExtension.getText() == null) {
-            repoInitExtension.setText(statement);
-        } else {
-            repoInitExtension.setText(repoInitExtension.getText() + '\n' + statement);
-        }
-    }
-
     public void addConfiguration(String runMode, String pid, Dictionary<String, Object> configurationProperties) {
         if (!mergeConfigurations) {
             checkConfigurationExist(getTargetFeature(), pid);
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/acl/Acl.java b/src/main/java/org/apache/sling/feature/cpconverter/acl/Acl.java
new file mode 100644
index 0000000..0f483ff
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/cpconverter/acl/Acl.java
@@ -0,0 +1,59 @@
+/*
+ * 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.feature.cpconverter.acl;
+
+import java.util.Formatter;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Simple single ACL statement representation.
+ */
+public final class Acl {
+
+    private final String operation;
+
+    private final String privileges;
+
+    private final String path;
+
+    private final List<String> restrictions = new LinkedList<>();
+
+    protected Acl(String operation, String privileges, String path) {
+        this.operation = operation;
+        this.privileges = privileges;
+        this.path = path;
+    }
+
+    public void addRestriction(String restriction) {
+        if (restriction != null && !restriction.isEmpty()) {
+            restrictions.add(restriction);
+        }
+    }
+
+    protected void addAclStatement(Formatter formatter) {
+        formatter.format("%s %s on %s", operation, privileges, path);
+
+        if (!restrictions.isEmpty()) {
+            formatter.format(" restriction(%s)", restrictions.stream().collect(Collectors.joining(",")));
+        }
+
+        formatter.format("%n");
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/acl/AclManager.java b/src/main/java/org/apache/sling/feature/cpconverter/acl/AclManager.java
new file mode 100644
index 0000000..54564c9
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/cpconverter/acl/AclManager.java
@@ -0,0 +1,84 @@
+/*
+ * 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.feature.cpconverter.acl;
+
+import java.util.Formatter;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.sling.feature.Extension;
+import org.apache.sling.feature.ExtensionType;
+import org.apache.sling.feature.Feature;
+
+/**
+ * The Manager able to collect and build System Users and related ACL policies.
+ */
+public final class AclManager {
+
+    private final Set<String> systemUsers = new LinkedHashSet<>();
+
+    private final Map<String, List<Acl>> acls = new HashMap<>();
+
+    public boolean addSystemUser(String systemUser) {
+        if (systemUser != null && !systemUser.isEmpty()) {
+            return systemUsers.add(systemUser);
+        }
+        return false;
+    }
+
+    public Acl addAcl(String systemUser, String operation, String privileges, String path) {
+        Acl acl = new Acl(operation, privileges, path);
+        acls.computeIfAbsent(systemUser, k -> new LinkedList<>()).add(acl);
+        return acl;
+    }
+
+    public void addRepoinitExtension(Feature feature) {
+        if (systemUsers.isEmpty()) {
+            return;
+        }
+
+        Extension repoInitExtension = new Extension(ExtensionType.TEXT, Extension.EXTENSION_NAME_REPOINIT, true);
+
+        Formatter formatter = new Formatter();
+
+        for (String systemUser : systemUsers) {
+            formatter.format("create service user %s%n", systemUser);
+
+            List<Acl> authorizations = acls.get(systemUser);
+            if (authorizations != null && !authorizations.isEmpty()) {
+                formatter.format("set ACL for %s%n", systemUser);
+
+                for (Acl authorization : authorizations) {
+                    authorization.addAclStatement(formatter);
+                }
+
+                formatter.format("end%n");
+            }
+        }
+
+        String text = formatter.toString();
+        formatter.close();
+        repoInitExtension.setText(text);
+
+        feature.getExtensions().add(repoInitExtension);
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/acl/package-info.java b/src/main/java/org/apache/sling/feature/cpconverter/acl/package-info.java
new file mode 100644
index 0000000..0ef4e34
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/cpconverter/acl/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.
+ */
+
+/**
+ * Small utility set to handle System Users and related ACL policies.
+ */
+package org.apache.sling.feature.cpconverter.acl;
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/handlers/AbstractJcrNodeParser.java b/src/main/java/org/apache/sling/feature/cpconverter/handlers/AbstractJcrNodeParser.java
index 12e3089..e346ff3 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/handlers/AbstractJcrNodeParser.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/handlers/AbstractJcrNodeParser.java
@@ -27,7 +27,7 @@ import org.xml.sax.Attributes;
 import org.xml.sax.SAXException;
 import org.xml.sax.helpers.DefaultHandler;
 
-abstract class AbstractJcrNodeParser extends DefaultHandler {
+abstract class AbstractJcrNodeParser<O> extends DefaultHandler {
 
     private static final String JCR_ROOT = "jcr:root";
 
@@ -45,7 +45,7 @@ abstract class AbstractJcrNodeParser extends DefaultHandler {
     }
 
     @Override
-    public final void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
+    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
         String primaryType = attributes.getValue(JCR_PRIMARYTYPE);
 
         if (JCR_ROOT.equals(qName) && this.primaryType.equals(primaryType)) {
@@ -55,4 +55,6 @@ abstract class AbstractJcrNodeParser extends DefaultHandler {
 
     protected abstract void onJcrRootElement(String uri, String localName, String qName, Attributes attributes) throws SAXException;
 
+    protected abstract O getParsingResult();
+
 }
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/handlers/RepPolicyEntryHandler.java b/src/main/java/org/apache/sling/feature/cpconverter/handlers/RepPolicyEntryHandler.java
new file mode 100644
index 0000000..6dad716
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/cpconverter/handlers/RepPolicyEntryHandler.java
@@ -0,0 +1,143 @@
+/*
+ * 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.feature.cpconverter.handlers;
+
+import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE;
+
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Stack;
+import java.util.regex.Matcher;
+
+import org.apache.jackrabbit.vault.fs.io.Archive;
+import org.apache.jackrabbit.vault.fs.io.Archive.Entry;
+import org.apache.sling.feature.cpconverter.ContentPackage2FeatureModelConverter;
+import org.apache.sling.feature.cpconverter.acl.Acl;
+import org.apache.sling.feature.cpconverter.acl.AclManager;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+
+public final class RepPolicyEntryHandler extends AbstractRegexEntryHandler {
+
+    public RepPolicyEntryHandler() {
+        super("(jcr_root)?(/.+)/_rep_policy.xml");
+    }
+
+    @Override
+    public void handle(String path, Archive archive, Entry entry, ContentPackage2FeatureModelConverter converter)
+            throws Exception {
+        Matcher matcher = getPattern().matcher(path);
+        // we are pretty sure it matches, here
+        if (matcher.matches()) {
+            path = matcher.group(2);
+        } else {
+            throw new IllegalStateException("Something went terribly wrong: pattern '"
+                                            + getPattern().pattern()
+                                            + "' should have matched already with path '"
+                                            + path
+                                            + "' but it does not, currently");
+        }
+
+        RepPolicyParser systemUserParser = new RepPolicyParser(path, converter.getAclManager());
+        try (InputStream input = archive.openInputStream(entry)) {
+            systemUserParser.parse(input);
+        }
+    }
+
+    private static final class RepPolicyParser extends AbstractJcrNodeParser<Void> {
+
+        private static final String REP_ACL = "rep:ACL";
+
+        private static final String REP_GRANT_ACE = "rep:GrantACE";
+
+        private static final String REP_DENY_ACE = "rep:DenyACE";
+
+        private static final String REP_RESTRICTIONS = "rep:Restrictions";
+
+        private static final String REP_PRINCIPAL_NAME = "rep:principalName";
+
+        private static final String REP_PRIVILEGES = "rep:privileges";
+
+        private static final String REP_GLOB = "rep:glob";
+
+        private static final Map<String, String> operations = new HashMap<>();
+
+        static {
+            operations.put(REP_GRANT_ACE, "allow");
+            operations.put(REP_DENY_ACE, "deny");
+        }
+
+        private final Stack<Acl> acls = new Stack<>();
+
+        private final String path;
+
+        private final AclManager aclManager;
+
+        private boolean onRepAclNode = false;
+
+        public RepPolicyParser(String path, AclManager aclManager) {
+            super(REP_ACL);
+            this.path = path;
+            this.aclManager = aclManager;
+        }
+
+        @Override
+        public void startElement(String uri, String localName, String qName, Attributes attributes)
+                throws SAXException {
+            if (onRepAclNode) {
+                String primaryType = attributes.getValue(JCR_PRIMARYTYPE);
+                if (REP_GRANT_ACE.equals(primaryType) || REP_DENY_ACE.equals(primaryType)) {
+                    String principalName = attributes.getValue(REP_PRINCIPAL_NAME);
+
+                    String operation = operations.get(primaryType);
+
+                    String privileges = attributes.getValue(REP_PRIVILEGES);
+                    int beginIndex = privileges.indexOf('[') + 1;
+                    int endIndex = privileges.indexOf(']');
+                    privileges = privileges.substring(beginIndex, endIndex);
+
+                    acls.add(aclManager.addAcl(principalName, operation, privileges, path));
+                } else if (REP_RESTRICTIONS.equals(primaryType) && !acls.isEmpty()) {
+                    String restriction = attributes.getValue(REP_GLOB);
+                    acls.peek().addRestriction(restriction);
+                }
+            } else {
+                super.startElement(uri, localName, qName, attributes);
+            }
+        }
+
+        @Override
+        public void endElement(String uri, String localName, String qName) throws SAXException {
+            if (onRepAclNode && !acls.isEmpty()) {
+                acls.pop();
+            }
+        }
+
+        @Override
+        protected void onJcrRootElement(String uri, String localName, String qName, Attributes attributes) {
+            onRepAclNode = true;
+        }
+
+        @Override
+        protected Void getParsingResult() {
+            return null;
+        }
+
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/handlers/SystemUsersEntryHandler.java b/src/main/java/org/apache/sling/feature/cpconverter/handlers/SystemUsersEntryHandler.java
index b451e8b..7fe64aa 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/handlers/SystemUsersEntryHandler.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/handlers/SystemUsersEntryHandler.java
@@ -38,7 +38,7 @@ public final class SystemUsersEntryHandler extends AbstractRegexEntryHandler {
         }
     }
 
-    private static final class SystemUserParser extends AbstractJcrNodeParser {
+    private static final class SystemUserParser extends AbstractJcrNodeParser<Void> {
 
         private final static String REP_SYSTEM_USER = "rep:SystemUser";
 
@@ -55,10 +55,15 @@ public final class SystemUsersEntryHandler extends AbstractRegexEntryHandler {
         protected void onJcrRootElement(String uri, String localName, String qName, Attributes attributes) {
             String authorizableId = attributes.getValue(REP_AUTHORIZABLE_ID);
             if (authorizableId != null && !authorizableId.isEmpty()) {
-                converter.addRepoinitStatement("create service user %s", authorizableId);
+                converter.getAclManager().addSystemUser(authorizableId);
             }
         }
 
+        @Override
+        protected Void getParsingResult() {
+            return null;
+        }
+
     }
 
 }
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/handlers/XmlConfigurationEntryHandler.java b/src/main/java/org/apache/sling/feature/cpconverter/handlers/XmlConfigurationEntryHandler.java
index 5188b24..d1b1919 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/handlers/XmlConfigurationEntryHandler.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/handlers/XmlConfigurationEntryHandler.java
@@ -34,19 +34,15 @@ public final class XmlConfigurationEntryHandler extends AbstractConfigurationEnt
     protected Dictionary<String, Object> parseConfiguration(String name, InputStream input) throws Exception {
         JcrConfigurationHandler configurationHandler = new JcrConfigurationHandler();
         configurationHandler.parse(input);
-        return configurationHandler.getConfiguration();
+        return configurationHandler.getParsingResult();
     }
 
-    private static final class JcrConfigurationHandler extends AbstractJcrNodeParser {
+    private static final class JcrConfigurationHandler extends AbstractJcrNodeParser<Dictionary<String, Object>> {
 
         private static final String SLING_OSGICONFIG = "sling:OsgiConfig";
 
         private final Dictionary<String, Object> configuration = new Hashtable<>();
 
-        public Dictionary<String, Object> getConfiguration() {
-            return configuration;
-        }
-
         public JcrConfigurationHandler() {
             super(SLING_OSGICONFIG);
         }
@@ -75,6 +71,11 @@ public final class XmlConfigurationEntryHandler extends AbstractConfigurationEnt
             }
         }
 
+        @Override
+        protected Dictionary<String, Object> getParsingResult() {
+            return configuration;
+        }
+
     }
 
 }
diff --git a/src/main/resources/META-INF/services/org.apache.sling.feature.cpconverter.spi.EntryHandler b/src/main/resources/META-INF/services/org.apache.sling.feature.cpconverter.spi.EntryHandler
index b4b9ec0..53e84b4 100644
--- a/src/main/resources/META-INF/services/org.apache.sling.feature.cpconverter.spi.EntryHandler
+++ b/src/main/resources/META-INF/services/org.apache.sling.feature.cpconverter.spi.EntryHandler
@@ -3,5 +3,6 @@ org.apache.sling.feature.cpconverter.handlers.ConfigurationEntryHandler
 org.apache.sling.feature.cpconverter.handlers.ContentPackageEntryHandler
 org.apache.sling.feature.cpconverter.handlers.JsonConfigurationEntryHandler
 org.apache.sling.feature.cpconverter.handlers.PropertiesConfigurationEntryHandler
+org.apache.sling.feature.cpconverter.handlers.RepPolicyEntryHandler
 org.apache.sling.feature.cpconverter.handlers.SystemUsersEntryHandler
 org.apache.sling.feature.cpconverter.handlers.XmlConfigurationEntryHandler
diff --git a/src/test/java/org/apache/sling/feature/cpconverter/handlers/RepPolicyEntryHandlerTest.java b/src/test/java/org/apache/sling/feature/cpconverter/handlers/RepPolicyEntryHandlerTest.java
new file mode 100644
index 0000000..ecf5419
--- /dev/null
+++ b/src/test/java/org/apache/sling/feature/cpconverter/handlers/RepPolicyEntryHandlerTest.java
@@ -0,0 +1,161 @@
+/*
+ * 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.feature.cpconverter.handlers;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+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 org.apache.jackrabbit.vault.fs.io.Archive;
+import org.apache.jackrabbit.vault.fs.io.Archive.Entry;
+import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.Extension;
+import org.apache.sling.feature.ExtensionType;
+import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.cpconverter.ContentPackage2FeatureModelConverter;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public final class RepPolicyEntryHandlerTest {
+
+    private RepPolicyEntryHandler handler;
+
+    @Before
+    public void setUp() {
+        handler = new RepPolicyEntryHandler();
+    }
+
+    @After
+    public void tearDown() {
+        handler = null;
+    }
+
+    @Test
+    public void doesNotMatch() {
+        assertFalse(handler.matches("/this/is/a/path/not/pointing/to/a/valid/policy.xml"));
+    }
+
+    @Test
+    public void matches() {
+        assertTrue(handler.matches("/home/users/system/asd-share-commons/asd-index-definition-reader/_rep_policy.xml"));
+        assertTrue(handler.matches("jcr_root/home/users/system/asd-share-commons/asd-index-definition-reader/_rep_policy.xml"));
+    }
+
+    @Test
+    public void parseAcl() throws Exception {
+        Extension repoinitExtension = parseAndSetRepoinit("acs-commons-ensure-oak-index-service",
+                                                          "acs-commons-dispatcher-flush-service",
+                                                          "acs-commons-package-replication-status-event-service",
+                                                          "acs-commons-ensure-service-user-service",
+                                                          "acs-commons-automatic-package-replicator-service",
+                                                          "acs-commons-on-deploy-scripts-service");
+        assertNotNull(repoinitExtension);
+        assertEquals(ExtensionType.TEXT, repoinitExtension.getType());
+
+        String expected = "create service user acs-commons-ensure-oak-index-service\n" + 
+                "set ACL for acs-commons-ensure-oak-index-service\n" + 
+                "allow jcr:read,rep:write,rep:indexDefinitionManagement on /asd/public restriction(*/oak:index/*)\n" + 
+                "end\n" + 
+                "create service user acs-commons-dispatcher-flush-service\n" + 
+                "set ACL for acs-commons-dispatcher-flush-service\n" + 
+                "allow jcr:read,crx:replicate,jcr:removeNode on /asd/public\n" + 
+                "end\n" + 
+                "create service user acs-commons-package-replication-status-event-service\n" + 
+                "set ACL for acs-commons-package-replication-status-event-service\n" + 
+                "allow jcr:read,rep:write,jcr:readAccessControl,jcr:modifyAccessControl on /asd/public\n" + 
+                "end\n" + 
+                "create service user acs-commons-ensure-service-user-service\n" + 
+                "set ACL for acs-commons-ensure-service-user-service\n" + 
+                "allow jcr:read,rep:write,jcr:readAccessControl,jcr:modifyAccessControl on /asd/public\n" + 
+                "end\n" + 
+                "create service user acs-commons-automatic-package-replicator-service\n" + 
+                "set ACL for acs-commons-automatic-package-replicator-service\n" + 
+                "allow jcr:read on /asd/public\n" + 
+                "end\n" + 
+                "create service user acs-commons-on-deploy-scripts-service\n" + 
+                "set ACL for acs-commons-on-deploy-scripts-service\n" + 
+                "allow jcr:read on /asd/public\n" + 
+                "end\n";
+        String actual = repoinitExtension.getText();
+        assertEquals(expected, actual);
+    }
+
+    @Test
+    public void notDeclaredSystemUsersWontHaveAclSettings() throws Exception {
+        Extension repoinitExtension = parseAndSetRepoinit("acs-commons-package-replication-status-event-service",
+                                                          "acs-commons-ensure-service-user-service",
+                                                          "acs-commons-automatic-package-replicator-service",
+                                                          "acs-commons-on-deploy-scripts-service");
+        assertNotNull(repoinitExtension);
+        assertEquals(ExtensionType.TEXT, repoinitExtension.getType());
+
+        String expected = "create service user acs-commons-package-replication-status-event-service\n" + 
+                "set ACL for acs-commons-package-replication-status-event-service\n" + 
+                "allow jcr:read,rep:write,jcr:readAccessControl,jcr:modifyAccessControl on /asd/public\n" + 
+                "end\n" + 
+                "create service user acs-commons-ensure-service-user-service\n" + 
+                "set ACL for acs-commons-ensure-service-user-service\n" + 
+                "allow jcr:read,rep:write,jcr:readAccessControl,jcr:modifyAccessControl on /asd/public\n" + 
+                "end\n" + 
+                "create service user acs-commons-automatic-package-replicator-service\n" + 
+                "set ACL for acs-commons-automatic-package-replicator-service\n" + 
+                "allow jcr:read on /asd/public\n" + 
+                "end\n" + 
+                "create service user acs-commons-on-deploy-scripts-service\n" + 
+                "set ACL for acs-commons-on-deploy-scripts-service\n" + 
+                "allow jcr:read on /asd/public\n" + 
+                "end\n";
+        String actual = repoinitExtension.getText();
+        assertEquals(expected, actual);
+    }
+
+    @Test
+    public void parseEmptyAcl() throws Exception {
+        Extension repoinitExtension = parseAndSetRepoinit();
+        assertNull(repoinitExtension);
+    }
+
+    private Extension parseAndSetRepoinit(String...systemUsers) throws Exception {
+        String path = "jcr_root/asd/public/_rep_policy.xml";
+        Archive archive = mock(Archive.class);
+        Entry entry = mock(Entry.class);
+
+        when(archive.openInputStream(entry)).thenReturn(getClass().getResourceAsStream(path));
+
+        Feature feature = new Feature(new ArtifactId("org.apache.sling", "org.apache.sling.cp2fm", "0.0.1", null, null));
+        ContentPackage2FeatureModelConverter converter = spy(ContentPackage2FeatureModelConverter.class);
+        when(converter.getTargetFeature()).thenReturn(feature);
+
+        handler.handle(path, archive, entry, converter);
+
+        if (systemUsers != null) {
+            for (String systemUser : systemUsers) {
+                converter.getAclManager().addSystemUser(systemUser);
+            }
+        }
+
+        converter.getAclManager().addRepoinitExtension(feature);
+        return feature.getExtensions().getByName(Extension.EXTENSION_NAME_REPOINIT);
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/feature/cpconverter/handlers/SystemUsersEntryHandlerTest.java b/src/test/java/org/apache/sling/feature/cpconverter/handlers/SystemUsersEntryHandlerTest.java
index 5126e0f..70270d0 100644
--- a/src/test/java/org/apache/sling/feature/cpconverter/handlers/SystemUsersEntryHandlerTest.java
+++ b/src/test/java/org/apache/sling/feature/cpconverter/handlers/SystemUsersEntryHandlerTest.java
@@ -67,7 +67,7 @@ public class SystemUsersEntryHandlerTest {
         assertNotNull(repoinitExtension);
         assertEquals(ExtensionType.TEXT, repoinitExtension.getType());
         assertTrue(repoinitExtension.isRequired());
-        assertEquals("create service user asd-share-commons-asd-index-definition-reader-service", repoinitExtension.getText());
+        assertEquals("create service user asd-share-commons-asd-index-definition-reader-service\n", repoinitExtension.getText());
     }
 
     @Test
@@ -89,7 +89,8 @@ public class SystemUsersEntryHandlerTest {
 
         systemUsersEntryHandler.handle(path, archive, entry, converter);
 
-        return feature.getExtensions().getByName("repoinit");
+        converter.getAclManager().addRepoinitExtension(feature);
+        return feature.getExtensions().getByName(Extension.EXTENSION_NAME_REPOINIT);
     }
 
 }
diff --git a/src/test/java/org/apache/sling/feature/cpconverter/vltpkg/VaultPackageAssemblerTest.java b/src/test/java/org/apache/sling/feature/cpconverter/vltpkg/VaultPackageAssemblerTest.java
index d2aa3cc..011a516 100644
--- a/src/test/java/org/apache/sling/feature/cpconverter/vltpkg/VaultPackageAssemblerTest.java
+++ b/src/test/java/org/apache/sling/feature/cpconverter/vltpkg/VaultPackageAssemblerTest.java
@@ -81,7 +81,6 @@ public class VaultPackageAssemblerTest {
         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 }
         });
     }
diff --git a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/asd/public/_rep_policy.xml b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/asd/public/_rep_policy.xml
index 2b1119a..8b639bb 100644
--- a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/asd/public/_rep_policy.xml
+++ b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/asd/public/_rep_policy.xml
@@ -15,10 +15,34 @@
  License for the specific language governing permissions and limitations under
  the License.
 -->
-<jcr:root xmlns:crx="http://www.day.com/crx/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:rep="internal"
-    jcr:primaryType="rep:ACL">
-    <allow
-        jcr:primaryType="rep:GrantACE"
-        rep:principalName="everyone"
-        rep:privileges="{Name}[jcr:read]"/>
-</jcr:root>
\ No newline at end of file
+<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:rep="internal"
+          jcr:primaryType="rep:ACL">
+    <allow0
+            jcr:primaryType="rep:GrantACE"
+            rep:principalName="acs-commons-ensure-oak-index-service"
+            rep:privileges="{Name}[jcr:read,rep:write,rep:indexDefinitionManagement]">
+        <rep:restrictions
+                jcr:primaryType="rep:Restrictions"
+                rep:glob="*/oak:index/*"/>
+    </allow0>
+    <allow1
+            jcr:primaryType="rep:GrantACE"
+            rep:principalName="acs-commons-dispatcher-flush-service"
+            rep:privileges="{Name}[jcr:read,crx:replicate,jcr:removeNode]"/>
+    <allow2
+            jcr:primaryType="rep:GrantACE"
+            rep:principalName="acs-commons-package-replication-status-event-service"
+            rep:privileges="{Name}[jcr:read,rep:write,jcr:readAccessControl,jcr:modifyAccessControl]"/>
+    <allow3
+            jcr:primaryType="rep:GrantACE"
+            rep:principalName="acs-commons-ensure-service-user-service"
+            rep:privileges="{Name}[jcr:read,rep:write,jcr:readAccessControl,jcr:modifyAccessControl]"/>
+    <allow4
+            jcr:primaryType="rep:GrantACE"
+            rep:principalName="acs-commons-automatic-package-replicator-service"
+            rep:privileges="{Name}[jcr:read]"/>
+    <allow5
+            jcr:primaryType="rep:GrantACE"
+            rep:principalName="acs-commons-on-deploy-scripts-service"
+            rep:privileges="{Name}[jcr:read]"/>
+</jcr:root>


[sling-org-apache-sling-feature-cpconverter] 21/29: ignoring non meaningful regex group

Posted by si...@apache.org.
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-org-apache-sling-feature-cpconverter.git

commit c65eb2c409c237a5d0fa3d39d86ddb1178ba790b
Author: stripodi <st...@192.168.1.111>
AuthorDate: Fri Apr 26 15:27:35 2019 +0200

    ignoring non meaningful regex group
---
 .../apache/sling/feature/cpconverter/handlers/BundleEntryHandler.java   | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/main/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandler.java b/src/main/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandler.java
index cc2c447..098aec0 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandler.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandler.java
@@ -46,7 +46,7 @@ public final class BundleEntryHandler extends AbstractRegexEntryHandler {
     private final Pattern pomPropertiesPattern = Pattern.compile("META-INF/maven/[^/]+/[^/]+/pom.properties");
 
     public BundleEntryHandler() {
-        super("(jcr_root)?/(apps|libs)/[^/]+/install(\\.([^/]+))?/.+\\.jar");
+        super("(jcr_root)?/(?:apps|libs)/[^/]+/install(\\.([^/]+))?/.+\\.jar");
     }
 
     @Override


[sling-org-apache-sling-feature-cpconverter] 10/29: avoid any trouble when specifying an external ArtifactId which can have different classifiers

Posted by si...@apache.org.
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-org-apache-sling-feature-cpconverter.git

commit 1b7a9683e8c86f2d9bcd71d0a2d6e450cf4d96e2
Author: stripodi <st...@192.168.1.111>
AuthorDate: Fri Apr 12 01:17:32 2019 +0200

    avoid any trouble when specifying an external ArtifactId which can have
    different classifiers
---
 .../ContentPackage2FeatureModelConverter.java       | 21 ++++++++++++++-------
 .../cpconverter/vltpkg/VaultPackageAssembler.java   |  4 ++--
 .../ContentPackage2FeatureModelConverterTest.java   | 10 +++++-----
 3 files changed, 21 insertions(+), 14 deletions(-)

diff --git a/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java b/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
index 8c051e8..149c673 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
@@ -58,7 +58,7 @@ public class ContentPackage2FeatureModelConverter {
 
     public static final String ZIP_TYPE = "zip";
 
-    public static final String FEATURE_CLASSIFIER = "cp2fm-converted-feature";
+    public static final String PACKAGE_CLASSIFIER = "cp2fm-converted";
 
     private static final String SLING_OSGI_FEATURE_TILE_TYPE = "slingosgifeature";
 
@@ -159,11 +159,17 @@ public class ContentPackage2FeatureModelConverter {
         }
 
         ArtifactId id = getTargetFeature().getId();
+        final String classifier;
+        if (id.getClassifier() != null && !id.getClassifier().isEmpty()) {
+            classifier = id.getClassifier() + '-' + runMode;
+        } else {
+            classifier = runMode;
+        }
 
         return runModes.computeIfAbsent(runMode, k -> new Feature(new ArtifactId(id.getGroupId(),
                                                                                  id.getArtifactId(),
                                                                                  id.getVersion(),
-                                                                                 id.getClassifier() + '-' + runMode,
+                                                                                 classifier,
                                                                                  id.getType())));
     }
 
@@ -237,7 +243,7 @@ public class ContentPackage2FeatureModelConverter {
                 artifactId = new ArtifactId(group,
                                             name,
                                             version,
-                                            FEATURE_CLASSIFIER,
+                                            null,
                                             SLING_OSGI_FEATURE_TILE_TYPE);
             }
 
@@ -259,7 +265,7 @@ public class ContentPackage2FeatureModelConverter {
                                                            targetFeature.getId().getGroupId(),
                                                            targetFeature.getId().getArtifactId(),
                                                            targetFeature.getId().getVersion(),
-                                                           FEATURE_CLASSIFIER,
+                                                           PACKAGE_CLASSIFIER,
                                                            ZIP_TYPE);
 
             artifactDeployer.deploy(new MavenPomSupplierWriter(targetFeature.getId().getGroupId(),
@@ -276,7 +282,7 @@ public class ContentPackage2FeatureModelConverter {
                    targetFeature.getId().getGroupId(),
                    targetFeature.getId().getArtifactId(),
                    targetFeature.getId().getVersion(),
-                   FEATURE_CLASSIFIER,
+                   PACKAGE_CLASSIFIER,
                    ZIP_TYPE);
 
             // finally serialize the Feature Model(s) file(s)
@@ -331,8 +337,9 @@ public class ContentPackage2FeatureModelConverter {
     private void seralize(Feature feature) throws Exception {
         StringBuilder fileName = new StringBuilder().append(feature.getId().getArtifactId());
 
-        if (!FEATURE_CLASSIFIER.equals(feature.getId().getClassifier())) {
-            fileName.append(feature.getId().getClassifier().substring(FEATURE_CLASSIFIER.length()));
+        String classifier = feature.getId().getClassifier();
+        if (classifier != null && !classifier.isEmpty()) {
+            fileName.append('-').append(classifier);
         }
 
         fileName.append(JSON_FILE_EXTENSION);
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/vltpkg/VaultPackageAssembler.java b/src/main/java/org/apache/sling/feature/cpconverter/vltpkg/VaultPackageAssembler.java
index c68f788..08d4e81 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/vltpkg/VaultPackageAssembler.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/vltpkg/VaultPackageAssembler.java
@@ -16,7 +16,7 @@
  */
 package org.apache.sling.feature.cpconverter.vltpkg;
 
-import static org.apache.sling.feature.cpconverter.ContentPackage2FeatureModelConverter.FEATURE_CLASSIFIER;
+import static org.apache.sling.feature.cpconverter.ContentPackage2FeatureModelConverter.PACKAGE_CLASSIFIER;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -67,7 +67,7 @@ public final class VaultPackageAssembler implements EntryHandler {
         properties.setProperty(PackageProperties.NAME_VERSION,
                                packageProperties.getProperty(PackageProperties.NAME_VERSION)
                                                              + '-'
-                                                             + FEATURE_CLASSIFIER);
+                                                             + PACKAGE_CLASSIFIER);
 
         for (String key : new String[] {
                 PackageProperties.NAME_GROUP,
diff --git a/src/test/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverterTest.java b/src/test/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverterTest.java
index 6db646b..7fb3b15 100644
--- a/src/test/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverterTest.java
+++ b/src/test/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverterTest.java
@@ -128,24 +128,24 @@ public class ContentPackage2FeatureModelConverterTest {
 
         verifyFeatureFile(outputDirectory,
                           "asd.retail.all.json",
-                          "asd.sample:asd.retail.all:slingosgifeature:cp2fm-converted-feature:0.0.1",
+                          "asd.sample:asd.retail.all:slingosgifeature:0.0.1",
                           Arrays.asList("org.apache.felix:org.apache.felix.framework:6.0.1"),
                           Arrays.asList("org.apache.sling.commons.log.LogManager.factory.config-asd-retail"),
-                          Arrays.asList("asd.sample:asd.retail.all:zip:cp2fm-converted-feature:0.0.1"));
+                          Arrays.asList("asd.sample:asd.retail.all:zip:cp2fm-converted:0.0.1"));
         verifyFeatureFile(outputDirectory,
                           "asd.retail.all-author.json",
-                          "asd.sample:asd.retail.all:slingosgifeature:cp2fm-converted-feature-author:0.0.1",
+                          "asd.sample:asd.retail.all:slingosgifeature:author:0.0.1",
                           Arrays.asList("org.apache.sling:org.apache.sling.api:2.20.0"),
                           Collections.emptyList(),
                           Collections.emptyList());
         verifyFeatureFile(outputDirectory,
                           "asd.retail.all-publish.json",
-                          "asd.sample:asd.retail.all:slingosgifeature:cp2fm-converted-feature-publish:0.0.1",
+                          "asd.sample:asd.retail.all:slingosgifeature:publish:0.0.1",
                           Arrays.asList("org.apache.sling:org.apache.sling.models.api:1.3.8"),
                           Arrays.asList("org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended-asd-retail"),
                           Collections.emptyList());
 
-        ZipFile zipFile = new ZipFile(new File(outputDirectory, "asd/sample/asd.retail.all/0.0.1/asd.retail.all-0.0.1-cp2fm-converted-feature.zip"));
+        ZipFile zipFile = new ZipFile(new File(outputDirectory, "asd/sample/asd.retail.all/0.0.1/asd.retail.all-0.0.1-cp2fm-converted.zip"));
         for (String expectedEntry : new String[] {
                 "jcr_root/content/asd/.content.xml",
                 "jcr_root/content/asd/resources.xml",


[sling-org-apache-sling-feature-cpconverter] 12/29: Merge pull request #1 from DominikSuess/featureIdOnlyOverride

Posted by si...@apache.org.
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-org-apache-sling-feature-cpconverter.git

commit ccc5627f4ec9a28367b5682a17285499dfb636be
Merge: 1b7a968 c76dea6
Author: Simone Tripodi <si...@gmail.com>
AuthorDate: Fri Apr 12 11:53:33 2019 +0200

    Merge pull request #1 from DominikSuess/featureIdOnlyOverride
    
    override only supposed to affect FeatureFile, not filenames & packageIds

 .../ContentPackage2FeatureModelConverter.java      | 51 ++++++++++++++-------
 ...ntentPackage2FeatureModelConverterLauncher.java |  4 +-
 .../ContentPackage2FeatureModelConverterTest.java  | 52 ++++++++++++++++++++++
 3 files changed, 90 insertions(+), 17 deletions(-)


[sling-org-apache-sling-feature-cpconverter] 27/29: initial prototype of json file indexer

Posted by si...@apache.org.
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-org-apache-sling-feature-cpconverter.git

commit 80e408f851bb09ec66850aef3a834f4d1feb8141
Author: stripodi <st...@simos-mbp>
AuthorDate: Tue Apr 30 19:05:42 2019 +0200

    initial prototype of json file indexer
---
 .../ContentPackage2FeatureModelConverter.java      | 18 +++--
 .../sling/feature/cpconverter/RunmodeMapper.java   | 76 ++++++++++++++++++++++
 2 files changed, 89 insertions(+), 5 deletions(-)

diff --git a/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java b/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
index 6e0155d..94d9e24 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
@@ -179,7 +179,6 @@ public class ContentPackage2FeatureModelConverter {
     }
 
     private ArtifactId appendRunmode(ArtifactId id, String runMode) {
-
         ArtifactId newId;
         if (runMode == null) {
             newId = id;
@@ -302,13 +301,20 @@ public class ContentPackage2FeatureModelConverter {
 
             aclManager.addRepoinitExtension(getTargetFeature());
 
-            seralize(getTargetFeature(), null);
+            RunmodeMapper runmodeMapper = RunmodeMapper.open(featureModelsOutputDirectory);
+
+            File featureModel = seralize(getTargetFeature(), null);
+            runmodeMapper.addOrUpdate(null, featureModel);
 
             if (!runModes.isEmpty()) {
                 for (java.util.Map.Entry<String, Feature> runmodeEntry : runModes.entrySet()) {
-                    seralize(runmodeEntry.getValue(), runmodeEntry.getKey());
+                    String runmode = runmodeEntry.getKey();
+                    featureModel = seralize(runmodeEntry.getValue(), runmode);
+                    runmodeMapper.addOrUpdate(runmode, featureModel);
                 }
             }
+
+            runmodeMapper.save();
         }
     }
 
@@ -335,7 +341,7 @@ public class ContentPackage2FeatureModelConverter {
         }
     }
 
-    private void seralize(Feature feature, String runMode) throws Exception {
+    private File seralize(Feature feature, String runMode) throws Exception {
         StringBuilder fileName = new StringBuilder().append(feature.getId().getArtifactId());
 
         String classifier = feature.getId().getClassifier();
@@ -349,7 +355,7 @@ public class ContentPackage2FeatureModelConverter {
 
         logger.info("Conversion complete!", targetFile);
         logger.info("Writing resulting Feature File to '{}'...", targetFile);
-        
+
         if ( idOverride != null ) {
             ArtifactId idOverrride = appendRunmode(ArtifactId.parse(idOverride), runMode);
             feature = feature.copy(idOverrride);
@@ -360,6 +366,8 @@ public class ContentPackage2FeatureModelConverter {
 
             logger.info("'{}' Feature File successfully written!", targetFile);
         }
+
+        return targetFile;
     }
 
     public void processSubPackage(String path, File contentPackage) throws Exception {
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/RunmodeMapper.java b/src/main/java/org/apache/sling/feature/cpconverter/RunmodeMapper.java
new file mode 100644
index 0000000..601974f
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/cpconverter/RunmodeMapper.java
@@ -0,0 +1,76 @@
+/*
+ * 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.feature.cpconverter;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Properties;
+
+final class RunmodeMapper {
+
+    private static final String FILENAME = "runmode.mapping";
+
+    public static RunmodeMapper open(File featureModelsOutputDirectory) throws IOException {
+        Properties properties = new Properties();
+
+        File runmodeMappingFile = new File(featureModelsOutputDirectory, FILENAME);
+        if (runmodeMappingFile.exists()) {
+            try (FileInputStream input = new FileInputStream(runmodeMappingFile)) {
+                properties.load(input);
+            }
+        }
+
+        return new RunmodeMapper(runmodeMappingFile, properties);
+    }
+
+    private static final String DEFAULT = "(default)";
+
+    private final File runmodeMappingFile;
+
+    private final Properties properties;
+
+    private RunmodeMapper(File runmodeMappingFile, Properties properties) {
+        this.runmodeMappingFile = runmodeMappingFile;
+        this.properties = properties;
+    }
+
+    public void addOrUpdate(String runmode, File jsonFile) {
+        if (runmode == null) {
+            runmode = DEFAULT;
+        }
+
+        String jsonFileName = jsonFile.getName();
+        String value = properties.getProperty(runmode);
+
+        if (value != null && !value.contains(jsonFileName)) {
+            value += ',' + jsonFileName;
+        } else {
+            value = jsonFileName;
+        }
+
+        properties.setProperty(runmode, value);
+    }
+
+    public void save() throws IOException {
+        try (FileOutputStream output = new FileOutputStream(runmodeMappingFile)) {
+            properties.store(output, "File edited by the Apache Sling Content Package to Sling Feature converter");
+        }
+    }
+
+}


[sling-org-apache-sling-feature-cpconverter] 18/29: check configurations exist already only in the same runmode

Posted by si...@apache.org.
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-org-apache-sling-feature-cpconverter.git

commit 3b165baa728e6c0ecba956fe7fb96588ff3057e2
Author: stripodi <st...@192.168.1.111>
AuthorDate: Fri Apr 26 12:25:02 2019 +0200

    check configurations exist already only in the same runmode
---
 .../ContentPackage2FeatureModelConverter.java      | 26 +++++-----------------
 .../ContentPackage2FeatureModelConverterTest.java  | 26 +++++++++++++++++++++-
 2 files changed, 31 insertions(+), 21 deletions(-)

diff --git a/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java b/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
index 4014256..4a946ca 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
@@ -309,20 +309,18 @@ public class ContentPackage2FeatureModelConverter {
     }
 
     public void addConfiguration(String runMode, String pid, Dictionary<String, Object> configurationProperties) {
-        if (!mergeConfigurations) {
-            checkConfigurationExist(getTargetFeature(), pid);
-
-            for (Feature runModeFeature : runModes.values()) {
-                checkConfigurationExist(runModeFeature, pid);
-            }
-        }
-
         Feature feature = getRunMode(runMode);
         Configuration configuration = feature.getConfigurations().getConfiguration(pid);
 
         if (configuration == null) {
             configuration = new Configuration(pid);
             feature.getConfigurations().add(configuration);
+        } else if (!mergeConfigurations) {
+            throw new IllegalStateException("Configuration '"
+                                            + pid
+                                            + "' already defined in Feature Model '"
+                                            + feature.getId().toMvnId()
+                                            + "', set the 'mergeConfigurations' flag to 'true' if you want to merge multiple configurations with same PID");
         }
 
         Enumeration<String> keys = configurationProperties.keys();
@@ -333,18 +331,6 @@ public class ContentPackage2FeatureModelConverter {
         }
     }
 
-    private static void checkConfigurationExist(Feature feature, String pid) {
-        if (feature != null) {
-            if (feature.getConfigurations().getConfiguration(pid) != null) {
-                throw new IllegalStateException("Configuration '"
-                                                + pid
-                                                + "' already defined in Feature Model '"
-                                                + feature.getId().toMvnId()
-                                                + "', can not be added");
-            }
-        }
-    }
-
     private void seralize(Feature feature, String runMode) throws Exception {
         StringBuilder fileName = new StringBuilder().append(feature.getId().getArtifactId());
 
diff --git a/src/test/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverterTest.java b/src/test/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverterTest.java
index 0e06589..116ab7d 100644
--- a/src/test/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverterTest.java
+++ b/src/test/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverterTest.java
@@ -27,6 +27,7 @@ import java.io.Reader;
 import java.net.URL;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.Hashtable;
 import java.util.List;
 import java.util.StringTokenizer;
 import java.util.zip.ZipFile;
@@ -237,7 +238,30 @@ public class ContentPackage2FeatureModelConverterTest {
                  .convert(packageFile);
     }
 
-    
+    @Test(expected = IllegalStateException.class)
+    public void doesNotAllowSameConfigurationPidForSameRunmode() throws Exception {
+        addSamePidConfiguration(null, null);
+    }
+
+    @Test
+    public void allowSameConfigurationPidForDifferentRunmode() throws Exception {
+        addSamePidConfiguration(null, "test");
+    }
+
+    private void addSamePidConfiguration(String runmodeA, String runmodeB) throws Exception {
+        File outputDirectory = new File(System.getProperty("testDirectory"), getClass().getName() + '_' + System.currentTimeMillis());
+        URL packageUrl = getClass().getResource("test-content-package.zip");
+        File packageFile = FileUtils.toFile(packageUrl);
+
+        converter.setArtifactsOutputDirectory(outputDirectory)
+                 .setFeatureModelsOutputDirectory(outputDirectory)
+                 .convert(packageFile);
+
+        String pid = "this.is.just.a.pid";
+        converter.addConfiguration(runmodeA, pid, new Hashtable<String, Object>());
+        converter.addConfiguration(runmodeB, pid, new Hashtable<String, Object>());
+    }
+
     @Test
     public void overrideFeatureId() throws Exception {
         URL packageUrl = getClass().getResource("test-content-package.zip");


[sling-org-apache-sling-feature-cpconverter] 23/29: invalid (XML) OSGi configurations have to be treated ad regular resources that have to be included in the resulting package

Posted by si...@apache.org.
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-org-apache-sling-feature-cpconverter.git

commit 6e295823e47e951252631e9702352b55c0cfa08f
Author: stripodi <st...@192.168.1.111>
AuthorDate: Fri Apr 26 18:10:02 2019 +0200

    invalid (XML) OSGi configurations have to be treated ad regular
    resources that have to be included in the resulting package
---
 .../cpconverter/ContentPackage2FeatureModelConverter.java   |  4 ++++
 .../sling/feature/cpconverter/RegexBasedResourceFilter.java |  6 +++---
 .../handlers/AbstractConfigurationEntryHandler.java         |  6 ++++++
 .../cpconverter/handlers/XmlConfigurationEntryHandler.java  | 13 ++++++++++---
 4 files changed, 23 insertions(+), 6 deletions(-)

diff --git a/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java b/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
index 4a946ca..6e0155d 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
@@ -160,6 +160,10 @@ public class ContentPackage2FeatureModelConverter {
         return aclManager;
     }
 
+    public VaultPackageAssembler getMainPackageAssembler() {
+        return mainPackageAssembler;
+    }
+
     public Feature getRunMode(String runMode) {
         if (getTargetFeature() == null) {
             throw new IllegalStateException("Target Feature not initialized yet, please make sure convert() method was invoked first.");
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/RegexBasedResourceFilter.java b/src/main/java/org/apache/sling/feature/cpconverter/RegexBasedResourceFilter.java
index 9535acf..52785b5 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/RegexBasedResourceFilter.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/RegexBasedResourceFilter.java
@@ -38,15 +38,15 @@ final class RegexBasedResourceFilter {
             logger.debug("Checking if path '{}' matches against '{}' pattern...", path, pattern);
 
             if (pattern.matcher(path).matches()) {
-                logger.debug("Path '{}' matches against '{}' pattern.", path, pattern);
+                logger.debug("Path '{}' matches against '{}' filtering pattern.", path, pattern);
 
                 return true;
             } else {
-                logger.debug("Path '{}' does not matches against '{}' pattern.", path, pattern);
+                logger.debug("Path '{}' does not matches against '{}' filtering pattern.", path, pattern);
             }
         }
 
-        logger.debug("Path '{}' does not match against any configured pattern.", path);
+        logger.debug("Path '{}' does not match against any configured filtering pattern.", path);
 
         return false;
     }
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/handlers/AbstractConfigurationEntryHandler.java b/src/main/java/org/apache/sling/feature/cpconverter/handlers/AbstractConfigurationEntryHandler.java
index 6d6ed3b..e53a54a 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/handlers/AbstractConfigurationEntryHandler.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/handlers/AbstractConfigurationEntryHandler.java
@@ -41,6 +41,12 @@ abstract class AbstractConfigurationEntryHandler extends AbstractRegexEntryHandl
             configurationProperties = parseConfiguration(pid, input);
         }
 
+        if (configurationProperties == null) {
+            logger.info("{} entry does not contain a valid OSGi configuration, treating it as a regular resource", path);
+            converter.getMainPackageAssembler().addEntry(path, archive, entry);
+            return;
+        }
+
         if (configurationProperties.isEmpty()) {
             logger.info("No configuration properties found for configuration {}", path);
             return;
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/handlers/XmlConfigurationEntryHandler.java b/src/main/java/org/apache/sling/feature/cpconverter/handlers/XmlConfigurationEntryHandler.java
index d1b1919..18ce031 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/handlers/XmlConfigurationEntryHandler.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/handlers/XmlConfigurationEntryHandler.java
@@ -33,15 +33,20 @@ public final class XmlConfigurationEntryHandler extends AbstractConfigurationEnt
     @Override
     protected Dictionary<String, Object> parseConfiguration(String name, InputStream input) throws Exception {
         JcrConfigurationHandler configurationHandler = new JcrConfigurationHandler();
-        configurationHandler.parse(input);
-        return configurationHandler.getParsingResult();
+        try {
+            configurationHandler.parse(input);
+            return configurationHandler.getParsingResult();
+        } catch (Exception e) {
+            logger.warn("Current OSGi configuration does not represent a valid XML document, see nested exceptions", e);
+            return null;
+        }
     }
 
     private static final class JcrConfigurationHandler extends AbstractJcrNodeParser<Dictionary<String, Object>> {
 
         private static final String SLING_OSGICONFIG = "sling:OsgiConfig";
 
-        private final Dictionary<String, Object> configuration = new Hashtable<>();
+        private Dictionary<String, Object> configuration = null;
 
         public JcrConfigurationHandler() {
             super(SLING_OSGICONFIG);
@@ -49,6 +54,8 @@ public final class XmlConfigurationEntryHandler extends AbstractConfigurationEnt
 
         @Override
         protected void onJcrRootElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
+            configuration = new Hashtable<>();
+
             for (int i = 0; i < attributes.getLength(); i++) {
                 String attributeQName = attributes.getQName(i);
 


[sling-org-apache-sling-feature-cpconverter] 14/29: typo

Posted by si...@apache.org.
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-org-apache-sling-feature-cpconverter.git

commit ca90dcae612d64ff4a04d0e7697bc3aa2648d3a2
Author: stripodi <st...@192.168.1.111>
AuthorDate: Thu Apr 18 15:18:37 2019 +0200

    typo
---
 .../sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java b/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
index 2bda617..9cf9b1e 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
@@ -358,7 +358,7 @@ public class ContentPackage2FeatureModelConverter {
     private static void checkConfigurationExist(Feature feature, String pid) {
         if (feature != null) {
             if (feature.getConfigurations().getConfiguration(pid) != null) {
-                throw new IllegalStateException("Cinfiguration '"
+                throw new IllegalStateException("Configuration '"
                                                 + pid
                                                 + "' already defined in Feature Model '"
                                                 + feature.getId().toMvnId()