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/04/27 10:01:25 UTC

[sling-org-apache-sling-feature-modelconverter] 07/40: Support the slinstart maven plugin by providing a model converter API

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-org-apache-sling-feature-modelconverter.git

commit b9c87b7e46912e24e555e8e2a68f5733c0bfebbb
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
---
 pom.xml                                            |  26 +-
 .../modelconverter/impl/FeatureToProvisioning.java | 216 ++++++++
 .../sling/feature/modelconverter/impl/Main.java    | 554 +-------------------
 .../impl/{Main.java => ProvisioningToFeature.java} | 562 ++++++---------------
 4 files changed, 393 insertions(+), 965 deletions(-)

diff --git a/pom.xml b/pom.xml
index d5359d5..2e0c607 100644
--- a/pom.xml
+++ b/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/src/main/java/org/apache/sling/feature/modelconverter/impl/FeatureToProvisioning.java b/src/main/java/org/apache/sling/feature/modelconverter/impl/FeatureToProvisioning.java
new file mode 100644
index 0000000..44eaaac
--- /dev/null
+++ b/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/src/main/java/org/apache/sling/feature/modelconverter/impl/Main.java b/src/main/java/org/apache/sling/feature/modelconverter/impl/Main.java
index ccc3f85..81a9e3c 100644
--- a/src/main/java/org/apache/sling/feature/modelconverter/impl/Main.java
+++ b/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/src/main/java/org/apache/sling/feature/modelconverter/impl/Main.java b/src/main/java/org/apache/sling/feature/modelconverter/impl/ProvisioningToFeature.java
similarity index 61%
copy from src/main/java/org/apache/sling/feature/modelconverter/impl/Main.java
copy to src/main/java/org/apache/sling/feature/modelconverter/impl/ProvisioningToFeature.java
index ccc3f85..e3a4a52 100644
--- a/src/main/java/org/apache/sling/feature/modelconverter/impl/Main.java
+++ b/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);

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