sling-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From dav...@apache.org
Subject [sling-whiteboard] 01/04: Support the slinstart maven plugin by providing a model converter API
Date Thu, 15 Mar 2018 07:44:15 GMT
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 <david.bosschaert@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.

Mime
View raw message