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:19 UTC

[sling-org-apache-sling-feature-modelconverter] 01/40: Move feature model to whiteboard git

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 8ca0ccbf5a2efac4a09df847d86c3d4e4c55317a
Author: Carsten Ziegeler <cz...@adobe.com>
AuthorDate: Fri Nov 3 15:06:50 2017 +0100

    Move feature model to whiteboard git
---
 pom.xml                                            | 127 ++++
 .../sling/feature/modelconverter/impl/Main.java    | 729 +++++++++++++++++++++
 2 files changed, 856 insertions(+)

diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..64dbec0
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,127 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+    <!--
+        Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+        agreements. See the NOTICE file distributed with this work for additional information
+        regarding copyright ownership. The ASF licenses this file to you under the Apache License,
+        Version 2.0 (the "License"); you may not use this file except in compliance with the
+        License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+
+        Unless required by applicable law or agreed to in writing, software distributed under the
+        License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+        either express or implied. See the License for the specific language governing permissions
+        and limitations under the License.
+    -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.sling</groupId>
+        <artifactId>sling</artifactId>
+        <version>32</version>
+        <relativePath />
+    </parent>
+
+    <artifactId>org.apache.sling.feature.modelconverter</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+
+    <name>Apache Sling Feature Model Converter</name>
+    <description>
+        A feature describes an OSGi system
+    </description>
+
+    <properties>
+        <sling.java.version>8</sling.java.version>
+    </properties>
+
+    <scm>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/tooling/support/feature-modelconverter</connection>
+        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/trunk/tooling/support/feature-modelconverter</developerConnection>
+        <url>http://svn.apache.org/viewvc/sling/trunk/tooling/support/feature-modelconverter</url>
+    </scm>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-dependency-plugin</artifactId>
+                <executions>
+                <execution>
+                    <id>unpack-dependencies</id>
+                    <phase>prepare-package</phase>
+                    <goals>
+                        <goal>unpack-dependencies</goal>
+                    </goals>
+                    <configuration>
+                        <excludes>META-INF/**</excludes>
+                        <outputDirectory>${project.build.directory}/classes</outputDirectory>
+                        <overWriteReleases>false</overWriteReleases>
+                        <overWriteSnapshots>true</overWriteSnapshots>
+                        <includeArtifactIds>commons-cli,org.apache.sling.feature,org.apache.sling.feature.support,org.apache.sling.commons.johnzon,org.apache.sling.provisioning.model,slf4j-api,slf4j-simple,osgi.core</includeArtifactIds>
+                    </configuration>
+                </execution>
+            </executions>
+            </plugin>
+                <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
+                <configuration>
+                    <archive>
+                        <manifest>
+                            <mainClass>org.apache.sling.feature.modelconverter.impl.Main</mainClass>
+                        </manifest>
+                    </archive>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+    
+    <dependencies>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>osgi.core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+             <groupId>commons-cli</groupId>
+             <artifactId>commons-cli</artifactId>
+             <version>1.3.1</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.feature</artifactId>
+            <version>0.0.1-SNAPSHOT</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.feature.support</artifactId>
+            <version>0.0.1-SNAPSHOT</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.provisioning.model</artifactId>
+            <version>1.8.2</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.commons.johnzon</artifactId>
+            <version>1.0.0</version>
+            <scope>provided</scope>
+        </dependency>
+      <!-- Testing -->
+        <dependency>
+        	<groupId>junit</groupId>
+        	<artifactId>junit</artifactId>
+        </dependency>
+    </dependencies>
+</project>
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
new file mode 100644
index 0000000..f030367
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/modelconverter/impl/Main.java
@@ -0,0 +1,729 @@
+/*
+ * 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 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 java.util.stream.Collectors;
+
+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.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;
+
+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;
+    }
+
+    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");
+
+        LOGGER = LoggerFactory.getLogger("modelconverter");
+
+        LOGGER.info("Apache Sling Provisiong Model to Feature Application Converter");
+        LOGGER.info("");
+
+        parseArgs(args);
+
+        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);
+        } 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 {
+                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, 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);
+            }
+        }
+    }
+
+    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;
+            if ( feature.getName() != null ) {
+                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.fromMvnId(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
+        app.getBundles().add(1, new org.apache.sling.feature.Artifact(ArtifactId.fromMvnId("org.apache.sling/org.apache.sling.launchpad.api/1.2.0")));
+        // 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;
+                            }
+                            bundles.add(startLevel, 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 Map.Entry<Integer, org.apache.sling.feature.Artifact> bundle : app.getBundles()) {
+            final ArtifactId id = bundle.getValue().getId();
+            final Artifact newBundle = new Artifact(id.getGroupId(), id.getArtifactId(), id.getVersion(), id.getClassifier(), id.getType());
+            for(final Map.Entry<String, String> prop : bundle.getValue().getMetadata()) {
+                newBundle.getMetadata().put(prop.getKey(), prop.getValue());
+            }
+            f.getOrCreateRunMode(null).getOrCreateArtifactGroup(bundle.getKey()).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);
+        }
+    }
+}

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