You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by da...@apache.org on 2018/03/15 07:44:14 UTC

[sling-whiteboard] branch master updated (2dcab0e -> 977d6bc)

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

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


    from 2dcab0e  Some additional requirements for the feature model
     new 37eea8f  Support the slinstart maven plugin by providing a model converter API
     new 40ca6fb  Extra logging
     new 6b51e9f  Refactor feature file generator to use JsonWriter
     new 977d6bc  Update the comparison method for generated Application JSON

The 4 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:
 featuremodel/feature-applicationbuilder/pom.xml    |  24 +-
 .../feature/applicationbuilder/impl/Main.java      |  14 +-
 .../impl/ApplicationBuilderTest.java               |  55 +-
 featuremodel/feature-modelconverter/pom.xml        |  26 +-
 .../modelconverter/impl/FeatureToProvisioning.java | 216 ++++++++
 .../sling/feature/modelconverter/impl/Main.java    | 554 +-------------------
 .../impl/{Main.java => ProvisioningToFeature.java} | 565 ++++++---------------
 .../sling/feature/resolver/FrameworkResolver.java  |  34 ++
 .../apache/sling/feature/support/FeatureUtil.java  |  20 +-
 .../support/json/ApplicationJSONWriter.java        |  36 +-
 .../support/json/ConfigurationJSONWriter.java      |  23 +-
 .../feature/support/json/FeatureJSONWriter.java    | 152 +++---
 .../sling/feature/support/json/JSONReaderBase.java |  10 +-
 .../sling/feature/support/json/JSONWriterBase.java | 133 ++---
 .../support/json/FeatureJSONWriterTest.java        |  15 +-
 .../src/test/resources/features/test.json          |   1 +
 .../java/org/apache/sling/feature/KeyValueMap.java |  23 +-
 .../sling/feature/process/ApplicationBuilder.java  |  21 +-
 18 files changed, 740 insertions(+), 1182 deletions(-)
 create mode 100644 featuremodel/feature-modelconverter/src/main/java/org/apache/sling/feature/modelconverter/impl/FeatureToProvisioning.java
 copy featuremodel/feature-modelconverter/src/main/java/org/apache/sling/feature/modelconverter/impl/{Main.java => ProvisioningToFeature.java} (61%)

-- 
To stop receiving notification emails like this one, please contact
davidb@apache.org.

[sling-whiteboard] 02/04: Extra logging

Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 40ca6fb6a90100298f1afb28fe2319c2c7c05a34
Author: David Bosschaert <da...@gmail.com>
AuthorDate: Mon Mar 12 17:29:02 2018 +0000

    Extra logging
---
 .../sling/feature/modelconverter/impl/ProvisioningToFeature.java       | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/featuremodel/feature-modelconverter/src/main/java/org/apache/sling/feature/modelconverter/impl/ProvisioningToFeature.java b/featuremodel/feature-modelconverter/src/main/java/org/apache/sling/feature/modelconverter/impl/ProvisioningToFeature.java
index e3a4a52..59af64d 100644
--- a/featuremodel/feature-modelconverter/src/main/java/org/apache/sling/feature/modelconverter/impl/ProvisioningToFeature.java
+++ b/featuremodel/feature-modelconverter/src/main/java/org/apache/sling/feature/modelconverter/impl/ProvisioningToFeature.java
@@ -461,6 +461,9 @@ public class ProvisioningToFeature {
                 out = out.substring(0, lastDot) + "_" + String.valueOf(index) + out.substring(lastDot);
             }
         }
+
+        LOGGER.info("to file {}", out);
+
         final File file = new File(out);
         try ( final FileWriter writer = new FileWriter(file)) {
             FeatureJSONWriter.write(writer, f);

-- 
To stop receiving notification emails like this one, please contact
davidb@apache.org.

[sling-whiteboard] 01/04: Support the slinstart maven plugin by providing a model converter API

Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 37eea8fb852c128550f9ca915bb213bc8796bc78
Author: David Bosschaert <da...@gmail.com>
AuthorDate: Fri Mar 9 10:46:10 2018 +0000

    Support the slinstart maven plugin by providing a model converter API
---
 featuremodel/feature-modelconverter/pom.xml        |  26 +-
 .../modelconverter/impl/FeatureToProvisioning.java | 216 ++++++++
 .../sling/feature/modelconverter/impl/Main.java    | 554 +-------------------
 .../impl/{Main.java => ProvisioningToFeature.java} | 562 ++++++---------------
 .../sling/feature/resolver/FrameworkResolver.java  |  34 ++
 .../apache/sling/feature/support/FeatureUtil.java  |  20 +-
 .../sling/feature/support/json/JSONReaderBase.java |  10 +-
 .../java/org/apache/sling/feature/KeyValueMap.java |  23 +-
 .../sling/feature/process/ApplicationBuilder.java  |  21 +-
 9 files changed, 477 insertions(+), 989 deletions(-)

diff --git a/featuremodel/feature-modelconverter/pom.xml b/featuremodel/feature-modelconverter/pom.xml
index d5359d5..2e0c607 100644
--- a/featuremodel/feature-modelconverter/pom.xml
+++ b/featuremodel/feature-modelconverter/pom.xml
@@ -83,6 +83,12 @@
             <scope>provided</scope>
         </dependency>
         <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.service.resolver</artifactId>
+            <version>1.0.1</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-api</artifactId>
         </dependency>
@@ -104,6 +110,12 @@
         </dependency>
         <dependency>
             <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.feature.analyser</artifactId>
+            <version>0.0.1-SNAPSHOT</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.feature.resolver</artifactId>
             <version>0.0.1-SNAPSHOT</version>
             <scope>provided</scope>
@@ -126,10 +138,18 @@
             <version>1.0.0</version>
             <scope>provided</scope>
         </dependency>
-      <!-- Testing -->
+        
+        <!-- Testing -->
+        <dependency>
+        	    <groupId>junit</groupId>
+        	    <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
         <dependency>
-        	<groupId>junit</groupId>
-        	<artifactId>junit</artifactId>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.framework</artifactId>
+            <version>5.6.10</version>
+            <!--  <scope>test</scope>  -->
         </dependency>
     </dependencies>
 </project>
diff --git a/featuremodel/feature-modelconverter/src/main/java/org/apache/sling/feature/modelconverter/impl/FeatureToProvisioning.java b/featuremodel/feature-modelconverter/src/main/java/org/apache/sling/feature/modelconverter/impl/FeatureToProvisioning.java
new file mode 100644
index 0000000..44eaaac
--- /dev/null
+++ b/featuremodel/feature-modelconverter/src/main/java/org/apache/sling/feature/modelconverter/impl/FeatureToProvisioning.java
@@ -0,0 +1,216 @@
+/*
+ * 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.modelconverter.impl;
+
+import org.apache.sling.feature.Application;
+import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.Bundles;
+import org.apache.sling.feature.Configurations;
+import org.apache.sling.feature.Extension;
+import org.apache.sling.feature.Extensions;
+import org.apache.sling.feature.KeyValueMap;
+import org.apache.sling.feature.process.FeatureResolver;
+import org.apache.sling.feature.support.ArtifactManager;
+import org.apache.sling.feature.support.FeatureUtil;
+import org.apache.sling.feature.support.json.ApplicationJSONReader;
+import org.apache.sling.provisioning.model.Artifact;
+import org.apache.sling.provisioning.model.Configuration;
+import org.apache.sling.provisioning.model.Feature;
+import org.apache.sling.provisioning.model.Model;
+import org.apache.sling.provisioning.model.Section;
+import org.apache.sling.provisioning.model.io.ModelWriter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class FeatureToProvisioning {
+    private static Logger LOGGER = LoggerFactory.getLogger(FeatureToProvisioning.class);
+
+    public static void convert(File file, String output, ArtifactManager am) throws IOException {
+        org.apache.sling.feature.Feature feature = FeatureUtil.getFeature(file.getAbsolutePath(), am);
+
+        String featureName;
+        if (feature.getTitle() != null) {
+            featureName = feature.getTitle();
+        } else {
+            featureName = feature.getId().getArtifactId();
+        }
+
+        Feature newFeature = new Feature(featureName);
+        convert(newFeature, feature.getBundles(), feature.getConfigurations(), feature.getFrameworkProperties(), feature.getExtensions(), output);
+    }
+
+    public static void convert(List<File> files, String output, boolean createApp, ArtifactManager am) throws Exception {
+        try (FeatureResolver fr = null) { // TODO we could use the resolver: new FrameworkResolver(am)
+            if ( createApp ) {
+                // each file is an application
+                int index = 1;
+                for(final File appFile : files ) {
+                    try ( final FileReader r = new FileReader(appFile) ) {
+                        final Application app = ApplicationJSONReader.read(r);
+                        FeatureToProvisioning.convert(app, files.size() > 1 ? index : 0, output);
+                    }
+                    index++;
+                }
+            } else {
+                final Application app = FeatureUtil.assembleApplication(null, am, fr, files.stream()
+                        .map(File::getAbsolutePath)
+                        .toArray(String[]::new));
+                FeatureToProvisioning.convert(app, 0, output);
+            }
+        }
+    }
+
+    private static void convert(final Application app, final int index, final String outputFile) {
+        String featureName;
+
+        List<ArtifactId> fids = app.getFeatureIds();
+        if (fids.size() > 0) {
+            featureName = fids.get(0).getArtifactId();
+        } else {
+            featureName = "application";
+        }
+        final Feature feature = new Feature(featureName);
+
+        convert(feature, app.getBundles(), app.getConfigurations(), app.getFrameworkProperties(), app.getExtensions(), outputFile);
+    }
+
+    private static void convert(Feature f, Bundles bundles, Configurations configurations, KeyValueMap frameworkProps,
+            Extensions extensions, String outputFile) {
+        Map<org.apache.sling.feature.Configuration, org.apache.sling.feature.Artifact> configBundleMap = new HashMap<>();
+
+        // bundles
+        for(final org.apache.sling.feature.Artifact bundle : bundles) {
+            final ArtifactId id = bundle.getId();
+            final Artifact newBundle = new Artifact(id.getGroupId(), id.getArtifactId(), id.getVersion(), id.getClassifier(), id.getType());
+
+            Object configs = bundle.getMetadata().getObject("configurations");
+            if (configs instanceof List) {
+                for (Object config : (List<?>) configs) {
+                    if (config instanceof org.apache.sling.feature.Configuration) {
+                        configBundleMap.put((org.apache.sling.feature.Configuration) config, bundle);
+                    }
+                }
+            }
+
+            for(final Map.Entry<String, String> prop : bundle.getMetadata()) {
+                switch (prop.getKey()) {
+                    // these are handled separately
+                    case "start-level":
+                    case "run-modes":
+                        break;
+                    default:
+                        newBundle.getMetadata().put(prop.getKey(), prop.getValue());
+                }
+            }
+
+            int startLevel;
+            String sl = bundle.getMetadata().get("start-level");
+            if (sl != null) {
+                startLevel = Integer.parseInt(sl);
+            } else {
+                startLevel = 20;
+            }
+
+            String[] runModes = getRunModes(bundle);
+            f.getOrCreateRunMode(runModes).getOrCreateArtifactGroup(startLevel).add(newBundle);
+        }
+
+        // configurations
+        for(final org.apache.sling.feature.Configuration cfg : configurations) {
+            final Configuration c;
+            if ( cfg.isFactoryConfiguration() ) {
+                c = new Configuration(cfg.getName(), cfg.getFactoryPid());
+            } else {
+                c = new Configuration(cfg.getPid(), null);
+            }
+            final Enumeration<String> keys = cfg.getProperties().keys();
+            while ( keys.hasMoreElements() ) {
+                final String key = keys.nextElement();
+                c.getProperties().put(key, cfg.getProperties().get(key));
+            }
+
+            // Check if the configuration has an associated runmode via the bundle that it belongs to
+            org.apache.sling.feature.Artifact bundle = configBundleMap.get(cfg);
+            String[] runModes;
+            if (bundle != null) {
+                runModes = getRunModes(bundle);
+            } else {
+                runModes = null;
+            }
+            f.getOrCreateRunMode(runModes).getConfigurations().add(c);
+        }
+
+        // framework properties
+        for(final Map.Entry<String, String> prop : frameworkProps) {
+            f.getOrCreateRunMode(null).getSettings().put(prop.getKey(), prop.getValue());
+        }
+
+        // extensions: content packages and repoinit
+        for(final Extension ext : extensions) {
+            if ( Extension.NAME_CONTENT_PACKAGES.equals(ext.getName()) ) {
+                for(final org.apache.sling.feature.Artifact cp : ext.getArtifacts() ) {
+                    final ArtifactId id = cp.getId();
+                    final Artifact newCP = new Artifact(id.getGroupId(), id.getArtifactId(), id.getVersion(), id.getClassifier(), id.getType());
+                    for(final Map.Entry<String, String> prop : cp.getMetadata()) {
+                        newCP.getMetadata().put(prop.getKey(), prop.getValue());
+                    }
+                    f.getOrCreateRunMode(null).getOrCreateArtifactGroup(0).add(newCP);
+                }
+
+            } else if ( Extension.NAME_REPOINIT.equals(ext.getName()) ) {
+                final Section section = new Section("repoinit");
+                section.setContents(ext.getText());
+                f.getAdditionalSections().add(section);
+            } else if ( ext.isRequired() ) {
+                LOGGER.error("Unable to convert required extension {}", ext.getName());
+                System.exit(1);
+            }
+        }
+
+        LOGGER.info("Writing feature...");
+        final String out = outputFile;
+        final File file = new File(out);
+        final Model m = new Model();
+        m.getFeatures().add(f);
+        try ( final FileWriter writer = new FileWriter(file)) {
+            ModelWriter.write(writer, m);
+        } catch ( final IOException ioe) {
+            LOGGER.error("Unable to write feature to {} : {}", out, ioe.getMessage(), ioe);
+            System.exit(1);
+        }
+    }
+
+    private static String[] getRunModes(final org.apache.sling.feature.Artifact bundle) {
+        String runMode = bundle.getMetadata().get("run-modes");
+        String[] runModes;
+        if (runMode != null) {
+            runModes = runMode.split(",");
+        } else {
+            runModes = null;
+        }
+        return runModes;
+    }
+}
diff --git a/featuremodel/feature-modelconverter/src/main/java/org/apache/sling/feature/modelconverter/impl/Main.java b/featuremodel/feature-modelconverter/src/main/java/org/apache/sling/feature/modelconverter/impl/Main.java
index ccc3f85..81a9e3c 100644
--- a/featuremodel/feature-modelconverter/src/main/java/org/apache/sling/feature/modelconverter/impl/Main.java
+++ b/featuremodel/feature-modelconverter/src/main/java/org/apache/sling/feature/modelconverter/impl/Main.java
@@ -16,60 +16,23 @@
  */
 package org.apache.sling.feature.modelconverter.impl;
 
-import java.io.File;
-import java.io.FileReader;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Enumeration;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
 import org.apache.commons.cli.CommandLine;
 import org.apache.commons.cli.CommandLineParser;
 import org.apache.commons.cli.DefaultParser;
 import org.apache.commons.cli.Option;
 import org.apache.commons.cli.Options;
 import org.apache.commons.cli.ParseException;
-import org.apache.sling.feature.Application;
-import org.apache.sling.feature.ArtifactId;
-import org.apache.sling.feature.Bundles;
-import org.apache.sling.feature.Configurations;
-import org.apache.sling.feature.Extension;
-import org.apache.sling.feature.ExtensionType;
-import org.apache.sling.feature.Extensions;
-import org.apache.sling.feature.KeyValueMap;
-import org.apache.sling.feature.process.FeatureResolver;
-import org.apache.sling.feature.resolver.FrameworkResolver;
-import org.apache.sling.feature.support.ArtifactHandler;
 import org.apache.sling.feature.support.ArtifactManager;
 import org.apache.sling.feature.support.ArtifactManagerConfig;
-import org.apache.sling.feature.support.FeatureUtil;
-import org.apache.sling.feature.support.json.ApplicationJSONReader;
-import org.apache.sling.feature.support.json.ApplicationJSONWriter;
-import org.apache.sling.feature.support.json.FeatureJSONWriter;
-import org.apache.sling.provisioning.model.Artifact;
-import org.apache.sling.provisioning.model.ArtifactGroup;
-import org.apache.sling.provisioning.model.Configuration;
-import org.apache.sling.provisioning.model.Feature;
-import org.apache.sling.provisioning.model.MergeUtility;
-import org.apache.sling.provisioning.model.Model;
-import org.apache.sling.provisioning.model.ModelConstants;
-import org.apache.sling.provisioning.model.ModelUtility;
-import org.apache.sling.provisioning.model.ModelUtility.ResolverOptions;
-import org.apache.sling.provisioning.model.ModelUtility.VariableResolver;
-import org.apache.sling.provisioning.model.RunMode;
-import org.apache.sling.provisioning.model.Section;
-import org.apache.sling.provisioning.model.Traceable;
-import org.apache.sling.provisioning.model.io.ModelReader;
-import org.apache.sling.provisioning.model.io.ModelWriter;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
 public class Main {
 
     private static Logger LOGGER;
@@ -167,10 +130,6 @@ public class Main {
         return null;
     }
 
-    private static FeatureResolver getFeatureResolver(ArtifactManager am) {
-        return new FrameworkResolver(am, Collections.emptyMap());
-    }
-
     public static void main(final String[] args) {
         // setup logging
         System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "info");
@@ -229,41 +188,13 @@ public class Main {
             if ( output == null ) {
                 output = createApp ? "application.json" : "feature.json";
             }
-            final Model model = createModel(files, runModes);
-
-            if ( createApp ) {
-                final Application app = buildApplication(model);
-
-                writeApplication(app, output);
-            } else {
-                final List<org.apache.sling.feature.Feature> features = buildFeatures(model);
-                int index = 1;
-                for(final org.apache.sling.feature.Feature feature : features) {
-                    writeFeature(feature, output, features.size() > 1 ? index : 0);
-                    index++;
-                }
-            }
+            ProvisioningToFeature.convert(files, output, runModes, createApp, includeModelInfo, propsFile);
         } else {
             if ( output == null ) {
                 output = createApp ? "application.txt" : "feature.txt";
             }
-            try (FeatureResolver fr = getFeatureResolver(am)) {
-                if ( createApp ) {
-                    // each file is an application
-                    int index = 1;
-                    for(final File appFile : files ) {
-                        try ( final FileReader r = new FileReader(appFile) ) {
-                            final Application app = ApplicationJSONReader.read(r);
-                            convert(app, files.size() > 1 ? index : 0);
-                        }
-                        index++;
-                    }
-                } else {
-                    final Application app = FeatureUtil.assembleApplication(null, am, fr, files.stream()
-                            .map(File::getAbsolutePath)
-                            .toArray(String[]::new));
-                    convert(app, 0);
-                }
+            try {
+                FeatureToProvisioning.convert(files, output, createApp, am);
             } catch ( final IOException ioe) {
                 LOGGER.error("Unable to read feature/application files " + ioe.getMessage(), ioe);
                 System.exit(1);
@@ -274,471 +205,4 @@ public class Main {
         }
     }
 
-    private static List<org.apache.sling.feature.Feature> buildFeatures(final Model model) {
-        final List<org.apache.sling.feature.Feature> features = new ArrayList<>();
-
-        for(final Feature feature : model.getFeatures() ) {
-            final String idString;
-            // use a default name if not present or not usable as a Maven artifactId ( starts with ':')
-            if ( feature.getName() != null && !feature.isSpecial() ) {
-                if ( feature.getVersion() != null ) {
-                    idString = "generated/" + feature.getName() + "/" + feature.getVersion();
-                } else {
-                    idString = "generated/" + feature.getName() + "/1.0.0";
-                }
-            } else {
-                idString = "generated/feature/1.0.0";
-            }
-            final org.apache.sling.feature.Feature f = new org.apache.sling.feature.Feature(ArtifactId.parse(idString));
-            features.add(f);
-
-            buildFromFeature(feature, f.getBundles(), f.getConfigurations(), f.getExtensions(), f.getFrameworkProperties());
-        }
-
-        return features;
-    }
-
-    private static Application buildApplication(final Model model) {
-        final Application app = new Application();
-
-        for(final Feature feature : model.getFeatures() ) {
-            buildFromFeature(feature, app.getBundles(), app.getConfigurations(), app.getExtensions(), app.getFrameworkProperties());
-        }
-
-        // hard coded dependency to launchpad api
-        final org.apache.sling.feature.Artifact a = new org.apache.sling.feature.Artifact(ArtifactId.parse("org.apache.sling/org.apache.sling.launchpad.api/1.2.0"));
-        a.getMetadata().put(org.apache.sling.feature.Artifact.KEY_START_ORDER, "1");
-        // sling.properties (TODO)
-        if ( propsFile == null ) {
-            app.getFrameworkProperties().put("org.osgi.framework.bootdelegation", "sun.*,com.sun.*");
-        } else {
-
-        }
-        // felix framework hard coded for now
-        app.setFramework(FeatureUtil.getFelixFrameworkId(null));
-        return app;
-    }
-
-    private static void buildFromFeature(final Feature feature,
-            final Bundles bundles,
-            final Configurations configurations,
-            final Extensions extensions,
-            final KeyValueMap properties) {
-        Extension cpExtension = extensions.getByName(Extension.NAME_CONTENT_PACKAGES);
-        for(final RunMode runMode : feature.getRunModes() ) {
-            if ( !ModelConstants.FEATURE_LAUNCHPAD.equals(feature.getName()) ) {
-                for(final ArtifactGroup group : runMode.getArtifactGroups()) {
-                    for(final Artifact artifact : group) {
-                        final ArtifactId id = ArtifactId.fromMvnUrl(artifact.toMvnUrl());
-                        final org.apache.sling.feature.Artifact newArtifact = new org.apache.sling.feature.Artifact(id);
-
-                        for(final Map.Entry<String, String> entry : artifact.getMetadata().entrySet()) {
-                            newArtifact.getMetadata().put(entry.getKey(), entry.getValue());
-                        }
-
-                        if ( newArtifact.getId().getType().equals("zip") ) {
-                            if ( cpExtension == null ) {
-                                cpExtension = new Extension(ExtensionType.ARTIFACTS, Extension.NAME_CONTENT_PACKAGES, true);
-                                extensions.add(cpExtension);
-                            }
-                            cpExtension.getArtifacts().add(newArtifact);
-                        } else {
-                            int startLevel = group.getStartLevel();
-                            if ( ModelConstants.FEATURE_BOOT.equals(feature.getName()) ) {
-                                startLevel = 1;
-                            } else if ( startLevel == 0 ) {
-                                startLevel = 20;
-                            }
-                            newArtifact.getMetadata().put(org.apache.sling.feature.Artifact.KEY_START_ORDER, String.valueOf(startLevel));
-                            bundles.add(newArtifact);
-                        }
-                    }
-                }
-            }
-
-            for(final Configuration cfg : runMode.getConfigurations()) {
-                final org.apache.sling.feature.Configuration newCfg;
-                if ( cfg.getFactoryPid() != null ) {
-                    newCfg = new org.apache.sling.feature.Configuration(cfg.getFactoryPid(), cfg.getPid());
-                } else {
-                    newCfg = new org.apache.sling.feature.Configuration(cfg.getPid());
-                }
-                final Enumeration<String> keys = cfg.getProperties().keys();
-                while ( keys.hasMoreElements() ) {
-                    final String key = keys.nextElement();
-                    newCfg.getProperties().put(key, cfg.getProperties().get(key));
-                }
-                configurations.add(newCfg);
-            }
-
-            for(final Map.Entry<String, String> prop : runMode.getSettings()) {
-                properties.put(prop.getKey(), prop.getValue());
-            }
-        }
-        Extension repoExtension = extensions.getByName(Extension.NAME_REPOINIT);
-        for(final Section sect : feature.getAdditionalSections("repoinit")) {
-            final String text = sect.getContents();
-            if ( repoExtension == null ) {
-                repoExtension = new Extension(ExtensionType.TEXT, Extension.NAME_REPOINIT, true);
-                extensions.add(repoExtension);
-                repoExtension.setText(text);
-            } else {
-                repoExtension.setText(repoExtension.getText() + "\n\n" + text);
-            }
-        }
-    }
-
-    private static void writeApplication(final Application app, final String out) {
-        LOGGER.info("Writing application...");
-        final File file = new File(out);
-        try ( final FileWriter writer = new FileWriter(file)) {
-            ApplicationJSONWriter.write(writer, app);
-        } catch ( final IOException ioe) {
-            LOGGER.error("Unable to write application to {} : {}", out, ioe.getMessage(), ioe);
-            System.exit(1);
-        }
-    }
-
-    private static void writeFeature(final org.apache.sling.feature.Feature f, String out, final int index) {
-        LOGGER.info("Writing feature...");
-        if ( index > 0 ) {
-            final int lastDot = out.lastIndexOf('.');
-            if ( lastDot == -1 ) {
-                out = out + "_" + String.valueOf(index);
-            } else {
-                out = out.substring(0, lastDot) + "_" + String.valueOf(index) + out.substring(lastDot);
-            }
-        }
-        final File file = new File(out);
-        try ( final FileWriter writer = new FileWriter(file)) {
-            FeatureJSONWriter.write(writer, f);
-        } catch ( final IOException ioe) {
-            LOGGER.error("Unable to write feature to {} : {}", out, ioe.getMessage(), ioe);
-            System.exit(1);
-        }
-    }
-
-    /**
-     * Read the models and prepare the model
-     * @param files The model files
-     */
-    private static Model createModel(final List<File> files,
-            final String runModes) {
-        LOGGER.info("Assembling model...");
-        Model model = null;
-        for(final File initFile : files) {
-            try {
-                model = processModel(model, initFile);
-            } catch ( final IOException iae) {
-                LOGGER.error("Unable to read provisioning model {} : {}", initFile, iae.getMessage(), iae);
-                System.exit(1);
-            }
-        }
-
-        final Model effectiveModel = ModelUtility.getEffectiveModel(model, new ResolverOptions().variableResolver(new VariableResolver() {
-
-            @Override
-            public String resolve(Feature feature, String name) {
-                if ( "sling.home".equals(name) ) {
-                    return "${sling.home}";
-                }
-                return feature.getVariables().get(name);
-            }
-        }));
-        final Map<Traceable, String> errors = ModelUtility.validate(effectiveModel);
-        if ( errors != null ) {
-            LOGGER.error("Invalid assembled provisioning model.");
-            for(final Map.Entry<Traceable, String> entry : errors.entrySet()) {
-                LOGGER.error("- {} : {}", entry.getKey().getLocation(), entry.getValue());
-            }
-            System.exit(1);
-        }
-        final Set<String> modes = calculateRunModes(effectiveModel, runModes);
-
-        removeInactiveFeaturesAndRunModes(effectiveModel, modes);
-
-        return effectiveModel;
-    }
-
-    /**
-     * Process the given model and merge it into the provided model
-     * @param model The already read model
-     * @param modelFile The model file
-     * @return The merged model
-     * @throws IOException If reading fails
-     */
-    private static Model processModel(Model model,
-            final File modelFile) throws IOException {
-        LOGGER.info("- reading model {}", modelFile);
-
-        final Model nextModel = readProvisioningModel(modelFile);
-        // resolve references to other models
-        final ResolverOptions options = new ResolverOptions().variableResolver(new VariableResolver() {
-
-            @Override
-            public String resolve(final Feature feature, final String name) {
-                return name;
-            }
-        });
-
-
-        final Model effectiveModel = ModelUtility.getEffectiveModel(nextModel, options);
-        for(final Feature feature : effectiveModel.getFeatures()) {
-            for(final RunMode runMode : feature.getRunModes()) {
-                for(final ArtifactGroup group : runMode.getArtifactGroups()) {
-                    final List<org.apache.sling.provisioning.model.Artifact> removeList = new ArrayList<>();
-                    for(final org.apache.sling.provisioning.model.Artifact a : group) {
-                        if ( "slingstart".equals(a.getType())
-                             || "slingfeature".equals(a.getType())) {
-
-                            final ArtifactManagerConfig cfg = new ArtifactManagerConfig();
-                            final ArtifactManager mgr = ArtifactManager.getArtifactManager(cfg);
-
-                            final ArtifactId correctedId = new ArtifactId(a.getGroupId(),
-                                    a.getArtifactId(),
-                                    a.getVersion(),
-                                    "slingstart".equals(a.getType()) ? "slingfeature" : a.getClassifier(),
-                                    "txt");
-
-                            final ArtifactHandler handler = mgr.getArtifactHandler(correctedId.toMvnUrl());
-                            model = processModel(model, handler.getFile());
-
-                            removeList.add(a);
-                        } else {
-                            final org.apache.sling.provisioning.model.Artifact realArtifact = nextModel.getFeature(feature.getName()).getRunMode(runMode.getNames()).getArtifactGroup(group.getStartLevel()).search(a);
-
-                            if ( includeModelInfo ) {
-                                realArtifact.getMetadata().put("model-filename", modelFile.getName());
-                            }
-                            if ( runMode.getNames() != null ) {
-                                realArtifact.getMetadata().put("runmodes", String.join(",", runMode.getNames()));
-                            }
-                        }
-                    }
-                    for(final org.apache.sling.provisioning.model.Artifact r : removeList) {
-                        nextModel.getFeature(feature.getName()).getRunMode(runMode.getNames()).getArtifactGroup(group.getStartLevel()).remove(r);
-                    }
-                }
-            }
-        }
-
-        if ( model == null ) {
-            model = nextModel;
-        } else {
-            MergeUtility.merge(model, nextModel);
-        }
-        return model;
-    }
-
-    /**
-     * Read the provisioning model
-     */
-    private static Model readProvisioningModel(final File file)
-    throws IOException {
-        try (final FileReader is = new FileReader(file)) {
-            final Model m = ModelReader.read(is, file.getAbsolutePath());
-            return m;
-        }
-    }
-
-    private static void removeInactiveFeaturesAndRunModes(final Model m,
-            final Set<String> activeRunModes) {
-        final String[] requiredFeatures = new String[] {ModelConstants.FEATURE_LAUNCHPAD, ModelConstants.FEATURE_BOOT};
-        // first pass:
-        // - remove special features except boot required ones
-        // - remove special run modes and inactive run modes
-        // - remove special configurations (TODO)
-        final Iterator<Feature> i = m.getFeatures().iterator();
-        while ( i.hasNext() ) {
-            final Feature feature = i.next();
-            if ( feature.isSpecial() ) {
-                boolean remove = true;
-                if ( requiredFeatures != null ) {
-                    for(final String name : requiredFeatures) {
-                        if ( feature.getName().equals(name) ) {
-                            remove = false;
-                            break;
-                        }
-                    }
-                }
-                if ( remove ) {
-                    i.remove();
-                    continue;
-                }
-            }
-            feature.setComment(null);
-            final Iterator<RunMode> rmI = feature.getRunModes().iterator();
-            while ( rmI.hasNext() ) {
-                final RunMode rm = rmI.next();
-                if ( rm.isActive(activeRunModes) || rm.isRunMode(ModelConstants.RUN_MODE_STANDALONE) ) {
-                    final Iterator<Configuration> cI = rm.getConfigurations().iterator();
-                    while ( cI.hasNext() ) {
-                        final Configuration config = cI.next();
-                        if ( config.isSpecial() ) {
-                            cI.remove();
-                            continue;
-                        }
-                        config.setComment(null);
-                    }
-                } else {
-                    rmI.remove();
-                    continue;
-                }
-            }
-        }
-
-        // second pass: aggregate the settings and add them to the first required feature
-        final Feature requiredFeature = m.getFeature(requiredFeatures[0]);
-        if ( requiredFeature != null ) {
-            for(final Feature f : m.getFeatures()) {
-                if ( f.getName().equals(requiredFeature.getName()) ) {
-                    continue;
-                }
-                copyAndClearSettings(requiredFeature, f.getRunMode(new String[] {ModelConstants.RUN_MODE_STANDALONE}));
-                copyAndClearSettings(requiredFeature, f.getRunMode());
-            }
-        }
-    }
-
-    private static void copyAndClearSettings(final Feature requiredFeature, final RunMode rm) {
-        if ( rm != null && !rm.getSettings().isEmpty() ) {
-            final RunMode requiredRunMode = requiredFeature.getOrCreateRunMode(null);
-            final Set<String> keys = new HashSet<>();
-            for(final Map.Entry<String, String> entry : rm.getSettings()) {
-                requiredRunMode.getSettings().put(entry.getKey(), entry.getValue());
-                keys.add(entry.getKey());
-            }
-
-            for(final String key : keys) {
-                rm.getSettings().remove(key);
-            }
-        }
-    }
-
-    private static Set<String> calculateRunModes(final Model model, final String runModes) {
-        final Set<String> modesSet = new HashSet<>();
-
-        // check configuration property first
-        if (runModes != null && runModes.trim().length() > 0) {
-            final String[] modes = runModes.split(",");
-            for(int i=0; i < modes.length; i++) {
-                modesSet.add(modes[i].trim());
-            }
-        }
-
-        //  handle configured options
-        final Feature feature = model.getFeature(ModelConstants.FEATURE_BOOT);
-        if ( feature != null ) {
-            handleOptions(modesSet, feature.getRunMode().getSettings().get("sling.run.mode.options"));
-            handleOptions(modesSet, feature.getRunMode().getSettings().get("sling.run.mode.install.options"));
-        }
-
-        return modesSet;
-    }
-
-    private static void handleOptions(final Set<String> modesSet, final String propOptions) {
-        if ( propOptions != null && propOptions.trim().length() > 0 ) {
-
-            final String[] options = propOptions.trim().split("\\|");
-            for(final String opt : options) {
-                String selected = null;
-                final String[] modes = opt.trim().split(",");
-                for(int i=0; i<modes.length; i++) {
-                    modes[i] = modes[i].trim();
-                    if ( selected != null ) {
-                        modesSet.remove(modes[i]);
-                    } else {
-                        if ( modesSet.contains(modes[i]) ) {
-                            selected = modes[i];
-                        }
-                    }
-                }
-                if ( selected == null ) {
-                    selected = modes[0];
-                    modesSet.add(modes[0]);
-                }
-            }
-        }
-    }
-
-    private static void convert(final Application app, final int index) {
-        final Feature f = new Feature("application");
-
-        // bundles
-        for(final org.apache.sling.feature.Artifact bundle : app.getBundles()) {
-            final ArtifactId id = bundle.getId();
-            final Artifact newBundle = new Artifact(id.getGroupId(), id.getArtifactId(), id.getVersion(), id.getClassifier(), id.getType());
-            for(final Map.Entry<String, String> prop : bundle.getMetadata()) {
-                newBundle.getMetadata().put(prop.getKey(), prop.getValue());
-            }
-            int startLevel = bundle.getStartOrder();
-            if ( startLevel == 0 ) {
-                startLevel = 20;
-            }
-            f.getOrCreateRunMode(null).getOrCreateArtifactGroup(startLevel).add(newBundle);
-        }
-
-        // configurations
-        for(final org.apache.sling.feature.Configuration cfg : app.getConfigurations()) {
-            final Configuration c;
-            if ( cfg.isFactoryConfiguration() ) {
-                c = new Configuration(cfg.getName(), cfg.getFactoryPid());
-            } else {
-                c = new Configuration(cfg.getPid(), null);
-            }
-            final Enumeration<String> keys = cfg.getProperties().keys();
-            while ( keys.hasMoreElements() ) {
-                final String key = keys.nextElement();
-                c.getProperties().put(key, cfg.getProperties().get(key));
-            }
-            f.getOrCreateRunMode(null).getConfigurations().add(c);
-        }
-
-        // framework properties
-        for(final Map.Entry<String, String> prop : app.getFrameworkProperties()) {
-            f.getOrCreateRunMode(null).getSettings().put(prop.getKey(), prop.getValue());
-        }
-
-        // extensions: content packages and repoinit
-        for(final Extension ext : app.getExtensions()) {
-            if ( Extension.NAME_CONTENT_PACKAGES.equals(ext.getName()) ) {
-                for(final org.apache.sling.feature.Artifact cp : ext.getArtifacts() ) {
-                    final ArtifactId id = cp.getId();
-                    final Artifact newCP = new Artifact(id.getGroupId(), id.getArtifactId(), id.getVersion(), id.getClassifier(), id.getType());
-                    for(final Map.Entry<String, String> prop : cp.getMetadata()) {
-                        newCP.getMetadata().put(prop.getKey(), prop.getValue());
-                    }
-                    f.getOrCreateRunMode(null).getOrCreateArtifactGroup(0).add(newCP);
-                }
-
-            } else if ( Extension.NAME_REPOINIT.equals(ext.getName()) ) {
-                final Section section = new Section("repoinit");
-                section.setContents(ext.getText());
-                f.getAdditionalSections().add(section);
-            } else if ( ext.isRequired() ) {
-                LOGGER.error("Unable to convert required extension {}", ext.getName());
-                System.exit(1);
-            }
-        }
-
-        LOGGER.info("Writing feature...");
-        String out = output;
-        if ( index > 0 ) {
-            final int lastDot = out.lastIndexOf('.');
-            if ( lastDot == -1 ) {
-                out = out + "_" + String.valueOf(index);
-            } else {
-                out = out.substring(0, lastDot) + "_" + String.valueOf(index) + out.substring(lastDot);
-            }
-        }
-        final File file = new File(out);
-        final Model m = new Model();
-        m.getFeatures().add(f);
-        try ( final FileWriter writer = new FileWriter(file)) {
-            ModelWriter.write(writer, m);
-        } catch ( final IOException ioe) {
-            LOGGER.error("Unable to write feature to {} : {}", out, ioe.getMessage(), ioe);
-            System.exit(1);
-        }
-    }
 }
diff --git a/featuremodel/feature-modelconverter/src/main/java/org/apache/sling/feature/modelconverter/impl/Main.java b/featuremodel/feature-modelconverter/src/main/java/org/apache/sling/feature/modelconverter/impl/ProvisioningToFeature.java
similarity index 61%
copy from featuremodel/feature-modelconverter/src/main/java/org/apache/sling/feature/modelconverter/impl/Main.java
copy to featuremodel/feature-modelconverter/src/main/java/org/apache/sling/feature/modelconverter/impl/ProvisioningToFeature.java
index ccc3f85..e3a4a52 100644
--- a/featuremodel/feature-modelconverter/src/main/java/org/apache/sling/feature/modelconverter/impl/Main.java
+++ b/featuremodel/feature-modelconverter/src/main/java/org/apache/sling/feature/modelconverter/impl/ProvisioningToFeature.java
@@ -16,25 +16,6 @@
  */
 package org.apache.sling.feature.modelconverter.impl;
 
-import java.io.File;
-import java.io.FileReader;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Enumeration;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.commons.cli.CommandLine;
-import org.apache.commons.cli.CommandLineParser;
-import org.apache.commons.cli.DefaultParser;
-import org.apache.commons.cli.Option;
-import org.apache.commons.cli.Options;
-import org.apache.commons.cli.ParseException;
 import org.apache.sling.feature.Application;
 import org.apache.sling.feature.ArtifactId;
 import org.apache.sling.feature.Bundles;
@@ -43,13 +24,10 @@ import org.apache.sling.feature.Extension;
 import org.apache.sling.feature.ExtensionType;
 import org.apache.sling.feature.Extensions;
 import org.apache.sling.feature.KeyValueMap;
-import org.apache.sling.feature.process.FeatureResolver;
-import org.apache.sling.feature.resolver.FrameworkResolver;
 import org.apache.sling.feature.support.ArtifactHandler;
 import org.apache.sling.feature.support.ArtifactManager;
 import org.apache.sling.feature.support.ArtifactManagerConfig;
 import org.apache.sling.feature.support.FeatureUtil;
-import org.apache.sling.feature.support.json.ApplicationJSONReader;
 import org.apache.sling.feature.support.json.ApplicationJSONWriter;
 import org.apache.sling.feature.support.json.FeatureJSONWriter;
 import org.apache.sling.provisioning.model.Artifact;
@@ -66,369 +44,54 @@ import org.apache.sling.provisioning.model.RunMode;
 import org.apache.sling.provisioning.model.Section;
 import org.apache.sling.provisioning.model.Traceable;
 import org.apache.sling.provisioning.model.io.ModelReader;
-import org.apache.sling.provisioning.model.io.ModelWriter;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-public class Main {
-
-    private static Logger LOGGER;
-
-    private static String runModes;
-
-    private static String output;
-
-    private static String input;
-
-    private static boolean createApp = false;
-
-    private static boolean includeModelInfo = false;
-
-    private static String repoUrls;
-
-    private static String propsFile;
-
-    /**
-     * Parse the command line parameters and update a configuration object.
-     * @param args Command line parameters
-     * @return Configuration object.
-     */
-    private static void parseArgs(final String[] args) {
-        final Option repoOption =  Option.builder("u").hasArg().argName("Set repository url")
-                .desc("repository url").required().build();
-
-        final Option modelOption =  new Option("f", true, "Set feature files/directories");
-        final Option propsOption =  new Option("p", true, "sling.properties file");
-        final Option runModeOption =  new Option("r", true, "Set run modes (comma separated)");
-        final Option createAppOption = new Option("a", false, "If enabled, create application json");
-        createAppOption.setArgs(0);
-        final Option includeModelOption = new Option("i", false, "Include model filename as metadata for artifacts");
-        includeModelOption.setArgs(0);
-
-        final Option outputOption = Option.builder("o").hasArg().argName("Set output file")
-                .desc("output file").build();
-
-        final Options options = new Options();
-        options.addOption(repoOption);
-        options.addOption(modelOption);
-        options.addOption(createAppOption);
-        options.addOption(outputOption);
-        options.addOption(includeModelOption);
-        options.addOption(propsOption);
-        options.addOption(runModeOption);
-
-        final CommandLineParser parser = new DefaultParser();
-        try {
-            final CommandLine cl = parser.parse(options, args);
-
-            if ( cl.hasOption(repoOption.getOpt()) ) {
-                repoUrls = cl.getOptionValue(repoOption.getOpt());
-            }
-            if ( cl.hasOption(modelOption.getOpt()) ) {
-                input = cl.getOptionValue(modelOption.getOpt());
-            }
-            if ( cl.hasOption(createAppOption.getOpt()) ) {
-                createApp = true;
-            }
-            if ( cl.hasOption(includeModelOption.getOpt()) ) {
-                includeModelInfo = true;
-            }
-            if ( cl.hasOption(runModeOption.getOpt()) ) {
-                runModes = cl.getOptionValue(runModeOption.getOpt());
-            }
-            if ( cl.hasOption(outputOption.getOpt()) ) {
-                output = cl.getOptionValue(outputOption.getOpt());
-            }
-            if ( cl.hasOption(propsOption.getOpt()) ) {
-                propsFile = cl.getOptionValue(propsOption.getOpt());
-            }
-        } catch ( final ParseException pe) {
-            LOGGER.error("Unable to parse command line: {}", pe.getMessage(), pe);
-            System.exit(1);
-        }
-        if ( input == null ) {
-            LOGGER.error("Required argument missing: model file or directory");
-            System.exit(1);
-        }
-    }
-
-    private static ArtifactManager getArtifactManager() {
-        final ArtifactManagerConfig amConfig = new ArtifactManagerConfig();
-        if ( repoUrls != null ) {
-            amConfig.setRepositoryUrls(repoUrls.split(","));
-        }
-        try {
-            return ArtifactManager.getArtifactManager(amConfig);
-        } catch ( IOException ioe) {
-            LOGGER.error("Unable to create artifact manager " + ioe.getMessage(), ioe);
-            System.exit(1);
-        }
-        // we never reach this, but have to keep the compiler happy
-        return null;
-    }
-
-    private static FeatureResolver getFeatureResolver(ArtifactManager am) {
-        return new FrameworkResolver(am, Collections.emptyMap());
-    }
-
-    public static void main(final String[] args) {
-        // setup logging
-        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");
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
-        LOGGER = LoggerFactory.getLogger("modelconverter");
+public class ProvisioningToFeature {
+    private static Logger LOGGER = LoggerFactory.getLogger(ProvisioningToFeature.class);
 
-        LOGGER.info("Apache Sling Provisiong Model to Feature Application Converter");
-        LOGGER.info("");
+    public static void convert(List<File> files,  String outputFile, String runModes, boolean createApp,
+            boolean includeModelInfo, String propsFile) {
+        final Model model = createModel(files, runModes, includeModelInfo);
 
-        parseArgs(args);
+        if ( createApp ) {
+            final Application app = buildApplication(model, propsFile);
 
-        final ArtifactManagerConfig amConfig = new ArtifactManagerConfig();
-        if ( repoUrls != null ) {
-            amConfig.setRepositoryUrls(repoUrls.split(","));
-        }
-        final ArtifactManager am = getArtifactManager();
-
-        final File f = new File(input);
-        final List<File> files = new ArrayList<>();
-        if ( f.isDirectory() ) {
-            for(final File file : f.listFiles()) {
-                if ( file.isFile() && !file.getName().startsWith(".") ) {
-                    files.add(file);
-                }
-            }
-            if ( files.isEmpty() ) {
-                LOGGER.error("No files found in {}", f);
-                System.exit(1);
-            }
-            Collections.sort(files);
+            writeApplication(app, outputFile);
         } else {
-            files.add(f);
-        }
-        boolean isJson = false;
-        boolean isTxt = false;
-        for(final File t : files) {
-            if ( t.getName().endsWith(".json") ) {
-                if ( isTxt ) {
-                    LOGGER.error("Input files are a mixture of JSON and txt");
-                    System.exit(1);
-                }
-                isJson = true;
-            } else {
-                if ( isJson ) {
-                    LOGGER.error("Input files are a mixture of JSON and txt");
-                    System.exit(1);
-                }
-                isTxt = true;
-            }
-        }
-
-        if ( isTxt ) {
-            if ( output == null ) {
-                output = createApp ? "application.json" : "feature.json";
-            }
-            final Model model = createModel(files, runModes);
-
-            if ( createApp ) {
-                final Application app = buildApplication(model);
-
-                writeApplication(app, output);
-            } else {
-                final List<org.apache.sling.feature.Feature> features = buildFeatures(model);
-                int index = 1;
-                for(final org.apache.sling.feature.Feature feature : features) {
-                    writeFeature(feature, output, features.size() > 1 ? index : 0);
-                    index++;
-                }
-            }
-        } else {
-            if ( output == null ) {
-                output = createApp ? "application.txt" : "feature.txt";
-            }
-            try (FeatureResolver fr = getFeatureResolver(am)) {
-                if ( createApp ) {
-                    // each file is an application
-                    int index = 1;
-                    for(final File appFile : files ) {
-                        try ( final FileReader r = new FileReader(appFile) ) {
-                            final Application app = ApplicationJSONReader.read(r);
-                            convert(app, files.size() > 1 ? index : 0);
-                        }
-                        index++;
-                    }
-                } else {
-                    final Application app = FeatureUtil.assembleApplication(null, am, fr, files.stream()
-                            .map(File::getAbsolutePath)
-                            .toArray(String[]::new));
-                    convert(app, 0);
-                }
-            } catch ( final IOException ioe) {
-                LOGGER.error("Unable to read feature/application files " + ioe.getMessage(), ioe);
-                System.exit(1);
-            } catch ( final Exception e) {
-                LOGGER.error("Problem generating application", e);
-                System.exit(1);
-            }
-        }
-    }
-
-    private static List<org.apache.sling.feature.Feature> buildFeatures(final Model model) {
-        final List<org.apache.sling.feature.Feature> features = new ArrayList<>();
-
-        for(final Feature feature : model.getFeatures() ) {
-            final String idString;
-            // use a default name if not present or not usable as a Maven artifactId ( starts with ':')
-            if ( feature.getName() != null && !feature.isSpecial() ) {
-                if ( feature.getVersion() != null ) {
-                    idString = "generated/" + feature.getName() + "/" + feature.getVersion();
-                } else {
-                    idString = "generated/" + feature.getName() + "/1.0.0";
-                }
-            } else {
-                idString = "generated/feature/1.0.0";
+            final List<org.apache.sling.feature.Feature> features = buildFeatures(model);
+            int index = 1;
+            for(final org.apache.sling.feature.Feature feature : features) {
+                writeFeature(feature, outputFile, features.size() > 1 ? index : 0);
+                index++;
             }
-            final org.apache.sling.feature.Feature f = new org.apache.sling.feature.Feature(ArtifactId.parse(idString));
-            features.add(f);
-
-            buildFromFeature(feature, f.getBundles(), f.getConfigurations(), f.getExtensions(), f.getFrameworkProperties());
-        }
-
-        return features;
-    }
-
-    private static Application buildApplication(final Model model) {
-        final Application app = new Application();
-
-        for(final Feature feature : model.getFeatures() ) {
-            buildFromFeature(feature, app.getBundles(), app.getConfigurations(), app.getExtensions(), app.getFrameworkProperties());
-        }
-
-        // hard coded dependency to launchpad api
-        final org.apache.sling.feature.Artifact a = new org.apache.sling.feature.Artifact(ArtifactId.parse("org.apache.sling/org.apache.sling.launchpad.api/1.2.0"));
-        a.getMetadata().put(org.apache.sling.feature.Artifact.KEY_START_ORDER, "1");
-        // sling.properties (TODO)
-        if ( propsFile == null ) {
-            app.getFrameworkProperties().put("org.osgi.framework.bootdelegation", "sun.*,com.sun.*");
-        } else {
-
-        }
-        // felix framework hard coded for now
-        app.setFramework(FeatureUtil.getFelixFrameworkId(null));
-        return app;
-    }
-
-    private static void buildFromFeature(final Feature feature,
-            final Bundles bundles,
-            final Configurations configurations,
-            final Extensions extensions,
-            final KeyValueMap properties) {
-        Extension cpExtension = extensions.getByName(Extension.NAME_CONTENT_PACKAGES);
-        for(final RunMode runMode : feature.getRunModes() ) {
-            if ( !ModelConstants.FEATURE_LAUNCHPAD.equals(feature.getName()) ) {
-                for(final ArtifactGroup group : runMode.getArtifactGroups()) {
-                    for(final Artifact artifact : group) {
-                        final ArtifactId id = ArtifactId.fromMvnUrl(artifact.toMvnUrl());
-                        final org.apache.sling.feature.Artifact newArtifact = new org.apache.sling.feature.Artifact(id);
-
-                        for(final Map.Entry<String, String> entry : artifact.getMetadata().entrySet()) {
-                            newArtifact.getMetadata().put(entry.getKey(), entry.getValue());
-                        }
-
-                        if ( newArtifact.getId().getType().equals("zip") ) {
-                            if ( cpExtension == null ) {
-                                cpExtension = new Extension(ExtensionType.ARTIFACTS, Extension.NAME_CONTENT_PACKAGES, true);
-                                extensions.add(cpExtension);
-                            }
-                            cpExtension.getArtifacts().add(newArtifact);
-                        } else {
-                            int startLevel = group.getStartLevel();
-                            if ( ModelConstants.FEATURE_BOOT.equals(feature.getName()) ) {
-                                startLevel = 1;
-                            } else if ( startLevel == 0 ) {
-                                startLevel = 20;
-                            }
-                            newArtifact.getMetadata().put(org.apache.sling.feature.Artifact.KEY_START_ORDER, String.valueOf(startLevel));
-                            bundles.add(newArtifact);
-                        }
-                    }
-                }
-            }
-
-            for(final Configuration cfg : runMode.getConfigurations()) {
-                final org.apache.sling.feature.Configuration newCfg;
-                if ( cfg.getFactoryPid() != null ) {
-                    newCfg = new org.apache.sling.feature.Configuration(cfg.getFactoryPid(), cfg.getPid());
-                } else {
-                    newCfg = new org.apache.sling.feature.Configuration(cfg.getPid());
-                }
-                final Enumeration<String> keys = cfg.getProperties().keys();
-                while ( keys.hasMoreElements() ) {
-                    final String key = keys.nextElement();
-                    newCfg.getProperties().put(key, cfg.getProperties().get(key));
-                }
-                configurations.add(newCfg);
-            }
-
-            for(final Map.Entry<String, String> prop : runMode.getSettings()) {
-                properties.put(prop.getKey(), prop.getValue());
-            }
-        }
-        Extension repoExtension = extensions.getByName(Extension.NAME_REPOINIT);
-        for(final Section sect : feature.getAdditionalSections("repoinit")) {
-            final String text = sect.getContents();
-            if ( repoExtension == null ) {
-                repoExtension = new Extension(ExtensionType.TEXT, Extension.NAME_REPOINIT, true);
-                extensions.add(repoExtension);
-                repoExtension.setText(text);
-            } else {
-                repoExtension.setText(repoExtension.getText() + "\n\n" + text);
-            }
-        }
-    }
-
-    private static void writeApplication(final Application app, final String out) {
-        LOGGER.info("Writing application...");
-        final File file = new File(out);
-        try ( final FileWriter writer = new FileWriter(file)) {
-            ApplicationJSONWriter.write(writer, app);
-        } catch ( final IOException ioe) {
-            LOGGER.error("Unable to write application to {} : {}", out, ioe.getMessage(), ioe);
-            System.exit(1);
-        }
-    }
-
-    private static void writeFeature(final org.apache.sling.feature.Feature f, String out, final int index) {
-        LOGGER.info("Writing feature...");
-        if ( index > 0 ) {
-            final int lastDot = out.lastIndexOf('.');
-            if ( lastDot == -1 ) {
-                out = out + "_" + String.valueOf(index);
-            } else {
-                out = out.substring(0, lastDot) + "_" + String.valueOf(index) + out.substring(lastDot);
-            }
-        }
-        final File file = new File(out);
-        try ( final FileWriter writer = new FileWriter(file)) {
-            FeatureJSONWriter.write(writer, f);
-        } catch ( final IOException ioe) {
-            LOGGER.error("Unable to write feature to {} : {}", out, ioe.getMessage(), ioe);
-            System.exit(1);
         }
     }
 
     /**
      * Read the models and prepare the model
      * @param files The model files
+     * @param includeModelInfo
      */
     private static Model createModel(final List<File> files,
-            final String runModes) {
+            final String runModes, boolean includeModelInfo) {
         LOGGER.info("Assembling model...");
         Model model = null;
         for(final File initFile : files) {
             try {
-                model = processModel(model, initFile);
+                model = processModel(model, initFile, includeModelInfo);
             } catch ( final IOException iae) {
                 LOGGER.error("Unable to read provisioning model {} : {}", initFile, iae.getMessage(), iae);
                 System.exit(1);
@@ -464,11 +127,12 @@ public class Main {
      * Process the given model and merge it into the provided model
      * @param model The already read model
      * @param modelFile The model file
+     * @param includeModelInfo
      * @return The merged model
      * @throws IOException If reading fails
      */
     private static Model processModel(Model model,
-            final File modelFile) throws IOException {
+            final File modelFile, boolean includeModelInfo) throws IOException {
         LOGGER.info("- reading model {}", modelFile);
 
         final Model nextModel = readProvisioningModel(modelFile);
@@ -501,7 +165,7 @@ public class Main {
                                     "txt");
 
                             final ArtifactHandler handler = mgr.getArtifactHandler(correctedId.toMvnUrl());
-                            model = processModel(model, handler.getFile());
+                            model = processModel(model, handler.getFile(), includeModelInfo);
 
                             removeList.add(a);
                         } else {
@@ -661,68 +325,134 @@ public class Main {
         }
     }
 
-    private static void convert(final Application app, final int index) {
-        final Feature f = new Feature("application");
+    private static Application buildApplication(final Model model, String propsFile) {
+        final Application app = new Application();
+
+        for(final Feature feature : model.getFeatures() ) {
+            buildFromFeature(feature, app.getBundles(), app.getConfigurations(), app.getExtensions(), app.getFrameworkProperties());
+        }
+
+        // hard coded dependency to launchpad api
+        final org.apache.sling.feature.Artifact a = new org.apache.sling.feature.Artifact(ArtifactId.parse("org.apache.sling/org.apache.sling.launchpad.api/1.2.0"));
+        a.getMetadata().put(org.apache.sling.feature.Artifact.KEY_START_ORDER, "1");
+        // sling.properties (TODO)
+        if ( propsFile == null ) {
+            app.getFrameworkProperties().put("org.osgi.framework.bootdelegation", "sun.*,com.sun.*");
+        } else {
 
-        // bundles
-        for(final org.apache.sling.feature.Artifact bundle : app.getBundles()) {
-            final ArtifactId id = bundle.getId();
-            final Artifact newBundle = new Artifact(id.getGroupId(), id.getArtifactId(), id.getVersion(), id.getClassifier(), id.getType());
-            for(final Map.Entry<String, String> prop : bundle.getMetadata()) {
-                newBundle.getMetadata().put(prop.getKey(), prop.getValue());
+        }
+        // felix framework hard coded for now
+        app.setFramework(FeatureUtil.getFelixFrameworkId(null));
+        return app;
+    }
+
+    private static void buildFromFeature(final Feature feature,
+            final Bundles bundles,
+            final Configurations configurations,
+            final Extensions extensions,
+            final KeyValueMap properties) {
+        Extension cpExtension = extensions.getByName(Extension.NAME_CONTENT_PACKAGES);
+        for(final RunMode runMode : feature.getRunModes() ) {
+            if ( !ModelConstants.FEATURE_LAUNCHPAD.equals(feature.getName()) ) {
+                for(final ArtifactGroup group : runMode.getArtifactGroups()) {
+                    for(final Artifact artifact : group) {
+                        final ArtifactId id = ArtifactId.fromMvnUrl(artifact.toMvnUrl());
+                        final org.apache.sling.feature.Artifact newArtifact = new org.apache.sling.feature.Artifact(id);
+
+                        for(final Map.Entry<String, String> entry : artifact.getMetadata().entrySet()) {
+                            newArtifact.getMetadata().put(entry.getKey(), entry.getValue());
+                        }
+
+                        if ( newArtifact.getId().getType().equals("zip") ) {
+                            if ( cpExtension == null ) {
+                                cpExtension = new Extension(ExtensionType.ARTIFACTS, Extension.NAME_CONTENT_PACKAGES, true);
+                                extensions.add(cpExtension);
+                            }
+                            cpExtension.getArtifacts().add(newArtifact);
+                        } else {
+                            int startLevel = group.getStartLevel();
+                            if ( ModelConstants.FEATURE_BOOT.equals(feature.getName()) ) {
+                                startLevel = 1;
+                            } else if ( startLevel == 0 ) {
+                                startLevel = 20;
+                            }
+                            newArtifact.getMetadata().put(org.apache.sling.feature.Artifact.KEY_START_ORDER, String.valueOf(startLevel));
+                            bundles.add(newArtifact);
+                        }
+                    }
+                }
             }
-            int startLevel = bundle.getStartOrder();
-            if ( startLevel == 0 ) {
-                startLevel = 20;
+
+            for(final Configuration cfg : runMode.getConfigurations()) {
+                final org.apache.sling.feature.Configuration newCfg;
+                if ( cfg.getFactoryPid() != null ) {
+                    newCfg = new org.apache.sling.feature.Configuration(cfg.getFactoryPid(), cfg.getPid());
+                } else {
+                    newCfg = new org.apache.sling.feature.Configuration(cfg.getPid());
+                }
+                final Enumeration<String> keys = cfg.getProperties().keys();
+                while ( keys.hasMoreElements() ) {
+                    final String key = keys.nextElement();
+                    newCfg.getProperties().put(key, cfg.getProperties().get(key));
+                }
+                configurations.add(newCfg);
             }
-            f.getOrCreateRunMode(null).getOrCreateArtifactGroup(startLevel).add(newBundle);
-        }
 
-        // configurations
-        for(final org.apache.sling.feature.Configuration cfg : app.getConfigurations()) {
-            final Configuration c;
-            if ( cfg.isFactoryConfiguration() ) {
-                c = new Configuration(cfg.getName(), cfg.getFactoryPid());
-            } else {
-                c = new Configuration(cfg.getPid(), null);
+            for(final Map.Entry<String, String> prop : runMode.getSettings()) {
+                properties.put(prop.getKey(), prop.getValue());
             }
-            final Enumeration<String> keys = cfg.getProperties().keys();
-            while ( keys.hasMoreElements() ) {
-                final String key = keys.nextElement();
-                c.getProperties().put(key, cfg.getProperties().get(key));
+        }
+        Extension repoExtension = extensions.getByName(Extension.NAME_REPOINIT);
+        for(final Section sect : feature.getAdditionalSections("repoinit")) {
+            final String text = sect.getContents();
+            if ( repoExtension == null ) {
+                repoExtension = new Extension(ExtensionType.TEXT, Extension.NAME_REPOINIT, true);
+                extensions.add(repoExtension);
+                repoExtension.setText(text);
+            } else {
+                repoExtension.setText(repoExtension.getText() + "\n\n" + text);
             }
-            f.getOrCreateRunMode(null).getConfigurations().add(c);
         }
+    }
 
-        // framework properties
-        for(final Map.Entry<String, String> prop : app.getFrameworkProperties()) {
-            f.getOrCreateRunMode(null).getSettings().put(prop.getKey(), prop.getValue());
-        }
 
-        // extensions: content packages and repoinit
-        for(final Extension ext : app.getExtensions()) {
-            if ( Extension.NAME_CONTENT_PACKAGES.equals(ext.getName()) ) {
-                for(final org.apache.sling.feature.Artifact cp : ext.getArtifacts() ) {
-                    final ArtifactId id = cp.getId();
-                    final Artifact newCP = new Artifact(id.getGroupId(), id.getArtifactId(), id.getVersion(), id.getClassifier(), id.getType());
-                    for(final Map.Entry<String, String> prop : cp.getMetadata()) {
-                        newCP.getMetadata().put(prop.getKey(), prop.getValue());
-                    }
-                    f.getOrCreateRunMode(null).getOrCreateArtifactGroup(0).add(newCP);
-                }
+    private static List<org.apache.sling.feature.Feature> buildFeatures(final Model model) {
+        final List<org.apache.sling.feature.Feature> features = new ArrayList<>();
 
-            } else if ( Extension.NAME_REPOINIT.equals(ext.getName()) ) {
-                final Section section = new Section("repoinit");
-                section.setContents(ext.getText());
-                f.getAdditionalSections().add(section);
-            } else if ( ext.isRequired() ) {
-                LOGGER.error("Unable to convert required extension {}", ext.getName());
-                System.exit(1);
+        for(final Feature feature : model.getFeatures() ) {
+            final String idString;
+            // use a default name if not present or not usable as a Maven artifactId ( starts with ':')
+            if ( feature.getName() != null && !feature.isSpecial() ) {
+                if ( feature.getVersion() != null ) {
+                    idString = "generated/" + feature.getName() + "/" + feature.getVersion();
+                } else {
+                    idString = "generated/" + feature.getName() + "/1.0.0";
+                }
+            } else {
+                idString = "generated/feature/1.0.0";
             }
+            final org.apache.sling.feature.Feature f = new org.apache.sling.feature.Feature(ArtifactId.parse(idString));
+            features.add(f);
+
+            buildFromFeature(feature, f.getBundles(), f.getConfigurations(), f.getExtensions(), f.getFrameworkProperties());
         }
 
+        return features;
+    }
+
+    private static void writeApplication(final Application app, final String out) {
+        LOGGER.info("Writing application...");
+        final File file = new File(out);
+        try ( final FileWriter writer = new FileWriter(file)) {
+            ApplicationJSONWriter.write(writer, app);
+        } catch ( final IOException ioe) {
+            LOGGER.error("Unable to write application to {} : {}", out, ioe.getMessage(), ioe);
+            System.exit(1);
+        }
+    }
+
+    private static void writeFeature(final org.apache.sling.feature.Feature f, String out, final int index) {
         LOGGER.info("Writing feature...");
-        String out = output;
         if ( index > 0 ) {
             final int lastDot = out.lastIndexOf('.');
             if ( lastDot == -1 ) {
@@ -732,10 +462,8 @@ public class Main {
             }
         }
         final File file = new File(out);
-        final Model m = new Model();
-        m.getFeatures().add(f);
         try ( final FileWriter writer = new FileWriter(file)) {
-            ModelWriter.write(writer, m);
+            FeatureJSONWriter.write(writer, f);
         } catch ( final IOException ioe) {
             LOGGER.error("Unable to write feature to {} : {}", out, ioe.getMessage(), ioe);
             System.exit(1);
diff --git a/featuremodel/feature-resolver/src/main/java/org/apache/sling/feature/resolver/FrameworkResolver.java b/featuremodel/feature-resolver/src/main/java/org/apache/sling/feature/resolver/FrameworkResolver.java
index 249f4f2..77a1a51 100644
--- a/featuremodel/feature-resolver/src/main/java/org/apache/sling/feature/resolver/FrameworkResolver.java
+++ b/featuremodel/feature-resolver/src/main/java/org/apache/sling/feature/resolver/FrameworkResolver.java
@@ -29,6 +29,7 @@ import org.apache.sling.feature.resolver.impl.ResolveContextImpl;
 import org.apache.sling.feature.support.ArtifactManager;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.BundleException;
+import org.osgi.framework.Constants;
 import org.osgi.framework.ServiceReference;
 import org.osgi.framework.launch.Framework;
 import org.osgi.framework.launch.FrameworkFactory;
@@ -48,16 +49,42 @@ import java.io.File;
 import java.io.IOException;
 import java.util.Collections;
 import java.util.HashMap;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.ServiceLoader;
+import java.util.Set;
 
 public class FrameworkResolver implements FeatureResolver {
     private final ArtifactManager artifactManager;
     private final Resolver resolver;
     private final FeatureResource frameworkResource;
     private final Framework framework;
+    private String tempDirToBeDeleted = null;
+
+    public FrameworkResolver(ArtifactManager am) {
+        this(am, getTempDirProps());
+
+        // Since we create the temp dir, the close() method needs to delete it.
+        tempDirToBeDeleted = framework.getBundleContext().getProperty(Constants.FRAMEWORK_STORAGE);
+    }
+
+    private static Map<String, String> getTempDirProps() {
+        try {
+            String temp = Files.createTempDirectory("frameworkresolver").toFile().getAbsolutePath();
+            return Collections.singletonMap(Constants.FRAMEWORK_STORAGE, temp);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
 
     public FrameworkResolver(ArtifactManager am, Map<String, String> frameworkProperties) {
         artifactManager = am;
@@ -101,6 +128,13 @@ public class FrameworkResolver implements FeatureResolver {
     @Override
     public void close() throws Exception {
         framework.stop();
+
+        if (tempDirToBeDeleted != null) {
+            Files.walk(Paths.get(tempDirToBeDeleted))
+                .sorted(Comparator.reverseOrder())
+                .map(Path::toFile)
+                .forEach(File::delete);
+        }
     }
 
     @Override
diff --git a/featuremodel/feature-support/src/main/java/org/apache/sling/feature/support/FeatureUtil.java b/featuremodel/feature-support/src/main/java/org/apache/sling/feature/support/FeatureUtil.java
index 86bd08c..70a7b50 100644
--- a/featuremodel/feature-support/src/main/java/org/apache/sling/feature/support/FeatureUtil.java
+++ b/featuremodel/feature-support/src/main/java/org/apache/sling/feature/support/FeatureUtil.java
@@ -16,15 +16,6 @@
  */
 package org.apache.sling.feature.support;
 
-import java.io.File;
-import java.io.FileReader;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-
 import org.apache.sling.feature.Application;
 import org.apache.sling.feature.ArtifactId;
 import org.apache.sling.feature.Feature;
@@ -34,6 +25,15 @@ import org.apache.sling.feature.process.FeatureProvider;
 import org.apache.sling.feature.process.FeatureResolver;
 import org.apache.sling.feature.support.json.FeatureJSONReader;
 
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
 public class FeatureUtil {
     /**
      * Get an artifact id for the Apache Felix framework
@@ -272,7 +272,7 @@ public class FeatureUtil {
      * @return The read feature
      * @throws IOException If reading fails
      */
-    private static Feature getFeature(final String file,
+    public static Feature getFeature(final String file,
             final ArtifactManager artifactManager)
     throws IOException {
         final ArtifactHandler featureArtifact = artifactManager.getArtifactHandler(file);
diff --git a/featuremodel/feature-support/src/main/java/org/apache/sling/feature/support/json/JSONReaderBase.java b/featuremodel/feature-support/src/main/java/org/apache/sling/feature/support/json/JSONReaderBase.java
index ee66e68..3f02731 100644
--- a/featuremodel/feature-support/src/main/java/org/apache/sling/feature/support/json/JSONReaderBase.java
+++ b/featuremodel/feature-support/src/main/java/org/apache/sling/feature/support/json/JSONReaderBase.java
@@ -159,7 +159,8 @@ abstract class JSONReaderBase {
                 }
                 if ( bundleObj.containsKey(JSONConstants.FEATURE_CONFIGURATIONS) ) {
                     checkType(artifactType + " configurations", bundleObj.get(JSONConstants.FEATURE_CONFIGURATIONS), Map.class);
-                    addConfigurations(bundleObj, artifact, container);
+                    List<Configuration> bundleConfigs = addConfigurations(bundleObj, artifact, container);
+                    artifact.getMetadata().put(JSONConstants.FEATURE_CONFIGURATIONS, bundleConfigs);
                 }
             }
             artifacts.add(artifact);
@@ -171,7 +172,7 @@ abstract class JSONReaderBase {
         return val;
     }
 
-    protected void addConfigurations(final Map<String, Object> map,
+    protected List<Configuration> addConfigurations(final Map<String, Object> map,
             final Artifact artifact,
             final Configurations container) throws IOException {
         final JSONUtil.Report report = new JSONUtil.Report();
@@ -191,6 +192,8 @@ abstract class JSONReaderBase {
             }
             throw new IOException(builder.toString());
         }
+
+        List<Configuration> newConfigs = new ArrayList<>();
         for(final Config c : configs) {
             final int pos = c.getPid().indexOf('~');
             final Configuration config;
@@ -220,8 +223,9 @@ abstract class JSONReaderBase {
                 }
             }
             container.add(config);
+            newConfigs.add(config);
         }
-
+        return newConfigs;
     }
 
 
diff --git a/featuremodel/feature/src/main/java/org/apache/sling/feature/KeyValueMap.java b/featuremodel/feature/src/main/java/org/apache/sling/feature/KeyValueMap.java
index ce8c1c8..675ee21 100644
--- a/featuremodel/feature/src/main/java/org/apache/sling/feature/KeyValueMap.java
+++ b/featuremodel/feature/src/main/java/org/apache/sling/feature/KeyValueMap.java
@@ -28,7 +28,7 @@ public class KeyValueMap
     implements Iterable<Map.Entry<String, String>> {
 
     /** The map holding the actual key value pairs. */
-    private final Map<String, String> properties = new TreeMap<>();
+    private final Map<String, Object> properties = new TreeMap<>();
 
     /**
      * Get an item from the map.
@@ -36,6 +36,14 @@ public class KeyValueMap
      * @return The item or {@code null}.
      */
     public String get(final String key) {
+        Object val = this.properties.get(key);
+        if (val instanceof String) {
+            return (String) val;
+        }
+        return null;
+    }
+
+    public Object getObject(final String key) {
         return this.properties.get(key);
     }
 
@@ -44,7 +52,7 @@ public class KeyValueMap
      * @param key The key of the item.
      * @param value The value
      */
-    public void put(final String key, final String value) {
+    public void put(final String key, final Object value) {
         this.properties.put(key, value);
     }
 
@@ -53,7 +61,7 @@ public class KeyValueMap
      * @param key The key of the item.
      * @return The previously stored value for the key or {@code null}.
      */
-    public String remove(final String key) {
+    public Object remove(final String key) {
         return this.properties.remove(key);
     }
 
@@ -67,7 +75,14 @@ public class KeyValueMap
 
     @Override
     public Iterator<Entry<String, String>> iterator() {
-        return this.properties.entrySet().iterator();
+        // TODO hack
+        Map<String, String> copied = new TreeMap<>();
+        for (Entry<String, Object> entry : properties.entrySet()) {
+            if (entry.getValue() instanceof String) {
+                copied.put(entry.getKey(), (String) entry.getValue());
+            }
+        }
+        return copied.entrySet().iterator();
     }
 
     /**
diff --git a/featuremodel/feature/src/main/java/org/apache/sling/feature/process/ApplicationBuilder.java b/featuremodel/feature/src/main/java/org/apache/sling/feature/process/ApplicationBuilder.java
index 2a9698a..71fc8b0 100644
--- a/featuremodel/feature/src/main/java/org/apache/sling/feature/process/ApplicationBuilder.java
+++ b/featuremodel/feature/src/main/java/org/apache/sling/feature/process/ApplicationBuilder.java
@@ -23,6 +23,7 @@ import org.apache.sling.feature.Feature;
 import org.apache.sling.feature.FeatureResource;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -117,15 +118,21 @@ public class ApplicationBuilder {
             }
         }
 
-        // order by dependency chain
-        final List<FeatureResource> sortedResources = resolver.orderResources(featureList);
+        final List<Feature> sortedFeatures;
+        if (resolver != null) {
+            // order by dependency chain
+            final List<FeatureResource> sortedResources = resolver.orderResources(featureList);
 
-        final List<Feature> sortedFeatures = new ArrayList<>();
-        for (final FeatureResource fr : sortedResources) {
-            Feature f = fr.getFeature();
-            if (!sortedFeatures.contains(f)) {
-                sortedFeatures.add(f);
+            sortedFeatures = new ArrayList<>();
+            for (final FeatureResource fr : sortedResources) {
+                Feature f = fr.getFeature();
+                if (!sortedFeatures.contains(f)) {
+                    sortedFeatures.add(f);
+                }
             }
+        } else {
+            sortedFeatures = featureList;
+            Collections.sort(sortedFeatures);
         }
 
         // assemble

-- 
To stop receiving notification emails like this one, please contact
davidb@apache.org.

[sling-whiteboard] 03/04: Refactor feature file generator to use JsonWriter

Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 6b51e9f49b25848214c1717dfa8ac7f729b7d986
Author: David Bosschaert <da...@gmail.com>
AuthorDate: Mon Mar 12 17:29:14 2018 +0000

    Refactor feature file generator to use JsonWriter
    
    This is to support 'pretty' formatting of output files, rather than a
    single line that contains the whole file.
---
 .../support/json/ApplicationJSONWriter.java        |  36 +++--
 .../support/json/ConfigurationJSONWriter.java      |  23 +++-
 .../feature/support/json/FeatureJSONWriter.java    | 152 +++++++++++----------
 .../sling/feature/support/json/JSONWriterBase.java | 133 +++++++++---------
 .../support/json/FeatureJSONWriterTest.java        |  15 +-
 .../src/test/resources/features/test.json          |   1 +
 6 files changed, 198 insertions(+), 162 deletions(-)

diff --git a/featuremodel/feature-support/src/main/java/org/apache/sling/feature/support/json/ApplicationJSONWriter.java b/featuremodel/feature-support/src/main/java/org/apache/sling/feature/support/json/ApplicationJSONWriter.java
index 9e8b43f..32835ab 100644
--- a/featuremodel/feature-support/src/main/java/org/apache/sling/feature/support/json/ApplicationJSONWriter.java
+++ b/featuremodel/feature-support/src/main/java/org/apache/sling/feature/support/json/ApplicationJSONWriter.java
@@ -21,11 +21,16 @@ import org.apache.sling.feature.ArtifactId;
 import org.apache.sling.feature.Configuration;
 import org.apache.sling.feature.Configurations;
 
-import javax.json.Json;
-import javax.json.stream.JsonGenerator;
 import java.io.IOException;
 import java.io.Writer;
+import java.util.Collections;
 
+import javax.json.Json;
+import javax.json.JsonArrayBuilder;
+import javax.json.JsonObjectBuilder;
+import javax.json.JsonWriter;
+import javax.json.JsonWriterFactory;
+import javax.json.stream.JsonGenerator;
 
 /**
  * Simple JSON writer for an application
@@ -47,25 +52,25 @@ public class ApplicationJSONWriter extends JSONWriterBase {
 
    private void writeApp(final Writer writer, final Application app)
     throws IOException {
-        final JsonGenerator w = Json.createGenerator(writer);
-        w.writeStartObject();
+       JsonObjectBuilder ob = Json.createObjectBuilder();
 
         // framework
         if ( app.getFramework() != null ) {
-            w.write(JSONConstants.APP_FRAMEWORK, app.getFramework().toMvnId());
+            ob.add(JSONConstants.APP_FRAMEWORK, app.getFramework().toMvnId());
         }
 
         // features
         if ( !app.getFeatureIds().isEmpty() ) {
-            w.writeStartArray(JSONConstants.APP_FEATURES);
+            JsonArrayBuilder featuresArr = Json.createArrayBuilder();
+
             for(final ArtifactId id : app.getFeatureIds()) {
-                w.write(id.toMvnId());
+                featuresArr.add(id.toMvnId());
             }
-            w.writeEnd();
+            ob.add(JSONConstants.APP_FEATURES, featuresArr.build());
         }
 
         // bundles
-        writeBundles(w, app.getBundles(), app.getConfigurations());
+        writeBundles(ob, app.getBundles(), app.getConfigurations());
 
         // configurations
         final Configurations cfgs = new Configurations();
@@ -75,15 +80,18 @@ public class ApplicationJSONWriter extends JSONWriterBase {
                 cfgs.add(cfg);
             }
         }
-        writeConfigurations(w, cfgs);
+        writeConfigurations(ob, cfgs);
 
         // framework properties
-        writeFrameworkProperties(w, app.getFrameworkProperties());
+        writeFrameworkProperties(ob, app.getFrameworkProperties());
 
         // extensions
-        writeExtensions(w, app.getExtensions(), app.getConfigurations());
+        writeExtensions(ob, app.getExtensions(), app.getConfigurations());
 
-        w.writeEnd();
-        w.flush();
+        JsonWriterFactory writerFactory = Json.createWriterFactory(
+                Collections.singletonMap(JsonGenerator.PRETTY_PRINTING, true));
+        JsonWriter jw = writerFactory.createWriter(writer);
+        jw.writeObject(ob.build());
+        jw.close();
     }
 }
diff --git a/featuremodel/feature-support/src/main/java/org/apache/sling/feature/support/json/ConfigurationJSONWriter.java b/featuremodel/feature-support/src/main/java/org/apache/sling/feature/support/json/ConfigurationJSONWriter.java
index d72bad0..1024c51 100644
--- a/featuremodel/feature-support/src/main/java/org/apache/sling/feature/support/json/ConfigurationJSONWriter.java
+++ b/featuremodel/feature-support/src/main/java/org/apache/sling/feature/support/json/ConfigurationJSONWriter.java
@@ -18,10 +18,15 @@ package org.apache.sling.feature.support.json;
 
 import org.apache.sling.feature.Configurations;
 
-import javax.json.Json;
-import javax.json.stream.JsonGenerator;
 import java.io.IOException;
 import java.io.Writer;
+import java.util.Collections;
+
+import javax.json.Json;
+import javax.json.JsonObjectBuilder;
+import javax.json.JsonWriter;
+import javax.json.JsonWriterFactory;
+import javax.json.stream.JsonGenerator;
 
 
 /**
@@ -44,12 +49,16 @@ public class ConfigurationJSONWriter extends JSONWriterBase {
 
     private void writeConfigurations(final Writer writer, final Configurations configs)
     throws IOException {
-        final JsonGenerator w = Json.createGenerator(writer);
-        w.writeStartObject();
+        JsonObjectBuilder ob = Json.createObjectBuilder();
 
-        writeConfigurationsMap(w, configs);
+        // TODO is this correct?
+        ob.add(JSONConstants.FEATURE_CONFIGURATIONS,
+                writeConfigurationsMap(configs));
 
-        w.writeEnd();
-        w.flush();
+        JsonWriterFactory writerFactory = Json.createWriterFactory(
+                Collections.singletonMap(JsonGenerator.PRETTY_PRINTING, true));
+        JsonWriter jw = writerFactory.createWriter(writer);
+        jw.writeObject(ob.build());
+        jw.close();
     }
 }
diff --git a/featuremodel/feature-support/src/main/java/org/apache/sling/feature/support/json/FeatureJSONWriter.java b/featuremodel/feature-support/src/main/java/org/apache/sling/feature/support/json/FeatureJSONWriter.java
index 3c64391..6d52400 100644
--- a/featuremodel/feature-support/src/main/java/org/apache/sling/feature/support/json/FeatureJSONWriter.java
+++ b/featuremodel/feature-support/src/main/java/org/apache/sling/feature/support/json/FeatureJSONWriter.java
@@ -16,24 +16,29 @@
  */
 package org.apache.sling.feature.support.json;
 
-import static org.apache.sling.feature.support.util.ManifestUtil.marshalAttribute;
-import static org.apache.sling.feature.support.util.ManifestUtil.marshalDirective;
+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.Include;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Requirement;
 
 import java.io.IOException;
 import java.io.Writer;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
 import javax.json.Json;
+import javax.json.JsonArrayBuilder;
+import javax.json.JsonObjectBuilder;
+import javax.json.JsonWriter;
+import javax.json.JsonWriterFactory;
 import javax.json.stream.JsonGenerator;
 
-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.Include;
-import org.osgi.resource.Capability;
-import org.osgi.resource.Requirement;
+import static org.apache.sling.feature.support.util.ManifestUtil.marshalAttribute;
+import static org.apache.sling.feature.support.util.ManifestUtil.marshalDirective;
 
 /**
  * Simple JSON writer for a feature
@@ -53,130 +58,128 @@ public class FeatureJSONWriter extends JSONWriterBase {
         w.writeFeature(writer, feature);
     }
 
-    private void writeProperty(final JsonGenerator w, final String key, final String value) {
+    private void writeProperty(final JsonObjectBuilder ob, final String key, final String value) {
         if ( value != null ) {
-            w.write(key, value);
+            ob.add(key, value);
         }
     }
 
     private void writeFeature(final Writer writer, final Feature feature)
     throws IOException {
-        final JsonGenerator w = Json.createGenerator(writer);
-        w.writeStartObject();
-
-        w.write(JSONConstants.FEATURE_ID, feature.getId().toMvnId());
+        JsonObjectBuilder ob = Json.createObjectBuilder();
+        ob.add(JSONConstants.FEATURE_ID, feature.getId().toMvnId());
 
         // title, description, vendor, license
-        writeProperty(w, JSONConstants.FEATURE_TITLE, feature.getTitle());
-        writeProperty(w, JSONConstants.FEATURE_DESCRIPTION, feature.getDescription());
-        writeProperty(w, JSONConstants.FEATURE_VENDOR, feature.getVendor());
-        writeProperty(w, JSONConstants.FEATURE_LICENSE, feature.getLicense());
+        writeProperty(ob, JSONConstants.FEATURE_TITLE, feature.getTitle());
+        writeProperty(ob, JSONConstants.FEATURE_DESCRIPTION, feature.getDescription());
+        writeProperty(ob, JSONConstants.FEATURE_VENDOR, feature.getVendor());
+        writeProperty(ob, JSONConstants.FEATURE_LICENSE, feature.getLicense());
 
         // includes
         if ( !feature.getIncludes().isEmpty() ) {
-            w.writeStartArray(JSONConstants.FEATURE_INCLUDES);
-
+            JsonArrayBuilder incArray = Json.createArrayBuilder();
             for(final Include inc : feature.getIncludes()) {
                 if ( inc.getArtifactExtensionRemovals().isEmpty()
                      && inc.getBundleRemovals().isEmpty()
                      && inc.getConfigurationRemovals().isEmpty()
                      && inc.getFrameworkPropertiesRemovals().isEmpty() ) {
-                    w.write(inc.getId().toMvnId());
+                    incArray.add(inc.getId().toMvnId());
                 } else {
-                    w.writeStartObject();
-                    w.write(JSONConstants.ARTIFACT_ID, inc.getId().toMvnId());
-                    w.writeStartObject(JSONConstants.INCLUDE_REMOVALS);
+                    JsonObjectBuilder includeObj = Json.createObjectBuilder();
+                    includeObj.add(JSONConstants.ARTIFACT_ID, inc.getId().toMvnId());
+
+                    JsonObjectBuilder removalsObj = Json.createObjectBuilder();
                     if ( !inc.getArtifactExtensionRemovals().isEmpty()
                          || inc.getExtensionRemovals().isEmpty() ) {
-                        w.writeStartArray(JSONConstants.INCLUDE_EXTENSION_REMOVALS);
+                        JsonArrayBuilder extRemovals = Json.createArrayBuilder();
                         for(final String id : inc.getExtensionRemovals()) {
-                            w.write(id);
+                            extRemovals.add(id);
                         }
                         for(final Map.Entry<String, List<ArtifactId>> entry : inc.getArtifactExtensionRemovals().entrySet()) {
-                            w.writeStartObject(entry.getKey());
-                            w.writeStartArray();
+                            JsonArrayBuilder ab = Json.createArrayBuilder();
                             for(final ArtifactId id : entry.getValue()) {
-                                w.write(id.toMvnId());
+                                ab.add(id.toMvnId());
                             }
-                            w.writeEnd();
-                            w.writeEnd();
+                            extRemovals.add(Json.createObjectBuilder().add(entry.getKey(),
+                                    ab.build()).build());
                         }
-                        w.writeEnd();
+                        removalsObj.add(JSONConstants.INCLUDE_EXTENSION_REMOVALS, extRemovals.build());
                     }
                     if ( !inc.getConfigurationRemovals().isEmpty() ) {
-                        w.writeStartArray(JSONConstants.FEATURE_CONFIGURATIONS);
+                        JsonArrayBuilder cfgRemovals = Json.createArrayBuilder();
                         for(final String val : inc.getConfigurationRemovals()) {
-                            w.write(val);
+                            cfgRemovals.add(val);
                         }
-                        w.writeEnd();
+                        removalsObj.add(JSONConstants.FEATURE_CONFIGURATIONS, cfgRemovals.build());
                     }
                     if ( !inc.getBundleRemovals().isEmpty() ) {
-                        w.writeStartArray(JSONConstants.FEATURE_BUNDLES);
+                        JsonArrayBuilder bundleRemovals = Json.createArrayBuilder();
                         for(final ArtifactId val : inc.getBundleRemovals()) {
-                            w.write(val.toMvnId());
+                            bundleRemovals.add(val.toMvnId());
                         }
-                        w.writeEnd();
+                        removalsObj.add(JSONConstants.FEATURE_BUNDLES, bundleRemovals.build());
                     }
                     if ( !inc.getFrameworkPropertiesRemovals().isEmpty() ) {
-                        w.writeStartArray(JSONConstants.FEATURE_FRAMEWORK_PROPERTIES);
+                        JsonArrayBuilder propRemovals = Json.createArrayBuilder();
                         for(final String val : inc.getFrameworkPropertiesRemovals()) {
-                            w.write(val);
+                            propRemovals.add(val);
                         }
-                        w.writeEnd();
+                        removalsObj.add(JSONConstants.FEATURE_FRAMEWORK_PROPERTIES, propRemovals.build());
                     }
-                    w.writeEnd();
-                    w.writeEnd();
+                    includeObj.add(JSONConstants.INCLUDE_REMOVALS, removalsObj.build());
+
+                    incArray.add(includeObj.build());
                 }
             }
-            w.writeEnd();
+            ob.add(JSONConstants.FEATURE_INCLUDES, incArray.build());
         }
 
         // requirements
         if ( !feature.getRequirements().isEmpty() ) {
-            w.writeStartArray(JSONConstants.FEATURE_REQUIREMENTS);
+            JsonArrayBuilder requirements = Json.createArrayBuilder();
 
             for(final Requirement req : feature.getRequirements()) {
-                w.writeStartObject();
-                w.write(JSONConstants.REQCAP_NAMESPACE, req.getNamespace());
+                JsonObjectBuilder requirementObj = Json.createObjectBuilder();
+                requirementObj.add(JSONConstants.REQCAP_NAMESPACE, req.getNamespace());
                 if ( !req.getAttributes().isEmpty() ) {
-                    w.writeStartObject(JSONConstants.REQCAP_ATTRIBUTES);
-                    req.getAttributes().forEach((key, value) -> marshalAttribute(key, value, w::write));
-                    w.writeEnd();
+                    JsonObjectBuilder attrObj = Json.createObjectBuilder();
+                    req.getAttributes().forEach((key, value) -> marshalAttribute(key, value, attrObj::add));
+                    requirementObj.add(JSONConstants.REQCAP_ATTRIBUTES, attrObj.build());
                 }
                 if ( !req.getDirectives().isEmpty() ) {
-                    w.writeStartObject(JSONConstants.REQCAP_DIRECTIVES);
-                    req.getDirectives().forEach((key, value) -> marshalDirective(key, value, w::write));
-                    w.writeEnd();
+                    JsonObjectBuilder reqObj = Json.createObjectBuilder();
+                    req.getDirectives().forEach((key, value) -> marshalDirective(key, value, reqObj::add));
+                    requirementObj.add(JSONConstants.REQCAP_DIRECTIVES, reqObj.build());
                 }
-                w.writeEnd();
+                requirements.add(requirementObj.build());
             }
-            w.writeEnd();
+            ob.add(JSONConstants.FEATURE_REQUIREMENTS, requirements.build());
         }
 
         // capabilities
         if ( !feature.getCapabilities().isEmpty() ) {
-            w.writeStartArray(JSONConstants.FEATURE_CAPABILITIES);
+            JsonArrayBuilder capabilities = Json.createArrayBuilder();
 
             for(final Capability cap : feature.getCapabilities()) {
-                w.writeStartObject();
-                w.write(JSONConstants.REQCAP_NAMESPACE, cap.getNamespace());
+                JsonObjectBuilder capabilityObj = Json.createObjectBuilder();
+                capabilityObj.add(JSONConstants.REQCAP_NAMESPACE, cap.getNamespace());
                 if ( !cap.getAttributes().isEmpty() ) {
-                    w.writeStartObject(JSONConstants.REQCAP_ATTRIBUTES);
-                    cap.getAttributes().forEach((key, value) -> marshalAttribute(key, value, w::write));
-                    w.writeEnd();
+                    JsonObjectBuilder attrObj = Json.createObjectBuilder();
+                    cap.getAttributes().forEach((key, value) -> marshalAttribute(key, value, attrObj::add));
+                    capabilityObj.add(JSONConstants.REQCAP_ATTRIBUTES, attrObj.build());
                 }
                 if ( !cap.getDirectives().isEmpty() ) {
-                    w.writeStartObject(JSONConstants.REQCAP_DIRECTIVES);
-                    cap.getDirectives().forEach((key, value) -> marshalDirective(key, value, w::write));
-                    w.writeEnd();
+                    JsonObjectBuilder reqObj = Json.createObjectBuilder();
+                    cap.getDirectives().forEach((key, value) -> marshalDirective(key, value, reqObj::add));
+                    capabilityObj.add(JSONConstants.REQCAP_DIRECTIVES, reqObj.build());
                 }
-                w.writeEnd();
+                capabilities.add(capabilityObj.build());
             }
-            w.writeEnd();
+            ob.add(JSONConstants.FEATURE_CAPABILITIES, capabilities.build());
         }
 
         // bundles
-        writeBundles(w, feature.getBundles(), feature.getConfigurations());
+        writeBundles(ob, feature.getBundles(), feature.getConfigurations());
 
         // configurations
         final Configurations cfgs = new Configurations();
@@ -186,15 +189,18 @@ public class FeatureJSONWriter extends JSONWriterBase {
                 cfgs.add(cfg);
             }
         }
-        writeConfigurations(w, cfgs);
+        writeConfigurations(ob, cfgs);
 
         // framework properties
-        writeFrameworkProperties(w, feature.getFrameworkProperties());
+        writeFrameworkProperties(ob, feature.getFrameworkProperties());
 
         // extensions
-        writeExtensions(w, feature.getExtensions(), feature.getConfigurations());
+        writeExtensions(ob, feature.getExtensions(), feature.getConfigurations());
 
-        w.writeEnd();
-        w.flush();
+        JsonWriterFactory writerFactory = Json.createWriterFactory(
+                Collections.singletonMap(JsonGenerator.PRETTY_PRINTING, true));
+        JsonWriter jw = writerFactory.createWriter(writer);
+        jw.writeObject(ob.build());
+        jw.close();
     }
 }
diff --git a/featuremodel/feature-support/src/main/java/org/apache/sling/feature/support/json/JSONWriterBase.java b/featuremodel/feature-support/src/main/java/org/apache/sling/feature/support/json/JSONWriterBase.java
index ad6f1e0..f9a3aef 100644
--- a/featuremodel/feature-support/src/main/java/org/apache/sling/feature/support/json/JSONWriterBase.java
+++ b/featuremodel/feature-support/src/main/java/org/apache/sling/feature/support/json/JSONWriterBase.java
@@ -16,6 +16,14 @@
  */
 package org.apache.sling.feature.support.json;
 
+import org.apache.sling.feature.Artifact;
+import org.apache.sling.feature.Bundles;
+import org.apache.sling.feature.Configuration;
+import org.apache.sling.feature.Configurations;
+import org.apache.sling.feature.Extension;
+import org.apache.sling.feature.ExtensionType;
+import org.apache.sling.feature.KeyValueMap;
+
 import java.io.StringReader;
 import java.lang.reflect.Array;
 import java.util.Enumeration;
@@ -23,30 +31,22 @@ import java.util.List;
 import java.util.Map;
 
 import javax.json.Json;
+import javax.json.JsonArrayBuilder;
+import javax.json.JsonObject;
+import javax.json.JsonObjectBuilder;
 import javax.json.JsonStructure;
-import javax.json.stream.JsonGenerator;
-
-import org.apache.sling.feature.Artifact;
-import org.apache.sling.feature.Bundles;
-import org.apache.sling.feature.Configuration;
-import org.apache.sling.feature.Configurations;
-import org.apache.sling.feature.Extension;
-import org.apache.sling.feature.ExtensionType;
-import org.apache.sling.feature.KeyValueMap;
 
 
 /**
  * Common functionality for writing JSON
  */
 abstract class JSONWriterBase {
-
-
-    protected void writeBundles(final JsonGenerator w,
+    protected void writeBundles(final JsonObjectBuilder ob,
             final Bundles bundles,
             final Configurations allConfigs) {
         // bundles
         if ( !bundles.isEmpty() ) {
-            w.writeStartArray(JSONConstants.FEATURE_BUNDLES);
+            JsonArrayBuilder bundleArray = Json.createArrayBuilder();
 
             for(final Artifact artifact : bundles) {
                 final Configurations cfgs = new Configurations();
@@ -56,37 +56,45 @@ abstract class JSONWriterBase {
                         cfgs.add(cfg);
                     }
                 }
-                if ( artifact.getMetadata().isEmpty() && cfgs.isEmpty() ) {
-                    w.write(artifact.getId().toMvnId());
+                KeyValueMap md = artifact.getMetadata();
+                if ( md.isEmpty() && cfgs.isEmpty() ) {
+                    bundleArray.add(artifact.getId().toMvnId());
                 } else {
-                    w.writeStartObject();
-                    w.write(JSONConstants.ARTIFACT_ID, artifact.getId().toMvnId());
+                    JsonObjectBuilder bundleObj = Json.createObjectBuilder();
+                    bundleObj.add(JSONConstants.ARTIFACT_ID, artifact.getId().toMvnId());
+
+                    if (md.get("start-level") == null) {
+                        String so = md.get("start-order");
+                        if (so != null) {
+                            md.put("start-level", so);
+                        }
+                    }
+
+                    Object runmodes = md.remove("runmodes");
+                    if (runmodes instanceof String) {
+                        md.put("run-modes", runmodes);
+                    }
 
-                    for(final Map.Entry<String, String> me : artifact.getMetadata()) {
-                        w.write(me.getKey(), me.getValue());
+                    for(final Map.Entry<String, String> me : md) {
+                        bundleObj.add(me.getKey(), me.getValue());
                     }
 
-                    writeConfigurations(w, cfgs);
-                    w.writeEnd();
+                    bundleArray.add(bundleObj.build());
                 }
             }
-
-            w.writeEnd();
+            ob.add(JSONConstants.FEATURE_BUNDLES, bundleArray.build());
         }
     }
 
     /**
      * Write the list of configurations into a "configurations" element
-     * @param w The json generator
+     * @param ob The json generator
      * @param cfgs The list of configurations
      */
-    protected void writeConfigurations(final JsonGenerator w, final Configurations cfgs) {
+    protected void writeConfigurations(final JsonObjectBuilder ob, final Configurations cfgs) {
         if ( !cfgs.isEmpty() ) {
-            w.writeStartObject(JSONConstants.FEATURE_CONFIGURATIONS);
-
-            writeConfigurationsMap(w, cfgs);
-
-            w.writeEnd();
+            ob.add(JSONConstants.FEATURE_CONFIGURATIONS,
+                    writeConfigurationsMap(cfgs));
         }
     }
 
@@ -94,8 +102,10 @@ abstract class JSONWriterBase {
      * Write the list of configurations into a "configurations" element
      * @param w The json generator
      * @param cfgs The list of configurations
+     * @return
      */
-    protected void writeConfigurationsMap(final JsonGenerator w, final Configurations cfgs) {
+    protected JsonObject writeConfigurationsMap(final Configurations cfgs) {
+        JsonObjectBuilder configObj = Json.createObjectBuilder();
         for(final Configuration cfg : cfgs) {
             final String key;
             if ( cfg.isFactoryConfiguration() ) {
@@ -103,7 +113,7 @@ abstract class JSONWriterBase {
             } else {
                 key = cfg.getPid();
             }
-            w.writeStartObject(key);
+            JsonObjectBuilder cfgValObj = Json.createObjectBuilder();
 
             final Enumeration<String> e = cfg.getProperties().keys();
             while ( e.hasMoreElements() ) {
@@ -137,58 +147,57 @@ abstract class JSONWriterBase {
                 }
 
                 if ( val.getClass().isArray() ) {
-                    w.writeStartArray(name);
+                    JsonArrayBuilder ab = Json.createArrayBuilder();
                     for(int i=0; i<Array.getLength(val);i++ ) {
                         final Object obj = Array.get(val, i);
                         if ( typePostFix == null ) {
                             if ( obj instanceof String ) {
-                                w.write((String)obj);
+                                ab.add((String)obj);
                             } else if ( obj instanceof Boolean ) {
-                                w.write((Boolean)obj);
+                                ab.add((Boolean)obj);
                             } else if ( obj instanceof Long ) {
-                                w.write((Long)obj);
+                                ab.add((Long)obj);
                             } else if ( obj instanceof Double ) {
-                                w.write((Double)obj);
+                                ab.add((Double)obj);
                             }
                         } else {
-                            w.write(obj.toString());
+                            ab.add(obj.toString());
                         }
                     }
-                    w.writeEnd();
+                    cfgValObj.add(name, ab.build());
                 } else {
                     if ( typePostFix == null ) {
                         if ( val instanceof String ) {
-                            w.write(name, (String)val);
+                            cfgValObj.add(name, (String)val);
                         } else if ( val instanceof Boolean ) {
-                            w.write(name, (Boolean)val);
+                            cfgValObj.add(name, (Boolean)val);
                         } else if ( val instanceof Long ) {
-                            w.write(name, (Long)val);
+                            cfgValObj.add(name, (Long)val);
                         } else if ( val instanceof Double ) {
-                            w.write(name, (Double)val);
+                            cfgValObj.add(name, (Double)val);
                         }
                     } else {
-                        w.write(name + typePostFix, val.toString());
+                        cfgValObj.add(name + typePostFix, val.toString());
                     }
                 }
             }
-
-            w.writeEnd();
+            configObj.add(key, cfgValObj.build());
         }
+        return configObj.build();
     }
 
-    protected void writeFrameworkProperties(final JsonGenerator w, final KeyValueMap props) {
+    protected void writeFrameworkProperties(final JsonObjectBuilder ob, final KeyValueMap props) {
         // framework properties
         if ( !props.isEmpty() ) {
-            w.writeStartObject(JSONConstants.FEATURE_FRAMEWORK_PROPERTIES);
-
+            JsonObjectBuilder propsObj = Json.createObjectBuilder();
             for(final Map.Entry<String, String> entry : props) {
-                w.write(entry.getKey(), entry.getValue());
+                propsObj.add(entry.getKey(), entry.getValue());
             }
-            w.writeEnd();
+            ob.add(JSONConstants.FEATURE_FRAMEWORK_PROPERTIES, propsObj.build());
         }
     }
 
-    protected void writeExtensions(final JsonGenerator w,
+    protected void writeExtensions(final JsonObjectBuilder ob,
             final List<Extension> extensions,
             final Configurations allConfigs) {
         for(final Extension ext : extensions) {
@@ -198,11 +207,11 @@ abstract class JSONWriterBase {
                 try ( final StringReader reader = new StringReader(ext.getJSON()) ) {
                     struct = Json.createReader(reader).read();
                 }
-                w.write(key, struct);
+                ob.add(key, struct);
             } else if ( ext.getType() == ExtensionType.TEXT ) {
-                w.write(key, ext.getText());
+                ob.add(key, ext.getText());
             } else {
-                w.writeStartArray(key);
+                JsonArrayBuilder extensionArr = Json.createArrayBuilder();
                 for(final Artifact artifact : ext.getArtifacts()) {
                     final Configurations artifactCfgs = new Configurations();
                     for(final Configuration cfg : allConfigs) {
@@ -212,20 +221,20 @@ abstract class JSONWriterBase {
                         }
                     }
                     if ( artifact.getMetadata().isEmpty() && artifactCfgs.isEmpty() ) {
-                        w.write(artifact.getId().toMvnId());
+                        extensionArr.add(artifact.getId().toMvnId());
                     } else {
-                        w.writeStartObject();
-                        w.write(JSONConstants.ARTIFACT_ID, artifact.getId().toMvnId());
+                        JsonObjectBuilder extObj = Json.createObjectBuilder();
+                        extObj.add(JSONConstants.ARTIFACT_ID, artifact.getId().toMvnId());
 
                         for(final Map.Entry<String, String> me : artifact.getMetadata()) {
-                            w.write(me.getKey(), me.getValue());
+                            extObj.add(me.getKey(), me.getValue());
                         }
 
-                        writeConfigurations(w, artifactCfgs);
-                        w.writeEnd();
+                        writeConfigurations(ob, artifactCfgs);
+                        extensionArr.add(extObj.build());
                     }
                 }
-                w.writeEnd();
+                ob.add(key, extensionArr.build());
             }
         }
     }
diff --git a/featuremodel/feature-support/src/test/java/org/apache/sling/feature/support/json/FeatureJSONWriterTest.java b/featuremodel/feature-support/src/test/java/org/apache/sling/feature/support/json/FeatureJSONWriterTest.java
index 6ba5bd4..d1b4c72 100644
--- a/featuremodel/feature-support/src/test/java/org/apache/sling/feature/support/json/FeatureJSONWriterTest.java
+++ b/featuremodel/feature-support/src/test/java/org/apache/sling/feature/support/json/FeatureJSONWriterTest.java
@@ -28,17 +28,20 @@ import static org.junit.Assert.assertEquals;
 public class FeatureJSONWriterTest {
 
     @Test public void testRead() throws Exception {
-        final Feature feature = U.readFeature("test");
-        final Feature readFeature;
+        final Feature f = U.readFeature("test");
+        final Feature rf;
         try ( final StringWriter writer = new StringWriter() ) {
-            FeatureJSONWriter.write(writer, feature);
+            FeatureJSONWriter.write(writer, f);
             try ( final StringReader reader = new StringReader(writer.toString()) ) {
-                readFeature = FeatureJSONReader.read(reader, null);
+                rf = FeatureJSONReader.read(reader, null);
             }
         }
-        assertEquals(feature.getId(), readFeature.getId());
+        assertEquals(f.getId(), rf.getId());
+        assertEquals("org.apache.sling:test-feature:1.1", rf.getId().toMvnId());
+        assertEquals("The feature description", rf.getDescription());
+
         assertEquals(Arrays.asList("org.osgi.service.http.runtime.HttpServiceRuntime"),
-                U.findCapability(readFeature.getCapabilities(), "osgi.service").getAttributes().get("objectClass"));
+                U.findCapability(rf.getCapabilities(), "osgi.service").getAttributes().get("objectClass"));
     }
 
 }
diff --git a/featuremodel/feature-support/src/test/resources/features/test.json b/featuremodel/feature-support/src/test/resources/features/test.json
index 451b824..8ae346c 100644
--- a/featuremodel/feature-support/src/test/resources/features/test.json
+++ b/featuremodel/feature-support/src/test/resources/features/test.json
@@ -1,5 +1,6 @@
 {
     "id" : "org.apache.sling/test-feature/1.1",
+    "description": "The feature description",
 
     "includes" : [
          {

-- 
To stop receiving notification emails like this one, please contact
davidb@apache.org.

[sling-whiteboard] 04/04: Update the comparison method for generated Application JSON

Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 977d6bcbb2c7f11f2939c79458332ef96e2d4985
Author: David Bosschaert <da...@gmail.com>
AuthorDate: Mon Mar 12 17:56:18 2018 +0000

    Update the comparison method for generated Application JSON
---
 featuremodel/feature-applicationbuilder/pom.xml    | 24 +++++-----
 .../feature/applicationbuilder/impl/Main.java      | 14 +++---
 .../impl/ApplicationBuilderTest.java               | 55 +++++++++++++++++-----
 3 files changed, 62 insertions(+), 31 deletions(-)

diff --git a/featuremodel/feature-applicationbuilder/pom.xml b/featuremodel/feature-applicationbuilder/pom.xml
index 917e103..9f470e2 100644
--- a/featuremodel/feature-applicationbuilder/pom.xml
+++ b/featuremodel/feature-applicationbuilder/pom.xml
@@ -104,6 +104,12 @@
         </dependency>
         <dependency>
             <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.feature.analyser</artifactId>
+            <version>0.0.1-SNAPSHOT</version>
+            <scope>provided</scope>
+        </dependency>        
+        <dependency>
+            <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.feature.resolver</artifactId>
             <version>0.0.1-SNAPSHOT</version>
             <scope>provided</scope>
@@ -133,6 +139,12 @@
             <scope>provided</scope>
         </dependency>
         <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.framework</artifactId>
+            <version>5.6.10</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.commons.osgi</artifactId>
             <version>2.4.0</version>
@@ -151,17 +163,5 @@
             <artifactId>junit</artifactId>
             <scope>test</scope>
         </dependency>
-        <dependency>
-            <groupId>org.apache.felix</groupId>
-            <artifactId>org.apache.felix.framework</artifactId>
-            <version>5.6.10</version>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.sling</groupId>
-            <artifactId>org.apache.sling.feature.analyser</artifactId>
-            <version>0.0.1-SNAPSHOT</version>
-            <scope>test</scope>
-        </dependency>        
     </dependencies>
 </project>
diff --git a/featuremodel/feature-applicationbuilder/src/main/java/org/apache/sling/feature/applicationbuilder/impl/Main.java b/featuremodel/feature-applicationbuilder/src/main/java/org/apache/sling/feature/applicationbuilder/impl/Main.java
index db733cb..3235e81 100644
--- a/featuremodel/feature-applicationbuilder/src/main/java/org/apache/sling/feature/applicationbuilder/impl/Main.java
+++ b/featuremodel/feature-applicationbuilder/src/main/java/org/apache/sling/feature/applicationbuilder/impl/Main.java
@@ -16,12 +16,6 @@
  */
 package org.apache.sling.feature.applicationbuilder.impl;
 
-import java.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.util.Collections;
-import java.util.stream.Stream;
-
 import org.apache.commons.cli.CommandLine;
 import org.apache.commons.cli.CommandLineParser;
 import org.apache.commons.cli.DefaultParser;
@@ -39,6 +33,12 @@ import org.apache.sling.feature.support.json.ApplicationJSONWriter;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.stream.Stream;
+
 public class Main {
 
     private static Logger LOGGER;
@@ -190,7 +190,7 @@ public class Main {
     }
 
     private static void writeApplication(final Application app, final String out) {
-        LOGGER.info("Writing application...");
+        LOGGER.info("Writing application: " + out);
         final File file = new File(out);
         try ( final FileWriter writer = new FileWriter(file)) {
             ApplicationJSONWriter.write(writer, app);
diff --git a/featuremodel/feature-applicationbuilder/src/test/java/org/apache/sling/feature/applicationbuilder/impl/ApplicationBuilderTest.java b/featuremodel/feature-applicationbuilder/src/test/java/org/apache/sling/feature/applicationbuilder/impl/ApplicationBuilderTest.java
index 414d426..0c88957 100644
--- a/featuremodel/feature-applicationbuilder/src/test/java/org/apache/sling/feature/applicationbuilder/impl/ApplicationBuilderTest.java
+++ b/featuremodel/feature-applicationbuilder/src/test/java/org/apache/sling/feature/applicationbuilder/impl/ApplicationBuilderTest.java
@@ -36,6 +36,7 @@ import org.osgi.framework.Constants;
 
 import java.io.File;
 import java.io.FileReader;
+import java.io.StringReader;
 import java.io.StringWriter;
 import java.io.Writer;
 import java.net.URL;
@@ -45,6 +46,12 @@ import java.util.Collections;
 import java.util.Comparator;
 import java.util.Map;
 
+import javax.json.Json;
+import javax.json.JsonStructure;
+import javax.json.JsonWriter;
+import javax.json.JsonWriterFactory;
+import javax.json.stream.JsonGenerator;
+
 import static org.junit.Assert.assertEquals;
 
 public class ApplicationBuilderTest {
@@ -80,16 +87,21 @@ public class ApplicationBuilderTest {
 
         try (FeatureResolver fr = new FrameworkResolver(am, getFrameworkProps())) {
             Application app = ApplicationBuilder.assemble(null, bc, fr, features);
-            String genApp = writeApplication(app);
+            String actualJSON = writeApplication(app);
 
-            String expected = "{\"features\":["
+            String expectedJSON = "{\"features\":["
                     + "\"org.apache.sling.test.features:featureB:1.0.0\","
                     + "\"org.apache.sling.test.features:featureA:1.0.0\"],"
                 + "\"bundles\":["
-                    + "{\"id\":\"commons-io:commons-io:2.6\",\"start-order\":\"10\"},"
-                    + "{\"id\":\"org.apache.felix:org.apache.felix.http.servlet-api:1.1.2\",\"start-order\":\"15\"},"
-                    + "{\"id\":\"commons-fileupload:commons-fileupload:1.3.3\",\"start-order\":\"16\"}]}";
-            assertEquals(expected, genApp);
+                    + "{\"id\":\"commons-io:commons-io:2.6\",\"start-level\":\"10\",\"start-order\":\"10\"},"
+                    + "{\"id\":\"org.apache.felix:org.apache.felix.http.servlet-api:1.1.2\",\"start-level\":\"15\",\"start-order\":\"15\"},"
+                    + "{\"id\":\"commons-fileupload:commons-fileupload:1.3.3\",\"start-level\":\"16\",\"start-order\":\"16\"}]}";
+
+            StringWriter expectedWriter = new StringWriter();
+            StringWriter actualWriter = new StringWriter();
+
+            canonicalize(expectedJSON, expectedWriter, actualJSON, actualWriter);
+            assertEquals(expectedWriter.toString(), actualWriter.toString());
         }
     }
 
@@ -112,15 +124,28 @@ public class ApplicationBuilderTest {
             String expected = "{\"features\":["
                     + "\"org.apache.sling.test.features:featureC:1.0.0\","
                     + "\"org.apache.sling.test.features:featureD:1.0.0\"],"
-                    + "\"bundles\":[{\"id\":\"org.slf4j:slf4j-api:1.7.25\",\"start-order\":\"6\"}]}";
-            assertEquals(expected, genApp);
+                    + "\"bundles\":[{\"id\":\"org.slf4j:slf4j-api:1.7.25\",\"start-level\":\"6\",\"start-order\":\"6\"}]}";
+
+            StringWriter expectedWriter = new StringWriter();
+            StringWriter actualWriter = new StringWriter();
+            canonicalize(expected, expectedWriter, genApp, actualWriter);
+
+            assertEquals(expectedWriter.toString(), actualWriter.toString());
         }
     }
 
-    private static String writeApplication(Application app) throws Exception {
-        Writer writer = new StringWriter();
-        ApplicationJSONWriter.write(writer, app);
-        return writer.toString();
+    // Turn JSON into pretty-formatted canoncical JSON that should be comparable using String compare
+    private void canonicalize(String expected, StringWriter expectedWriter, String actual, StringWriter actualWriter) {
+        JsonStructure es = Json.createReader(new StringReader(expected)).read();
+        JsonStructure ea = Json.createReader(new StringReader(actual)).read();
+
+        JsonWriterFactory writerFactory = Json.createWriterFactory(
+                Collections.singletonMap(JsonGenerator.PRETTY_PRINTING, true));
+        JsonWriter w = writerFactory.createWriter(expectedWriter);
+        w.write(es);
+
+        JsonWriter w2 = writerFactory.createWriter(actualWriter);
+        w2.write(ea);
     }
 
     private Feature readFeature(final String res,
@@ -135,6 +160,12 @@ public class ApplicationBuilderTest {
         }
     }
 
+    private static String writeApplication(Application app) throws Exception {
+        Writer writer = new StringWriter();
+        ApplicationJSONWriter.write(writer, app);
+        return writer.toString();
+    }
+
     private static class TestFeatureProvider implements FeatureProvider {
         @Override
         public Feature provide(ArtifactId id) {

-- 
To stop receiving notification emails like this one, please contact
davidb@apache.org.