karaf-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From gno...@apache.org
Subject [2/2] karaf git commit: [KARAF-3546] Provide a programmatic way to build custom karaf distributions outside of maven
Date Fri, 27 Feb 2015 11:54:57 GMT
[KARAF-3546] Provide a programmatic way to build custom karaf distributions outside of maven


Project: http://git-wip-us.apache.org/repos/asf/karaf/repo
Commit: http://git-wip-us.apache.org/repos/asf/karaf/commit/e920dde0
Tree: http://git-wip-us.apache.org/repos/asf/karaf/tree/e920dde0
Diff: http://git-wip-us.apache.org/repos/asf/karaf/diff/e920dde0

Branch: refs/heads/master
Commit: e920dde01f7824e646e3d1b083141546a0502e5f
Parents: 3f6bca0
Author: Guillaume Nodet <gnodet@gmail.com>
Authored: Tue Feb 24 14:35:15 2015 +0100
Committer: Guillaume Nodet <gnodet@gmail.com>
Committed: Fri Feb 27 12:54:44 2015 +0100

----------------------------------------------------------------------
 demos/profiles/dynamic/pom.xml                  |   7 +-
 .../activemq/broker.profile/profile.cfg         |   2 +-
 .../main/resources/default.profile/profile.cfg  |   2 +
 .../org.ops4j.pax.logging.cfg#static            |  54 ++
 demos/profiles/static/pom.xml                   |   8 +-
 .../download/impl/MavenDownloadManager.java     |  14 +-
 main/pom.xml                                    |   2 +
 .../org/apache/karaf/main/ConfigProperties.java |   1 +
 .../main/java/org/apache/karaf/main/Main.java   |   1 +
 .../org/apache/karaf/main/PropertiesLoader.java | 207 ----
 .../karaf/main/util/SimpleMavenResolver.java    |  61 +-
 .../main/util/SimpleMavenResolverTest.java      |   6 +-
 pom.xml                                         |   5 +
 profile/pom.xml                                 |  61 +-
 .../java/org/apache/karaf/profile/Profile.java  |  12 +
 .../assembly/AssemblyDeployCallback.java        | 227 +++++
 .../apache/karaf/profile/assembly/Builder.java  | 969 +++++++++++++++++++
 .../profile/assembly/CustomDownloadManager.java |  43 +
 .../assembly/CustomSimpleDownloadTask.java      | 161 +++
 .../profile/assembly/FakeBundleRevision.java    | 157 +++
 .../apache/karaf/profile/impl/ProfileImpl.java  |  40 +-
 .../karaf/profile/assembly/BuilderTest.java     |  68 ++
 .../karaf/tooling/features/InstallKarsMojo.java | 683 ++-----------
 .../features/VerifyFeatureResolutionMojo.java   |  13 +-
 .../karaf/tooling/utils/PropertiesLoader.java   | 249 -----
 .../karaf/util/config/PropertiesLoader.java     | 249 +++++
 .../org/apache/karaf/util/maven/Parser.java     |  13 +
 27 files changed, 2154 insertions(+), 1161 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/karaf/blob/e920dde0/demos/profiles/dynamic/pom.xml
----------------------------------------------------------------------
diff --git a/demos/profiles/dynamic/pom.xml b/demos/profiles/dynamic/pom.xml
index 87990c7..6db6a5d 100644
--- a/demos/profiles/dynamic/pom.xml
+++ b/demos/profiles/dynamic/pom.xml
@@ -106,13 +106,8 @@
                     </execution>
                 </executions>
                 <configuration>
-                    <!--
-                    <profilesArtifact>
-                        org.apache.karaf.demo.profiles:org.apache.karaf.demo.profiles.registry
-                    </profilesArtifact>
-                    -->
                     <profilesUri>
-                        file://${basedir}/../registry/src/main/resources
+                        jar:mvn:org.apache.karaf.demos.profiles/registry/${project.version}!/
                     </profilesUri>
                     <bootFeatures>
                         <feature>deployer</feature>

http://git-wip-us.apache.org/repos/asf/karaf/blob/e920dde0/demos/profiles/registry/src/main/resources/activemq/broker.profile/profile.cfg
----------------------------------------------------------------------
diff --git a/demos/profiles/registry/src/main/resources/activemq/broker.profile/profile.cfg b/demos/profiles/registry/src/main/resources/activemq/broker.profile/profile.cfg
index 269c1f9..65a0806 100644
--- a/demos/profiles/registry/src/main/resources/activemq/broker.profile/profile.cfg
+++ b/demos/profiles/registry/src/main/resources/activemq/broker.profile/profile.cfg
@@ -18,4 +18,4 @@
 attribute.parents = karaf
 
 feature.aries-bluerint = aries-blueprint
-feature.activemq-broker = activemq-broker
+feature.activemq-broker = activemq-broker-noweb

http://git-wip-us.apache.org/repos/asf/karaf/blob/e920dde0/demos/profiles/registry/src/main/resources/default.profile/profile.cfg
----------------------------------------------------------------------
diff --git a/demos/profiles/registry/src/main/resources/default.profile/profile.cfg b/demos/profiles/registry/src/main/resources/default.profile/profile.cfg
index 08f0801..a4cc652 100644
--- a/demos/profiles/registry/src/main/resources/default.profile/profile.cfg
+++ b/demos/profiles/registry/src/main/resources/default.profile/profile.cfg
@@ -18,6 +18,8 @@
 #framework=mvn\:org.apache.felix/org.apache.felix.framework/${felix.framework.version}
 repository.karaf-standard=mvn\:org.apache.karaf.assemblies.features/standard/${profile:version/karaf}/xml/features
 
+library.jolokia-agent=mvn\:org.jolokia/jolokia-jvm/1.2.2/jar/agent
+
 org.ops4j.pax.url.mvn.repositories= \
     file:${runtime.home}/${karaf.default.repository}@snapshots@id=karaf-default, \
     http://repo1.maven.org/maven2@id=central, \

http://git-wip-us.apache.org/repos/asf/karaf/blob/e920dde0/demos/profiles/registry/src/main/resources/karaf.profile/org.ops4j.pax.logging.cfg#static
----------------------------------------------------------------------
diff --git a/demos/profiles/registry/src/main/resources/karaf.profile/org.ops4j.pax.logging.cfg#static b/demos/profiles/registry/src/main/resources/karaf.profile/org.ops4j.pax.logging.cfg#static
new file mode 100644
index 0000000..8b8184d
--- /dev/null
+++ b/demos/profiles/registry/src/main/resources/karaf.profile/org.ops4j.pax.logging.cfg#static
@@ -0,0 +1,54 @@
+#
+#    Licensed to the Apache Software Foundation (ASF) under one or more
+#    contributor license agreements.  See the NOTICE file distributed with
+#    this work for additional information regarding copyright ownership.
+#    The ASF licenses this file to You under the Apache License, Version 2.0
+#    (the "License"); you may not use this file except in compliance with
+#    the License.  You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS,
+#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#    See the License for the specific language governing permissions and
+#    limitations under the License.
+#
+
+# Root logger
+log4j.rootLogger=INFO, stdout, osgi:*
+log4j.throwableRenderer=org.apache.log4j.OsgiThrowableRenderer
+
+# CONSOLE appender not used by default
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} | %-5.5p | %-16.16t | %-32.32c{1} | %X{bundle.id} - %X{bundle.name} - %X{bundle.version} | %m%n
+# use this for source code lines enabled in the logs (beware it impacts performance)
+#log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} | %-5.5p | %-16.16t | %-32.32c{1} | %-32.32C %4L | %X{bundle.id} - %X{bundle.name} - %X{bundle.version} | %m%n
+
+# File appender
+log4j.appender.out=org.apache.log4j.RollingFileAppender
+log4j.appender.out.layout=org.apache.log4j.PatternLayout
+log4j.appender.out.layout.ConversionPattern=%d{ISO8601} | %-5.5p | %-16.16t | %-32.32c{1} | %X{bundle.id} - %X{bundle.name} - %X{bundle.version} | %m%n
+# use this for source code lines enabled in the logs (beware it impacts performance)
+#log4j.appender.out.layout.ConversionPattern=%d{ISO8601} | %-5.5p | %-16.16t | %-32.32c{1} | %-32.32C %4L | %X{bundle.id} - %X{bundle.name} - %X{bundle.version} | %m%n
+log4j.appender.out.file=${karaf.data}/log/karaf.log
+log4j.appender.out.append=true
+log4j.appender.out.maxFileSize=100MB
+log4j.appender.out.maxBackupIndex=10
+
+# Sift appender
+log4j.appender.sift=org.apache.log4j.sift.MDCSiftingAppender
+log4j.appender.sift.key=bundle.name
+log4j.appender.sift.default=karaf
+log4j.appender.sift.appender=org.apache.log4j.FileAppender
+log4j.appender.sift.appender.layout=org.apache.log4j.PatternLayout
+log4j.appender.sift.appender.layout.ConversionPattern=%d{ISO8601} | %-5.5p | %-16.16t | %-32.32c{1} | %m%n
+# use this for source code lines enabled in the logs (beware it impacts performance)
+#log4j.appender.sift.appender.layout.ConversionPattern=%d{ISO8601} | %-5.5p | %-16.16t | %-32.32c{1} | %-32.32C %4L | %m%n
+log4j.appender.sift.appender.file=${karaf.data}/log/$\\{bundle.name\\}.log
+log4j.appender.sift.appender.append=true
+
+# To avoid flooding the log when using DEBUG level on an ssh connection and doing log:tail
+log4j.logger.org.apache.sshd.server.channel.ChannelSession = INFO
+

http://git-wip-us.apache.org/repos/asf/karaf/blob/e920dde0/demos/profiles/static/pom.xml
----------------------------------------------------------------------
diff --git a/demos/profiles/static/pom.xml b/demos/profiles/static/pom.xml
index 4f12134..8db3ef1 100644
--- a/demos/profiles/static/pom.xml
+++ b/demos/profiles/static/pom.xml
@@ -121,13 +121,9 @@
                 </executions>
                 <configuration>
                     <useReferenceUrls>true</useReferenceUrls>
-                    <!--
-                    <profilesArtifact>
-                        org.apache.karaf.demo.profiles:org.apache.karaf.demo.profiles.registry
-                    </profilesArtifact>
-                    -->
+                    <environment>static</environment>
                     <profilesUri>
-                        file://${basedir}/../registry/src/main/resources
+                        jar:mvn:org.apache.karaf.demos.profiles/registry/${project.version}!/
                     </profilesUri>
                     <startupProfiles>
                         <profile>karaf</profile>

http://git-wip-us.apache.org/repos/asf/karaf/blob/e920dde0/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/MavenDownloadManager.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/MavenDownloadManager.java b/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/MavenDownloadManager.java
index 82f3eea..99dec02 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/MavenDownloadManager.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/MavenDownloadManager.java
@@ -33,11 +33,11 @@ import org.ops4j.pax.url.mvn.MavenResolver;
 
 public class MavenDownloadManager implements DownloadManager {
 
-    private final MavenResolver mavenResolver;
+    protected final MavenResolver mavenResolver;
 
-    private final ScheduledExecutorService executorService;
+    protected final ScheduledExecutorService executorService;
 
-    private File tmpPath;
+    protected File tmpPath;
 
     private final Map<String, AbstractDownloadTask> downloaded = new HashMap<>();
 
@@ -143,7 +143,7 @@ public class MavenDownloadManager implements DownloadManager {
             });
         }
 
-        private AbstractDownloadTask createDownloadTask(final String url) {
+        protected AbstractDownloadTask createDownloadTask(final String url) {
             final String mvnUrl = DownloadManagerHelper.stripUrl(url);
             if (mvnUrl.startsWith("mvn:")) {
                 if (!mvnUrl.equals(url)) {
@@ -152,7 +152,7 @@ public class MavenDownloadManager implements DownloadManager {
                     return new MavenDownloadTask(executorService, mavenResolver, mvnUrl);
                 }
             } else {
-                return new SimpleDownloadTask(executorService, url, tmpPath);
+                return createCustomDownloadTask(url);
             }
         }
 
@@ -205,4 +205,8 @@ public class MavenDownloadManager implements DownloadManager {
 
     }
 
+    protected AbstractDownloadTask createCustomDownloadTask(final String url) {
+        return new SimpleDownloadTask(executorService, url, tmpPath);
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/karaf/blob/e920dde0/main/pom.xml
----------------------------------------------------------------------
diff --git a/main/pom.xml b/main/pom.xml
index e3783b4..f47f1b3 100644
--- a/main/pom.xml
+++ b/main/pom.xml
@@ -94,6 +94,8 @@
                         <Private-Package>
                             org.apache.karaf.main*,
                             org.apache.felix.utils.properties;-split-package:=merge-first,
+                            org.apache.karaf.util.config,
+                            org.apache.karaf.util.maven,
                             org.apache.karaf.util.locks;-split-package:=merge-first,
                             META-INF;-split-package:=merge-first
                         </Private-Package>

http://git-wip-us.apache.org/repos/asf/karaf/blob/e920dde0/main/src/main/java/org/apache/karaf/main/ConfigProperties.java
----------------------------------------------------------------------
diff --git a/main/src/main/java/org/apache/karaf/main/ConfigProperties.java b/main/src/main/java/org/apache/karaf/main/ConfigProperties.java
index 32947d9..d2803af 100644
--- a/main/src/main/java/org/apache/karaf/main/ConfigProperties.java
+++ b/main/src/main/java/org/apache/karaf/main/ConfigProperties.java
@@ -29,6 +29,7 @@ import org.apache.felix.utils.properties.Properties;
 
 import org.apache.karaf.main.lock.SimpleFileLock;
 import org.apache.karaf.main.util.Utils;
+import org.apache.karaf.util.config.PropertiesLoader;
 import org.osgi.framework.Constants;
 
 public class ConfigProperties {

http://git-wip-us.apache.org/repos/asf/karaf/blob/e920dde0/main/src/main/java/org/apache/karaf/main/Main.java
----------------------------------------------------------------------
diff --git a/main/src/main/java/org/apache/karaf/main/Main.java b/main/src/main/java/org/apache/karaf/main/Main.java
index b35a055..169e31a 100644
--- a/main/src/main/java/org/apache/karaf/main/Main.java
+++ b/main/src/main/java/org/apache/karaf/main/Main.java
@@ -46,6 +46,7 @@ import org.apache.karaf.main.util.ArtifactResolver;
 import org.apache.karaf.main.util.BootstrapLogManager;
 import org.apache.karaf.main.util.SimpleMavenResolver;
 import org.apache.karaf.main.util.Utils;
+import org.apache.karaf.util.config.PropertiesLoader;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.BundleException;

http://git-wip-us.apache.org/repos/asf/karaf/blob/e920dde0/main/src/main/java/org/apache/karaf/main/PropertiesLoader.java
----------------------------------------------------------------------
diff --git a/main/src/main/java/org/apache/karaf/main/PropertiesLoader.java b/main/src/main/java/org/apache/karaf/main/PropertiesLoader.java
deleted file mode 100644
index 7fe2057..0000000
--- a/main/src/main/java/org/apache/karaf/main/PropertiesLoader.java
+++ /dev/null
@@ -1,207 +0,0 @@
-/*
- * 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.karaf.main;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.Enumeration;
-import org.apache.felix.utils.properties.Properties;
-import java.util.StringTokenizer;
-
-import org.apache.karaf.main.util.Utils;
-
-import static org.apache.felix.utils.properties.InterpolationHelper.substVars;
-
-public class PropertiesLoader {
-
-    private static final String INCLUDES_PROPERTY = "${includes}"; // mandatory includes
-
-    private static final String OPTIONALS_PROPERTY = "${optionals}"; // optionals include
-
-	private static final String OVERRIDE_PREFIX = "karaf.override."; // prefix that marks that system property should override defaults.
-
-    /**
-     * <p>
-     * Loads the configuration properties in the configuration property file
-     * associated with the framework installation; these properties
-     * are accessible to the framework and to bundles and are intended
-     * for configuration purposes. By default, the configuration property
-     * file is located in the <tt>conf/</tt> directory of the Felix
-     * installation directory and is called "<tt>config.properties</tt>".
-     * The installation directory of Felix is assumed to be the parent
-     * directory of the <tt>felix.jar</tt> file as found on the system class
-     * path property. The precise file from which to load configuration
-     * properties can be set by initializing the "<tt>felix.config.properties</tt>"
-     * system property to an arbitrary URL.
-     * </p>
-     *
-     * @return A <tt>Properties</tt> instance or <tt>null</tt> if there was an error.
-     * @throws Exception if something wrong occurs
-     */
-    static Properties loadConfigProperties(File file) throws Exception {
-        // See if the property URL was specified as a property.
-        URL configPropURL;
-        try {
-            configPropURL = file.toURI().toURL();
-        }
-        catch (MalformedURLException ex) {
-            System.err.print("Main: " + ex);
-            return null;
-        }
-
-        Properties configProps = loadPropertiesFile(configPropURL, false);
-        copySystemProperties(configProps);
-        configProps.substitute();
-
-        // Perform variable substitution for system properties.
-//        for (Enumeration<?> e = configProps.propertyNames(); e.hasMoreElements();) {
-//            String name = (String) e.nextElement();
-//            configProps.setProperty(name,
-//                    SubstHelper.substVars(configProps.getProperty(name), name, null, configProps));
-//        }
-
-        return configProps;
-    }
-
-    /**
-     * <p>
-     * Loads the properties in the system property file associated with the
-     * framework installation into <tt>System.setProperty()</tt>. These properties
-     * are not directly used by the framework in anyway. By default, the system
-     * property file is located in the <tt>conf/</tt> directory of the Felix
-     * installation directory and is called "<tt>system.properties</tt>". The
-     * installation directory of Felix is assumed to be the parent directory of
-     * the <tt>felix.jar</tt> file as found on the system class path property.
-     * The precise file from which to load system properties can be set by
-     * initializing the "<tt>felix.system.properties</tt>" system property to an
-     * arbitrary URL.
-     * </p>
-     *
-     * @param karafBase the karaf base folder
-     * @throws IOException
-     */
-    static void loadSystemProperties(File file) throws IOException {
-        Properties props = new Properties(false);
-        try {
-            InputStream is = new FileInputStream(file);
-            props.load(is);
-            is.close();
-        } catch (Exception e1) {
-            // Ignore
-        }
-
-        for (Enumeration<?> e = props.propertyNames(); e.hasMoreElements();) {
-            String name = (String) e.nextElement();
-			if (name.startsWith(OVERRIDE_PREFIX)) {
-				String overrideName = name.substring(OVERRIDE_PREFIX.length());
-				String value = props.getProperty(name);
-				System.setProperty(overrideName, substVars(value, name, null, props));
-			} else {
-				String value = System.getProperty(name, props.getProperty(name));
-				System.setProperty(name, substVars(value, name, null, props));
-			}
-        }
-    }
-
-    static void copySystemProperties(Properties configProps) {
-        for (Enumeration<?> e = System.getProperties().propertyNames();
-             e.hasMoreElements();) {
-            String key = (String) e.nextElement();
-            if (key.startsWith("felix.") ||
-                    key.startsWith("karaf.") ||
-                    key.startsWith("org.osgi.framework.")) {
-                configProps.setProperty(key, System.getProperty(key));
-            }
-        }
-    }
-
-    static Properties loadPropertiesOrFail(File configFile) {
-        try {
-            URL configPropURL = configFile.toURI().toURL();
-            return loadPropertiesFile(configPropURL, true);
-        } catch (Exception e) {
-            throw new RuntimeException("Error loading properties from " + configFile, e);
-        }
-    }
-
-    private static Properties loadPropertiesFile(URL configPropURL, boolean failIfNotFound) throws Exception {
-        Properties configProps = new Properties(null, false);
-        InputStream is = null;
-        try {
-            is = configPropURL.openConnection().getInputStream();
-            configProps.load(is);
-            is.close();
-        } catch (FileNotFoundException ex) {
-            if (failIfNotFound) {
-                throw ex;
-            } else {
-                System.err.println("WARN: " + configPropURL + " is not found, so not loaded");
-            }
-        } catch (Exception ex) {
-            System.err.println("Error loading config properties from " + configPropURL);
-            System.err.println("Main: " + ex);
-            return configProps;
-        } finally {
-            try {
-                if (is != null) {
-                    is.close();
-                }
-            }
-            catch (IOException ex2) {
-                // Nothing we can do.
-            }
-        }
-        loadIncludes(INCLUDES_PROPERTY, true, configPropURL, configProps);
-        loadIncludes(OPTIONALS_PROPERTY, false, configPropURL, configProps);
-        trimValues(configProps);
-        return configProps;
-    }
-
-    private static void loadIncludes(String propertyName, boolean mandatory, URL configPropURL, Properties configProps)
-            throws MalformedURLException, Exception {
-        String includes = (String) configProps.get(propertyName);
-        if (includes != null) {
-            StringTokenizer st = new StringTokenizer(includes, "\" ", true);
-            if (st.countTokens() > 0) {
-                String location;
-                do {
-                    location = Utils.nextLocation(st);
-                    if (location != null) {
-                        URL url = new URL(configPropURL, location);
-                        Properties props = loadPropertiesFile(url, mandatory);
-                        configProps.putAll(props);
-                    }
-                }
-                while (location != null);
-            }
-        }
-        configProps.remove(propertyName);
-    }
-
-    private static void trimValues(Properties configProps) {
-        for (String key : configProps.keySet()) {
-            configProps.put(key, configProps.get(key).trim());
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e920dde0/main/src/main/java/org/apache/karaf/main/util/SimpleMavenResolver.java
----------------------------------------------------------------------
diff --git a/main/src/main/java/org/apache/karaf/main/util/SimpleMavenResolver.java b/main/src/main/java/org/apache/karaf/main/util/SimpleMavenResolver.java
index 6ca10c1..b703c58 100644
--- a/main/src/main/java/org/apache/karaf/main/util/SimpleMavenResolver.java
+++ b/main/src/main/java/org/apache/karaf/main/util/SimpleMavenResolver.java
@@ -19,16 +19,16 @@
 package org.apache.karaf.main.util;
 
 import java.io.File;
+import java.net.MalformedURLException;
 import java.net.URI;
 import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
+
+import org.apache.karaf.util.maven.Parser;
 
 /**
  * Resolves local maven artifacts and raw file paths
  */
 public class SimpleMavenResolver implements ArtifactResolver {
-    private static final Pattern mvnPattern = Pattern.compile("mvn:([^/ ]+)/([^/ ]+)/([^/ ]*)(/([^/ ]+)(/([^/ ]+))?)?");
     private final List<File> mavenRepos;
 
     /**
@@ -60,54 +60,17 @@ public class SimpleMavenResolver implements ArtifactResolver {
     }
 
     private static File findFile(File dir, URI mvnUri) {
-        String path = fromMaven(mvnUri);
-        File theFile = new File(dir, path);
-
-        if (theFile.exists() && !theFile.isDirectory()) {
-            return theFile;
-        }
-        return null;
-    }
-
-    
+        try {
+            String path = Parser.pathFromMaven(mvnUri.toString());
+            File theFile = new File(dir, path);
 
-    /**
-     * Returns a path for an srtifact.
-     * Input: path (no ':') returns path
-     * Input:  converts to default repo location path
-     * type and classifier are optional.
-     *
-     *
-     * @param name input artifact info
-     * @return path as supplied or a default maven repo path
-     */
-    static String fromMaven(URI name) {
-        Matcher m = mvnPattern.matcher(name.toString());
-        if (!m.matches()) {
-            return name.toString();
-        }
-        StringBuilder path = new StringBuilder();
-        path.append(m.group(1).replace(".", "/"));
-        path.append("/");//groupId
-        String artifactId = m.group(2);
-        String version = m.group(3);
-        String extension = m.group(5);
-        String classifier = m.group(7);
-        path.append(artifactId).append("/");//artifactId
-        path.append(version).append("/");//version
-        path.append(artifactId).append("-").append(version);
-        if (present(classifier)) {
-            path.append("-").append(classifier);
-        }
-        if (present(extension)) {
-            path.append(".").append(extension);
-        } else {
-            path.append(".jar");
+            if (theFile.exists() && !theFile.isDirectory()) {
+                return theFile;
+            }
+            return null;
+        } catch (MalformedURLException e) {
+            return null;
         }
-        return path.toString();
     }
 
-    private static boolean present(String part) {
-        return part != null && !part.isEmpty();
-    }
 }

http://git-wip-us.apache.org/repos/asf/karaf/blob/e920dde0/main/src/test/java/org/apache/karaf/main/util/SimpleMavenResolverTest.java
----------------------------------------------------------------------
diff --git a/main/src/test/java/org/apache/karaf/main/util/SimpleMavenResolverTest.java b/main/src/test/java/org/apache/karaf/main/util/SimpleMavenResolverTest.java
index b914e33..541f0d4 100644
--- a/main/src/test/java/org/apache/karaf/main/util/SimpleMavenResolverTest.java
+++ b/main/src/test/java/org/apache/karaf/main/util/SimpleMavenResolverTest.java
@@ -19,20 +19,22 @@
 package org.apache.karaf.main.util;
 
 import java.io.File;
+import java.net.MalformedURLException;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.util.Collections;
 
 import junit.framework.Assert;
 
+import org.apache.karaf.util.maven.Parser;
 import org.junit.Test;
 
 public class SimpleMavenResolverTest {
     private static final String ARTIFACT_COORDS = "mvn:org.apache.karaf.features/framework/1.0.0/xml/features";
 
     @Test
-    public void mavenToPath() throws URISyntaxException {
-        String resolvedPath = SimpleMavenResolver.fromMaven(new URI(ARTIFACT_COORDS));
+    public void mavenToPath() throws MalformedURLException {
+        String resolvedPath = Parser.pathFromMaven(ARTIFACT_COORDS);
         Assert.assertEquals("org/apache/karaf/features/framework/1.0.0/framework-1.0.0-features.xml", resolvedPath);
     }
     

http://git-wip-us.apache.org/repos/asf/karaf/blob/e920dde0/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index ddba0e1..0b555c1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -532,6 +532,11 @@
                 <artifactId>demos</artifactId>
                 <version>${project.version}</version>
             </dependency>
+            <dependency>
+                <groupId>org.apache.karaf.demos.profiles</groupId>
+                <artifactId>registry</artifactId>
+                <version>${project.version}</version>
+            </dependency>
 
             <dependency>
                 <groupId>org.apache.karaf.features</groupId>

http://git-wip-us.apache.org/repos/asf/karaf/blob/e920dde0/profile/pom.xml
----------------------------------------------------------------------
diff --git a/profile/pom.xml b/profile/pom.xml
index 086355b..b2fab7d 100644
--- a/profile/pom.xml
+++ b/profile/pom.xml
@@ -77,6 +77,56 @@
         </dependency>
 
         <dependency>
+            <groupId>org.apache.karaf.features</groupId>
+            <artifactId>org.apache.karaf.features.core</artifactId>
+            <optional>true</optional>
+        </dependency>
+
+        <dependency>
+            <groupId>org.eclipse.equinox</groupId>
+            <artifactId>org.eclipse.equinox.region</artifactId>
+            <optional>true</optional>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.resolver</artifactId>
+            <optional>true</optional>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.karaf.kar</groupId>
+            <artifactId>org.apache.karaf.kar.core</artifactId>
+            <optional>true</optional>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.karaf.deployer</groupId>
+            <artifactId>org.apache.karaf.deployer.blueprint</artifactId>
+            <optional>true</optional>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.karaf.deployer</groupId>
+            <artifactId>org.apache.karaf.deployer.spring</artifactId>
+            <optional>true</optional>
+        </dependency>
+
+        <dependency>
+            <groupId>org.ops4j.pax.url</groupId>
+            <artifactId>pax-url-wrap</artifactId>
+            <classifier>uber</classifier>
+            <optional>true</optional>
+        </dependency>
+
+        <dependency>
+            <groupId>org.ops4j.pax.url</groupId>
+            <artifactId>pax-url-war</artifactId>
+            <classifier>uber</classifier>
+            <optional>true</optional>
+        </dependency>
+
+        <dependency>
             <groupId>org.jledit</groupId>
             <artifactId>core</artifactId>
             <optional>true</optional>
@@ -87,6 +137,12 @@
             <artifactId>slf4j-jdk14</artifactId>
             <scope>test</scope>
         </dependency>
+
+        <dependency>
+            <groupId>org.apache.karaf.demos.profiles</groupId>
+            <artifactId>registry</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <build>
@@ -123,10 +179,13 @@
                         <Private-Package>
                             org.apache.karaf.profile.command,
                             org.apache.karaf.profile.command.completers,
+                            org.apache.karaf.profile.assembly,
                             org.apache.karaf.profile.impl,
                             org.apache.karaf.profile.impl.osgi,
+                            org.apache.karaf.util.config,
+                            org.apache.karaf.util.maven,
                             org.apache.karaf.util.tracker,
-                            org.apache.karaf.util.properties,
+                            org.apache.felix.utils.version,
                             org.apache.felix.utils.properties,
                         </Private-Package>
                         <Provide-Capability>

http://git-wip-us.apache.org/repos/asf/karaf/blob/e920dde0/profile/src/main/java/org/apache/karaf/profile/Profile.java
----------------------------------------------------------------------
diff --git a/profile/src/main/java/org/apache/karaf/profile/Profile.java b/profile/src/main/java/org/apache/karaf/profile/Profile.java
index f924b7a..6f4d9fd 100644
--- a/profile/src/main/java/org/apache/karaf/profile/Profile.java
+++ b/profile/src/main/java/org/apache/karaf/profile/Profile.java
@@ -69,7 +69,19 @@ public interface Profile {
      */
     String ATTRIBUTE_PREFIX = "attribute.";
 
+    /**
+     * The config prefix for in the agent configuration
+     */
+    String CONFIG_PREFIX = "config.";
+
+    /**
+     * The config prefix for in the agent configuration
+     */
+    String SYSTEM_PREFIX = "system.";
+
     Map<String, String> getAttributes();
+    Map<String, String> getConfig();
+    Map<String, String> getSystem();
 
     List<String> getParentIds();
 

http://git-wip-us.apache.org/repos/asf/karaf/blob/e920dde0/profile/src/main/java/org/apache/karaf/profile/assembly/AssemblyDeployCallback.java
----------------------------------------------------------------------
diff --git a/profile/src/main/java/org/apache/karaf/profile/assembly/AssemblyDeployCallback.java b/profile/src/main/java/org/apache/karaf/profile/assembly/AssemblyDeployCallback.java
new file mode 100644
index 0000000..826aa7e
--- /dev/null
+++ b/profile/src/main/java/org/apache/karaf/profile/assembly/AssemblyDeployCallback.java
@@ -0,0 +1,227 @@
+/*
+ * 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.karaf.profile.assembly;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.StandardOpenOption;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.jar.Attributes;
+import java.util.jar.JarFile;
+
+import org.apache.karaf.features.FeatureEvent;
+import org.apache.karaf.features.FeaturesService;
+import org.apache.karaf.features.internal.download.DownloadCallback;
+import org.apache.karaf.features.internal.download.DownloadManager;
+import org.apache.karaf.features.internal.download.Downloader;
+import org.apache.karaf.features.internal.download.StreamProvider;
+import org.apache.karaf.features.internal.model.Config;
+import org.apache.karaf.features.internal.model.ConfigFile;
+import org.apache.karaf.features.internal.model.Feature;
+import org.apache.karaf.features.internal.model.Features;
+import org.apache.karaf.features.internal.service.Deployer;
+import org.apache.karaf.features.internal.service.State;
+import org.apache.karaf.features.internal.util.MapUtils;
+import org.apache.karaf.util.maven.Parser;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.startlevel.BundleStartLevel;
+import org.osgi.framework.wiring.BundleRevision;
+import org.osgi.resource.Resource;
+import org.osgi.resource.Wire;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class AssemblyDeployCallback implements Deployer.DeployCallback {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(Builder.class);
+
+    private final DownloadManager manager;
+    private final Path homeDirectory;
+    private final int defaultStartLevel;
+    private final Path etcDirectory;
+    private final Path systemDirectory;
+    private final Deployer.DeploymentState dstate;
+    private final AtomicLong nextBundleId = new AtomicLong(0);
+
+    private final Map<String, Bundle> bundles = new HashMap<>();
+
+    public AssemblyDeployCallback(DownloadManager manager, Path homeDirectory, int defaultStartLevel, BundleRevision systemBundle, Collection<Features> repositories) throws Exception {
+        this.manager = manager;
+        this.homeDirectory = homeDirectory;
+        this.etcDirectory = homeDirectory.resolve("etc");
+        this.systemDirectory = homeDirectory.resolve("system");
+        this.defaultStartLevel = defaultStartLevel;
+        dstate = new Deployer.DeploymentState();
+        dstate.bundles = new HashMap<>();
+        dstate.features = new HashMap<>();
+        dstate.bundlesPerRegion = new HashMap<>();
+        dstate.filtersPerRegion = new HashMap<>();
+        dstate.state = new State();
+
+        MapUtils.addToMapSet(dstate.bundlesPerRegion, FeaturesService.ROOT_REGION, 0l);
+        dstate.bundles.put(0l, systemBundle.getBundle());
+        for (Features repo : repositories) {
+            for (Feature f : repo.getFeature()) {
+                dstate.features.put(f.getId(), f);
+            }
+        }
+    }
+
+    public Map<String, Integer> getStartupBundles() {
+        Map<String, Integer> startup = new HashMap<>();
+        for (Map.Entry<String, Bundle> bundle : bundles.entrySet()) {
+            int level = bundle.getValue().adapt(BundleStartLevel.class).getStartLevel();
+            if (level <= 0) {
+                level = defaultStartLevel;
+            }
+            startup.put(bundle.getKey(), level);
+        }
+        return startup;
+    }
+
+    public Deployer.DeploymentState getDeploymentState() {
+        return dstate;
+    }
+
+    @Override
+    public void print(String message, boolean verbose) {
+    }
+
+    @Override
+    public void saveState(State state) {
+        dstate.state.replace(state);
+    }
+
+    @Override
+    public void persistResolveRequest(Deployer.DeploymentRequest request) throws IOException {
+    }
+
+    @Override
+    public void installFeatureConfigs(org.apache.karaf.features.Feature feature) throws IOException, InvalidSyntaxException {
+        LOGGER.info("Installing feature config for " + feature.getId());
+        for (Config config : ((Feature) feature).getConfig()) {
+            Path configFile = etcDirectory.resolve(config.getName());
+            if (!Files.exists(configFile)) {
+                Files.write(configFile, config.getValue().getBytes());
+            } else if (config.isAppend()) {
+                Files.write(configFile, config.getValue().getBytes(), StandardOpenOption.APPEND);
+            }
+        }
+        Downloader downloader = manager.createDownloader();
+        for (final ConfigFile configFile : ((Feature) feature).getConfigfile()) {
+            downloader.download(configFile.getLocation(), new DownloadCallback() {
+                @Override
+                public void downloaded(StreamProvider provider) throws Exception {
+                    Path input = provider.getFile().toPath();
+                    String path = configFile.getFinalname();
+                    if (path.startsWith("/")) {
+                        path = path.substring(1);
+                    }
+                    Path output = homeDirectory.resolve(path);
+                    Files.copy(input, output, StandardCopyOption.REPLACE_EXISTING);
+                }
+            });
+        }
+        try {
+            downloader.await();
+        } catch (Exception e) {
+            throw new IOException("Error downloading configuration files", e);
+        }
+    }
+
+    @Override
+    public void callListeners(FeatureEvent featureEvent) {
+    }
+
+    @Override
+    public Bundle installBundle(String region, String uri, InputStream is) throws BundleException {
+        LOGGER.info("Installing bundle " + uri);
+        try {
+            String path;
+            if (uri.startsWith("mvn:")) {
+                path = Parser.pathFromMaven(uri);
+            } else {
+                path = "generated/" + uri.replaceAll("[^0-9a-zA-Z.\\-_]+", "_");
+            }
+            final Path bundleSystemFile = systemDirectory.resolve(path);
+            Files.createDirectories(bundleSystemFile.getParent());
+            Files.copy(is, bundleSystemFile, StandardCopyOption.REPLACE_EXISTING);
+
+            Hashtable<String, String> headers = new Hashtable<>();
+            JarFile jar = new JarFile(bundleSystemFile.toFile());
+            Attributes attributes = jar.getManifest().getMainAttributes();
+            for (Map.Entry attr : attributes.entrySet()) {
+                headers.put(attr.getKey().toString(), attr.getValue().toString());
+            }
+            BundleRevision revision = new FakeBundleRevision(headers, uri, nextBundleId.incrementAndGet());
+            Bundle bundle = revision.getBundle();
+            MapUtils.addToMapSet(dstate.bundlesPerRegion, region, bundle.getBundleId());
+            dstate.bundles.put(bundle.getBundleId(), bundle);
+
+            bundles.put(path, bundle);
+            return bundle;
+        } catch (IOException e) {
+            throw new BundleException("Unable to install bundle", e);
+        }
+    }
+
+    @Override
+    public void updateBundle(Bundle bundle, String uri, InputStream is) throws BundleException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void uninstall(Bundle bundle) throws BundleException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void startBundle(Bundle bundle) throws BundleException {
+    }
+
+    @Override
+    public void stopBundle(Bundle bundle, int options) throws BundleException {
+    }
+
+    @Override
+    public void setBundleStartLevel(Bundle bundle, int startLevel) {
+        bundle.adapt(BundleStartLevel.class).setStartLevel(startLevel);
+    }
+
+    @Override
+    public void refreshPackages(Collection<Bundle> bundles) throws InterruptedException {
+    }
+
+    @Override
+    public void resolveBundles(Set<Bundle> bundles, Map<Resource, List<Wire>> wiring, Map<Resource, Bundle> resToBnd) {
+    }
+
+    @Override
+    public void replaceDigraph(Map<String, Map<String, Map<String, Set<String>>>> policies, Map<String, Set<Long>> bundles) throws BundleException, InvalidSyntaxException {
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e920dde0/profile/src/main/java/org/apache/karaf/profile/assembly/Builder.java
----------------------------------------------------------------------
diff --git a/profile/src/main/java/org/apache/karaf/profile/assembly/Builder.java b/profile/src/main/java/org/apache/karaf/profile/assembly/Builder.java
new file mode 100644
index 0000000..e0f3ce3
--- /dev/null
+++ b/profile/src/main/java/org/apache/karaf/profile/assembly/Builder.java
@@ -0,0 +1,969 @@
+/*
+ * 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.karaf.profile.assembly;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystemNotFoundException;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.UUID;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.jar.Attributes;
+import java.util.jar.Manifest;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+import org.apache.felix.utils.properties.InterpolationHelper;
+import org.apache.felix.utils.properties.Properties;
+import org.apache.karaf.features.FeaturesService;
+import org.apache.karaf.features.internal.download.DownloadCallback;
+import org.apache.karaf.features.internal.download.DownloadManager;
+import org.apache.karaf.features.internal.download.Downloader;
+import org.apache.karaf.features.internal.download.StreamProvider;
+import org.apache.karaf.features.internal.download.impl.DownloadManagerHelper;
+import org.apache.karaf.features.internal.model.Bundle;
+import org.apache.karaf.features.internal.model.Conditional;
+import org.apache.karaf.features.internal.model.ConfigFile;
+import org.apache.karaf.features.internal.model.Dependency;
+import org.apache.karaf.features.internal.model.Feature;
+import org.apache.karaf.features.internal.model.Features;
+import org.apache.karaf.features.internal.model.JaxbUtil;
+import org.apache.karaf.features.internal.repository.BaseRepository;
+import org.apache.karaf.features.internal.resolver.ResourceBuilder;
+import org.apache.karaf.features.internal.service.Deployer;
+import org.apache.karaf.features.internal.util.MapUtils;
+import org.apache.karaf.kar.internal.Kar;
+import org.apache.karaf.profile.Profile;
+import org.apache.karaf.profile.ProfileBuilder;
+import org.apache.karaf.profile.impl.Profiles;
+import org.apache.karaf.util.config.PropertiesLoader;
+import org.apache.karaf.util.maven.Parser;
+import org.ops4j.pax.url.mvn.MavenResolver;
+import org.ops4j.pax.url.mvn.MavenResolvers;
+import org.osgi.framework.Constants;
+import org.osgi.framework.wiring.BundleRevision;
+import org.osgi.resource.Resource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static java.util.jar.JarFile.MANIFEST_NAME;
+
+public class Builder {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(Builder.class);
+
+    private static final String FEATURES_REPOSITORIES = "featuresRepositories";
+    private static final String FEATURES_BOOT = "featuresBoot";
+
+    public static enum Stage {
+        Startup, Boot, Installed
+    }
+    
+    static class RepositoryInfo {
+        Stage stage;
+        boolean addAll;
+    }
+    
+    //
+    // Input parameters
+    //
+
+    List<String> profilesUris = new ArrayList<>();
+    boolean defaultAddAll = true;
+    Stage defaultStage = Stage.Startup;
+    Map<String, RepositoryInfo> kars = new LinkedHashMap<>();
+    Map<String, Stage> profiles = new LinkedHashMap<>();
+    Map<String, RepositoryInfo> repositories = new LinkedHashMap<>();
+    Map<String, Stage> features = new LinkedHashMap<>();
+    Map<String, Stage> bundles = new LinkedHashMap<>();
+    String javase = "1.7";
+    String environment = null;
+    boolean useReferenceUrls;
+    boolean use24SyntaxForStartup;
+    boolean ignoreDependencyFlag;
+    int defaultStartLevel = 50;
+    Path homeDirectory;
+
+    private ScheduledExecutorService executor;
+    private DownloadManager manager;
+    private Path etcDirectory;
+    private Path systemDirectory;
+    private Map<String, Profile> allProfiles;
+
+    public static Builder newInstance() {
+        return new Builder();
+    }
+
+    public Builder defaultStage(Stage stage) {
+        this.defaultStage = stage;
+        return this;
+    }
+
+    public Builder defaultAddAll(boolean addAll) {
+        this.defaultAddAll = addAll;
+        return this;
+    }
+
+    public Builder profilesUris(String... profilesUri) {
+        Collections.addAll(this.profilesUris, profilesUri);
+        return this;
+    }
+
+    public Builder kars(String... kars) {
+        return kars(defaultStage, defaultAddAll, kars);
+    }
+
+    public Builder kars(boolean addAll, String... kars) {
+        return kars(defaultStage, addAll, kars);
+    }
+
+    public Builder kars(Stage stage, boolean addAll, String... kars) {
+        for (String kar : kars) {
+            RepositoryInfo info = new RepositoryInfo();
+            info.stage = stage;
+            info.addAll = addAll;
+            this.kars.put(kar, info);
+        }
+        return this;
+    }
+
+    public Builder repositories(String... repositories) {
+        return repositories(defaultStage, defaultAddAll, repositories);
+    }
+
+    public Builder repositories(boolean addAll, String... repositories) {
+        return repositories(defaultStage, addAll, repositories);
+    }
+
+    public Builder repositories(Stage stage, boolean addAll, String... repositories) {
+        for (String repository : repositories) {
+            RepositoryInfo info = new RepositoryInfo();
+            info.stage = stage;
+            info.addAll = addAll;
+            this.repositories.put(repository, info);
+        }
+        return this;
+    }
+
+    public Builder features(String... features) {
+        return features(defaultStage, features);
+    }
+
+    public Builder features(Stage stage, String... features) {
+        for (String feature : features) {
+            this.features.put(feature, stage);
+        }
+        return this;
+    }
+
+    public Builder bundles(String... bundles) {
+        return bundles(defaultStage, bundles);
+    }
+
+    public Builder bundles(Stage stage, String... bundles) {
+        for (String bundle : bundles) {
+            this.bundles.put(bundle, stage);
+        }
+        return this;
+    }
+
+    public Builder profiles(String... profiles) {
+        return profiles(defaultStage, profiles);
+    }
+
+    public Builder profiles(Stage stage, String... profiles) {
+        for (String profile : profiles) {
+            this.profiles.put(profile, stage);
+        }
+        return this;
+    }
+
+    public Builder homeDirectory(Path homeDirectory) {
+        if (homeDirectory == null) {
+            throw new IllegalArgumentException("homeDirectory is null");
+        }
+        this.homeDirectory = homeDirectory;
+        return this;
+    }
+
+    public Builder javase(String javase) {
+        if (javase == null) {
+            throw new IllegalArgumentException("javase is null");
+        }
+        this.javase = javase;
+        return this;
+    }
+
+    public Builder environment(String environment) {
+        this.environment = environment;
+        return this;
+    }
+
+    public Builder useReferenceUrls() {
+        return useReferenceUrls(true);
+    }
+
+    public Builder useReferenceUrls(boolean useReferenceUrls) {
+        this.useReferenceUrls = useReferenceUrls;
+        return this;
+    }
+
+    public Builder use24SyntaxForStartup() {
+        return use24SyntaxForStartup(true);
+    }
+
+    public Builder use24SyntaxForStartup(boolean use24SyntaxForStartup) {
+        this.use24SyntaxForStartup = use24SyntaxForStartup;
+        return this;
+    }
+
+    public Builder defaultStartLevel(int defaultStartLevel) {
+        this.defaultStartLevel = defaultStartLevel;
+        return this;
+    }
+
+    public Builder ignoreDependencyFlag() {
+        return ignoreDependencyFlag(true);
+    }
+
+    public Builder ignoreDependencyFlag(boolean ignoreDependencyFlag) {
+        this.ignoreDependencyFlag = ignoreDependencyFlag;
+        return this;
+    }
+
+    public Builder staticFramework() {
+        // TODO: load this from resources
+        return staticFramework("4.0.0-SNAPSHOT");
+    }
+
+    public Builder staticFramework(String version) {
+        return this.defaultStage(Stage.Startup)
+                   .useReferenceUrls()
+                   .kars(Stage.Startup, true, "mvn:org.apache.karaf.features/static/" + version + "/kar");
+    }
+
+    public void generateAssembly() throws Exception {
+        if (javase == null) {
+            throw new IllegalArgumentException("javase is not set");
+        }
+        if (homeDirectory == null) {
+            throw new IllegalArgumentException("homeDirectory is not set");
+        }
+        try {
+            doGenerateAssembly();
+        } finally {
+            if (executor != null) {
+                executor.shutdownNow();
+            }
+        }
+    }
+
+    private void doGenerateAssembly() throws Exception {
+        systemDirectory = homeDirectory.resolve("system");
+        etcDirectory = homeDirectory.resolve("etc");
+
+        LOGGER.info("Generating karaf assembly: " + homeDirectory);
+
+        //
+        // Create download manager
+        //
+        Dictionary<String, String> props = new Hashtable<>();
+        MavenResolver resolver = MavenResolvers.createMavenResolver(props, "org.ops4j.pax.url.mvn");
+        executor = Executors.newScheduledThreadPool(8);
+        manager = new CustomDownloadManager(resolver, executor);
+
+        //
+        // Unzip kars
+        //
+        LOGGER.info("Unzipping kars");
+        Map<String, RepositoryInfo> repositories = new LinkedHashMap<>(this.repositories);
+        Downloader downloader = manager.createDownloader();
+        for (String kar : kars.keySet()) {
+            downloader.download(kar, null);
+        }
+        downloader.await();
+        for (String karUri : kars.keySet()) {
+            Kar kar = new Kar(manager.getProviders().get(karUri).getFile().toURI());
+            kar.extract(systemDirectory.toFile(), homeDirectory.toFile());
+            RepositoryInfo info = kars.get(karUri);
+            for (URI repositoryUri : kar.getFeatureRepos()) {
+                repositories.put(repositoryUri.toString(), info);
+            }
+        }
+
+        //
+        // Propagate feature installation from repositories
+        //
+        Map<String, Stage> features = new LinkedHashMap<>(this.features);
+        Map<String, Features> karRepositories = loadRepositories(manager, repositories.keySet(), false);
+        for (String repo : repositories.keySet()) {
+            RepositoryInfo info = repositories.get(repo);
+            if (info.addAll) {
+                for (Feature feature : karRepositories.get(repo).getFeature()) {
+                    features.put(feature.getId(), info.stage);
+                }
+            }
+        }
+
+        //
+        // Load profiles
+        //
+        LOGGER.info("Loading profiles");
+        allProfiles = new HashMap<>();
+        for (String profilesUri : profilesUris) {
+            String uri = profilesUri;
+            if (uri.startsWith("jar:") && uri.contains("!/")) {
+                uri = uri.substring("jar:".length(), uri.indexOf("!/"));
+            }
+            if (!uri.startsWith("file:")) {
+                downloader = manager.createDownloader();
+                downloader.download(uri, null);
+                downloader.await();
+                StreamProvider provider = manager.getProviders().get(uri);
+                profilesUri = profilesUri.replace(uri, provider.getFile().toURI().toString());
+            }
+            URI profileURI = URI.create(profilesUri);
+            Path profilePath;
+            try {
+                profilePath = Paths.get(profileURI);
+            } catch (FileSystemNotFoundException e) {
+                // file system does not exist, try to create it
+                FileSystem fs = FileSystems.newFileSystem(profileURI, new HashMap<String, Object>(), Builder.class.getClassLoader());
+                profilePath = fs.provider().getPath(profileURI);
+            }
+            allProfiles.putAll(Profiles.loadProfiles(profilePath));
+        }
+
+        // Generate profiles
+        Profile startupProfile = generateProfile(Stage.Startup, profiles, repositories, features, bundles);
+        Profile bootProfile = generateProfile(Stage.Boot, profiles, repositories, features, bundles);
+        Profile installedProfile = generateProfile(Stage.Installed, profiles, repositories, features, bundles);
+
+        //
+        // Compute overall profile
+        //
+        Profile overallProfile = ProfileBuilder.Factory.create(UUID.randomUUID().toString())
+                .setParents(Arrays.asList(startupProfile.getId(), bootProfile.getId(), installedProfile.getId()))
+                .getProfile();
+        Profile overallOverlay = Profiles.getOverlay(overallProfile, allProfiles, environment);
+        Profile overallEffective = Profiles.getEffective(overallOverlay, false);
+
+        manager = new CustomDownloadManager(resolver, executor, overallEffective);
+
+        Hashtable<String, String> agentProps = new Hashtable<>(overallEffective.getConfiguration("org.ops4j.pax.url.mvn"));
+        final Map<String, String> properties = new HashMap<>();
+        properties.put("karaf.default.repository", "system");
+        InterpolationHelper.performSubstitution(agentProps, new InterpolationHelper.SubstitutionCallback() {
+            @Override
+            public String getValue(String key) {
+                return properties.get(key);
+            }
+        }, false, false, true);
+
+        //
+        // Write config and system properties
+        //
+        Path configPropertiesPath = etcDirectory.resolve("config.properties");
+        Properties configProperties = new Properties(configPropertiesPath.toFile());
+        configProperties.putAll(overallEffective.getConfig());
+        configProperties.save();
+
+        Path systemPropertiesPath = etcDirectory.resolve("system.properties");
+        Properties systemProperties = new Properties(systemPropertiesPath.toFile());
+        systemProperties.putAll(overallEffective.getSystem());
+        systemProperties.save();
+
+        //
+        // Download libraries
+        //
+        // TODO: handle karaf 2.x and 3.x libraries
+        LOGGER.info("Downloading libraries");
+        downloader = manager.createDownloader();
+        downloadLibraries(downloader, overallEffective.getLibraries(), "lib");
+        downloadLibraries(downloader, overallEffective.getEndorsedLibraries(), "lib/endorsed");
+        downloadLibraries(downloader, overallEffective.getExtensionLibraries(), "lib/ext");
+        downloadLibraries(downloader, overallEffective.getBootLibraries(), "lib/boot");
+        downloader.await();
+
+        //
+        // Write all configuration files
+        //
+        for (Map.Entry<String, byte[]> config : overallEffective.getFileConfigurations().entrySet()) {
+            Path configFile = etcDirectory.resolve(config.getKey());
+            Files.createDirectories(configFile.getParent());
+            Files.write(configFile, config.getValue());
+        }
+
+        //
+        // Startup stage
+        //
+        Profile startupEffective = startupStage(startupProfile);
+
+        //
+        // Boot stage
+        //
+        Set<Feature> allBootFeatures = bootStage(bootProfile, startupEffective);
+
+        //
+        // Installed stage
+        //
+        installStage(installedProfile, allBootFeatures);
+    }
+
+    private void downloadLibraries(Downloader downloader, List<String> libraries, final String path) throws MalformedURLException {
+        for (String library : libraries) {
+            downloader.download(library, new DownloadCallback() {
+                @Override
+                public void downloaded(final StreamProvider provider) throws Exception {
+                    synchronized (provider) {
+                        Path input = provider.getFile().toPath();
+                        Path output = homeDirectory.resolve(path).resolve(input.getFileName().toString());
+                        Files.copy(input, output);
+                    }
+                }
+            });
+        }
+    }
+
+    private void installStage(Profile installedProfile, Set<Feature> allBootFeatures) throws Exception {
+        Downloader downloader;//
+        // Handle installed profiles
+        //
+        Profile installedOverlay = Profiles.getOverlay(installedProfile, allProfiles, environment);
+        Profile installedEffective = Profiles.getEffective(installedOverlay, false);
+
+        downloader = manager.createDownloader();
+
+        // Load startup repositories
+        Map<String, Features> installedRepositories = loadRepositories(manager, installedEffective.getRepositories(), true);
+        // Compute startup feature dependencies
+        Set<Feature> allInstalledFeatures = new HashSet<>();
+        for (Features repo : installedRepositories.values()) {
+            allInstalledFeatures.addAll(repo.getFeature());
+        }
+        Set<Feature> installedFeatures = new LinkedHashSet<>();
+        // Add boot features for search
+        allInstalledFeatures.addAll(allBootFeatures);
+        for (String feature : installedEffective.getFeatures()) {
+            addFeatures(installedFeatures, allInstalledFeatures, feature);
+        }
+        for (Feature feature : installedFeatures) {
+            for (Bundle bundle : feature.getBundle()) {
+                if (!ignoreDependencyFlag || !bundle.isDependency()) {
+                    installArtifact(downloader, bundle.getLocation().trim());
+                }
+            }
+            for (Conditional cond : feature.getConditional()) {
+                for (Bundle bundle : cond.getBundle()) {
+                    if (!ignoreDependencyFlag || !bundle.isDependency()) {
+                        installArtifact(downloader, bundle.getLocation().trim());
+                    }
+                }
+            }
+        }
+        for (String location : installedEffective.getBundles()) {
+            installArtifact(downloader, location);
+        }
+        downloader.await();
+    }
+
+    private Set<Feature> bootStage(Profile bootProfile, Profile startupEffective) throws Exception {
+        //
+        // Handle boot profiles
+        //
+        Profile bootOverlay = Profiles.getOverlay(bootProfile, allProfiles, environment);
+        Profile bootEffective = Profiles.getEffective(bootOverlay, false);
+        // Load startup repositories
+        Map<String, Features> bootRepositories = loadRepositories(manager, bootEffective.getRepositories(), true);
+        // Compute startup feature dependencies
+        Set<Feature> allBootFeatures = new HashSet<>();
+        for (Features repo : bootRepositories.values()) {
+            allBootFeatures.addAll(repo.getFeature());
+        }
+        // Generate a global feature
+        Map<String, Dependency> generatedDep = new HashMap<>();
+        Feature generated = new Feature();
+        generated.setName(UUID.randomUUID().toString());
+        // Add feature dependencies
+        for (String dependency : bootEffective.getFeatures()) {
+            Dependency dep = generatedDep.get(dependency);
+            if (dep == null) {
+                dep = new Dependency();
+                dep.setName(dependency);
+                generated.getFeature().add(dep);
+                generatedDep.put(dep.getName(), dep);
+            }
+            dep.setDependency(false);
+        }
+        // Add bundles
+        for (String location : bootEffective.getBundles()) {
+            location = location.replace("profile:", "file:etc/");
+            Bundle bun = new Bundle();
+            bun.setLocation(location);
+            generated.getBundle().add(bun);
+        }
+        Features rep = new Features();
+        rep.setName(UUID.randomUUID().toString());
+        rep.getRepository().addAll(bootEffective.getRepositories());
+        rep.getFeature().add(generated);
+        allBootFeatures.add(generated);
+
+        Downloader downloader = manager.createDownloader();
+
+        // Compute startup feature dependencies
+        Set<Feature> bootFeatures = new HashSet<>();
+        addFeatures(bootFeatures, allBootFeatures, generated.getName());
+        for (Feature feature : bootFeatures) {
+            // the feature is a startup feature, updating startup.properties file
+            LOGGER.info("Feature " + feature.getName() + " is defined as a boot feature");
+            // add the feature in the system folder
+            Set<String> locations = new HashSet<>();
+            for (Bundle bundle : feature.getBundle()) {
+                if (!ignoreDependencyFlag || !bundle.isDependency()) {
+                    locations.add(bundle.getLocation().trim());
+                }
+            }
+            for (Conditional cond : feature.getConditional()) {
+                for (Bundle bundle : cond.getBundle()) {
+                    if (!ignoreDependencyFlag || !bundle.isDependency()) {
+                        locations.add(bundle.getLocation().trim());
+                    }
+                }
+            }
+
+            // Build optional features and known prerequisites
+            Map<String, List<String>> prereqs = new HashMap<>();
+            prereqs.put("blueprint:", Arrays.asList("deployer", "aries-blueprint"));
+            prereqs.put("spring:", Arrays.asList("deployer", "spring"));
+            prereqs.put("wrap:", Arrays.asList("wrap"));
+            prereqs.put("war:", Arrays.asList("war"));
+            for (String location : locations) {
+                installArtifact(downloader, location);
+                for (Map.Entry<String, List<String>> entry : prereqs.entrySet()) {
+                    if (location.startsWith(entry.getKey())) {
+                        for (String prereq : entry.getValue()) {
+                            Dependency dep = generatedDep.get(prereq);
+                            if (dep == null) {
+                                dep = new Dependency();
+                                dep.setName(prereq);
+                                generated.getFeature().add(dep);
+                                generatedDep.put(dep.getName(), dep);
+                            }
+                            dep.setPrerequisite(true);
+                        }
+                    }
+                }
+            }
+            // Install config files
+            for (ConfigFile configFile : feature.getConfigfile()) {
+                installArtifact(downloader, configFile.getLocation().trim());
+            }
+            for (Conditional cond : feature.getConditional()) {
+                for (ConfigFile configFile : cond.getConfigfile()) {
+                    installArtifact(downloader, configFile.getLocation().trim());
+                }
+            }
+        }
+
+        // If there are bundles to install, we can't use the boot features only
+        // so keep the generated feature
+        Path featuresCfgFile = etcDirectory.resolve("org.apache.karaf.features.cfg");
+        if (!generated.getBundle().isEmpty()) {
+            File output = etcDirectory.resolve(rep.getName() + ".xml").toFile();
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            JaxbUtil.marshal(rep, baos);
+            ByteArrayInputStream bais;
+            String repoUrl;
+            if (use24SyntaxForStartup) {
+                String str = baos.toString();
+                str = str.replace("http://karaf.apache.org/xmlns/features/v1.3.0", "http://karaf.apache.org/xmlns/features/v1.2.0");
+                str = str.replaceAll(" dependency=\".*?\"", "");
+                str = str.replaceAll(" prerequisite=\".*?\"", "");
+                for (Feature f : rep.getFeature()) {
+                    for (Dependency d : f.getFeature()) {
+                        if (d.isPrerequisite()) {
+                            if (!startupEffective.getFeatures().contains(d.getName())) {
+                                LOGGER.warn("Feature " + d.getName() + " is a prerequisite and should be installed as a startup feature.");                }
+                        }
+                    }
+                }
+                bais = new ByteArrayInputStream(str.getBytes());
+                repoUrl = "file:etc/" + output.getName();
+            } else {
+                bais = new ByteArrayInputStream(baos.toByteArray());
+                repoUrl = "file:${karaf.home}/etc/" + output.getName();
+            }
+            Files.copy(bais, output.toPath());
+            Properties featuresProperties = new Properties(featuresCfgFile.toFile());
+            featuresProperties.put(FEATURES_REPOSITORIES, repoUrl);
+            featuresProperties.put(FEATURES_BOOT, generated.getName());
+            featuresProperties.save();
+        }
+        else {
+            String boot = "";
+            for (Dependency dep : generatedDep.values()) {
+                if (dep.isPrerequisite()) {
+                    if (boot.isEmpty()) {
+                        boot = "(";
+                    } else {
+                        boot = boot + ",";
+                    }
+                    boot = boot + dep.getName();
+                }
+            }
+            if (!boot.isEmpty()) {
+                boot = boot + ")";
+            }
+            // TODO: for dependencies, we'd need to resolve the features completely
+            for (Dependency dep : generatedDep.values()) {
+                if (!dep.isPrerequisite() && !dep.isDependency()) {
+                    if (!boot.isEmpty()) {
+                        boot = boot + ",";
+                    }
+                    boot = boot + dep.getName();
+                }
+            }
+            String repos = "";
+            for (String repo : new HashSet<>(rep.getRepository())) {
+                if (!repos.isEmpty()) {
+                    repos = repos + ",";
+                }
+                repos = repos + repo;
+            }
+
+            Properties featuresProperties = new Properties(featuresCfgFile.toFile());
+            featuresProperties.put(FEATURES_REPOSITORIES, repos);
+            featuresProperties.put(FEATURES_BOOT, boot);
+            // TODO: reformat to multiline values
+            featuresProperties.save();
+        }
+        downloader.await();
+        return allBootFeatures;
+    }
+
+    private Profile startupStage(Profile startupProfile) throws Exception {
+        //
+        // Compute startup
+        //
+        Profile startupOverlay = Profiles.getOverlay(startupProfile, allProfiles, environment);
+        Profile startupEffective = Profiles.getEffective(startupOverlay, false);
+        // Load startup repositories
+        LOGGER.info("Loading repositories");
+        Map<String, Features> startupRepositories = loadRepositories(manager, startupEffective.getRepositories(), false);
+
+        //
+        // Resolve
+        //
+        LOGGER.info("Resolving features");
+        Map<String, Integer> bundles =
+                resolve(manager,
+                        startupRepositories.values(),
+                        startupEffective.getFeatures(),
+                        startupEffective.getBundles(),
+                        startupEffective.getOverrides(),
+                        startupEffective.getOptionals());
+
+        //
+        // Generate startup.properties
+        //
+        Properties startup = new Properties();
+        startup.setHeader(Collections.singletonList("# Bundles to be started on startup, with startlevel"));
+        Map<Integer, Set<String>> invertedStartupBundles = MapUtils.invert(bundles);
+        for (Map.Entry<Integer, Set<String>> entry : invertedStartupBundles.entrySet()) {
+            String startLevel = Integer.toString(entry.getKey());
+            for (String location : new TreeSet<>(entry.getValue())) {
+                if (location.startsWith("file:") && useReferenceUrls) {
+                    location = "reference:" + location;
+                }
+                if (location.startsWith("file:") && use24SyntaxForStartup) {
+                    location = location.substring("file:".length());
+                }
+                startup.put(location, startLevel);
+            }
+        }
+        Path startupProperties = etcDirectory.resolve("startup.properties");
+        startup.save(startupProperties.toFile());
+        return startupEffective;
+    }
+
+    private void installArtifact(Downloader downloader, String location) throws Exception {
+        LOGGER.info("== Installing artifact " + location);
+        location = DownloadManagerHelper.stripUrl(location);
+//        location = DownloadManagerHelper.removeInlinedMavenRepositoryUrl(location);
+        if (location.startsWith("mvn:")) {
+            if (location.endsWith("/")) {
+                // for bad formed URL (like in Camel for mustache-compiler), we remove the trailing /
+                location = location.substring(0, location.length() - 1);
+            }
+            downloader.download(location, new DownloadCallback() {
+                @Override
+                public void downloaded(final StreamProvider provider) throws Exception {
+                    Path path = systemDirectory.resolve(Parser.pathFromMaven(provider.getUrl()));
+                    synchronized (provider) {
+                        Files.createDirectories(path.getParent());
+                        Files.copy(provider.getFile().toPath(), path, StandardCopyOption.REPLACE_EXISTING);
+                    }
+                }
+            });
+            // add metadata for snapshot
+            /*
+            Artifact artifact = dependencyHelper.mvnToArtifact(location);
+            if (artifact.isSnapshot()) {
+                File metadataTarget = new File(targetFile.getParentFile(), "maven-metadata-local.xml");
+                try {
+                    MavenUtil.generateMavenMetadata(artifact, metadataTarget);
+                } catch (Exception e) {
+                    getLog().warn("Could not create maven-metadata-local.xml", e);
+                    getLog().warn("It means that this SNAPSHOT could be overwritten by an older one present on remote repositories");
+                }
+            }
+            */
+        } else {
+            LOGGER.warn("Ignoring artifact " + location);
+        }
+    }
+
+    private void addFeatures(Set<Feature> startupFeatures, Set<Feature> features, String feature) {
+        int nbFound = 0;
+        for (Feature f : features) {
+            String[] split = feature.split("/");
+            if (split.length == 2) {
+                if (f.getName().equals(split[0]) && f.getVersion().equals(split[1])) {
+                    for (Dependency dep : f.getFeature()) {
+                        addFeatures(startupFeatures, features, dep.getName());
+                    }
+                    startupFeatures.add(f);
+                    nbFound++;
+                }
+            } else {
+                if (feature.equals(f.getName())) {
+                    for (Dependency dep : f.getFeature()) {
+                        addFeatures(startupFeatures, features, dep.getName());
+                    }
+                    startupFeatures.add(f);
+                    nbFound++;
+                }
+            }
+        }
+        if (nbFound == 0) {
+            throw new IllegalStateException("Could not find matching feature for " + feature);
+        }
+    }
+
+    private List<String> getStaged(Stage stage, Map<String, Stage> data) {
+        List<String> staged = new ArrayList<>();
+        for (String s : data.keySet()) {
+            if (data.get(s) == stage) {
+                staged.add(s);
+            }
+        }
+        return staged;
+    }
+
+    private List<String> getStagedRepositories(Stage stage, Map<String, RepositoryInfo> data) {
+        List<String> staged = new ArrayList<>();
+        for (String s : data.keySet()) {
+            if (data.get(s).stage == stage) {
+                staged.add(s);
+            }
+        }
+        return staged;
+    }
+
+    private Map<String, Features> loadRepositories(DownloadManager manager, Collection<String> repositories, final boolean install) throws Exception {
+        final Map<String, Features> loaded = new HashMap<>();
+        final Downloader downloader = manager.createDownloader();
+        for (String repository : repositories) {
+            downloader.download(repository, new DownloadCallback() {
+                @Override
+                public void downloaded(final StreamProvider provider) throws Exception {
+                    if (install) {
+                        synchronized (provider) {
+                            Path path = systemDirectory.resolve(Parser.pathFromMaven(provider.getUrl()));
+                            Files.createDirectories(path.getParent());
+                            Files.copy(provider.getFile().toPath(), path, StandardCopyOption.REPLACE_EXISTING);
+                        }
+                    }
+                    try (InputStream is = provider.open()) {
+                        Features featuresModel = JaxbUtil.unmarshal(provider.getUrl(), is, false);
+                        synchronized (loaded) {
+                            loaded.put(provider.getUrl(), featuresModel);
+                            for (String innerRepository : featuresModel.getRepository()) {
+                                downloader.download(innerRepository, this);
+                            }
+                        }
+                    }
+                }
+            });
+        }
+        downloader.await();
+        return loaded;
+    }
+
+    private Profile generateProfile(Stage stage, Map<String, Stage> profiles, Map<String, RepositoryInfo> repositories, Map<String, Stage> features, Map<String, Stage> bundles) {
+        Profile profile = ProfileBuilder.Factory.create(UUID.randomUUID().toString())
+                .setParents(getStaged(stage, profiles))
+                .setRepositories(getStagedRepositories(stage, repositories))
+                .setFeatures(getStaged(stage, features))
+                .setBundles(getStaged(stage, bundles))
+                .getProfile();
+        allProfiles.put(profile.getId(), profile);
+        return profile;
+    }
+
+    private Map<String, Integer> resolve(
+                    DownloadManager manager,
+                    Collection<Features> repositories,
+                    Collection<String> features,
+                    Collection<String> bundles,
+                    Collection<String> overrides,
+                    Collection<String> optionals) throws Exception {
+        BundleRevision systemBundle = getSystemBundle();
+        AssemblyDeployCallback callback = new AssemblyDeployCallback(manager, homeDirectory, defaultStartLevel, systemBundle, repositories);
+        Deployer deployer = new Deployer(manager, callback);
+
+        // Install framework
+        Deployer.DeploymentRequest request = createDeploymentRequest();
+        // Add overrides
+        request.overrides.addAll(overrides);
+        // Add optional resources
+        final List<Resource> resources = new ArrayList<>();
+        Downloader downloader = manager.createDownloader();
+        for (String optional : optionals) {
+            downloader.download(optional, new DownloadCallback() {
+                @Override
+                public void downloaded(StreamProvider provider) throws Exception {
+                    Resource resource = ResourceBuilder.build(provider.getUrl(), getHeaders(provider));
+                    synchronized (resources) {
+                        resources.add(resource);
+                    }
+                }
+            });
+        }
+        downloader.await();
+        request.globalRepository = new BaseRepository(resources);
+        // Install features
+        for (String feature : features) {
+            MapUtils.addToMapSet(request.requirements, FeaturesService.ROOT_REGION, feature);
+        }
+        for (String bundle : bundles) {
+            MapUtils.addToMapSet(request.requirements, FeaturesService.ROOT_REGION, "bundle:" + bundle);
+        }
+        Set<String> prereqs = new HashSet<>();
+        while (true) {
+            try {
+                deployer.deploy(callback.getDeploymentState(), request);
+                break;
+            } catch (Deployer.PartialDeploymentException e) {
+                if (!prereqs.containsAll(e.getMissing())) {
+                    prereqs.addAll(e.getMissing());
+                } else {
+                    throw new Exception("Deployment aborted due to loop in missing prerequisites: " + e.getMissing());
+                }
+            }
+        }
+
+        return callback.getStartupBundles();
+    }
+
+    private Deployer.DeploymentRequest createDeploymentRequest() {
+        Deployer.DeploymentRequest request = new Deployer.DeploymentRequest();
+        request.bundleUpdateRange = FeaturesService.DEFAULT_BUNDLE_UPDATE_RANGE;
+        request.featureResolutionRange = FeaturesService.DEFAULT_FEATURE_RESOLUTION_RANGE;
+        request.overrides = new HashSet<>();
+        request.requirements = new HashMap<>();
+        request.stateChanges = new HashMap<>();
+        request.options = EnumSet.noneOf(FeaturesService.Option.class);
+        return request;
+    }
+
+    private BundleRevision getSystemBundle() throws Exception {
+        Path configPropPath = etcDirectory.resolve("config.properties");
+        Properties configProps = PropertiesLoader.loadPropertiesOrFail(configPropPath.toFile());
+        configProps.put("java.specification.version", javase);
+        configProps.substitute();
+
+        Attributes attributes = new Attributes();
+        attributes.putValue(Constants.BUNDLE_MANIFESTVERSION, "2");
+        attributes.putValue(Constants.BUNDLE_SYMBOLICNAME, "system.bundle");
+        attributes.putValue(Constants.BUNDLE_VERSION, "0.0.0");
+
+        String exportPackages = configProps.getProperty("org.osgi.framework.system.packages");
+        if (configProps.containsKey("org.osgi.framework.system.packages.extra")) {
+            exportPackages += "," + configProps.getProperty("org.osgi.framework.system.packages.extra");
+        }
+        exportPackages = exportPackages.replaceAll(",\\s*,", ",");
+        attributes.putValue(Constants.EXPORT_PACKAGE, exportPackages);
+
+        String systemCaps = configProps.getProperty("org.osgi.framework.system.capabilities");
+        attributes.putValue(Constants.PROVIDE_CAPABILITY, systemCaps);
+
+        final Hashtable<String, String> headers = new Hashtable<>();
+        for (Map.Entry attr : attributes.entrySet()) {
+            headers.put(attr.getKey().toString(), attr.getValue().toString());
+        }
+
+        return new FakeBundleRevision(headers, "system-bundle", 0l);
+    }
+
+    Map<String, String> getHeaders(StreamProvider provider) throws IOException {
+        try (
+                InputStream is = provider.open()
+        ) {
+            ZipInputStream zis = new ZipInputStream(is);
+            ZipEntry entry;
+            while ((entry = zis.getNextEntry()) != null) {
+                if (MANIFEST_NAME.equals(entry.getName())) {
+                    Attributes attributes = new Manifest(zis).getMainAttributes();
+                    Map<String, String> headers = new HashMap<>();
+                    for (Map.Entry attr : attributes.entrySet()) {
+                        headers.put(attr.getKey().toString(), attr.getValue().toString());
+                    }
+                    return headers;
+                }
+            }
+        }
+        throw new IllegalArgumentException("Resource " + provider.getUrl() + " does not contain a manifest");
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e920dde0/profile/src/main/java/org/apache/karaf/profile/assembly/CustomDownloadManager.java
----------------------------------------------------------------------
diff --git a/profile/src/main/java/org/apache/karaf/profile/assembly/CustomDownloadManager.java b/profile/src/main/java/org/apache/karaf/profile/assembly/CustomDownloadManager.java
new file mode 100644
index 0000000..824d435
--- /dev/null
+++ b/profile/src/main/java/org/apache/karaf/profile/assembly/CustomDownloadManager.java
@@ -0,0 +1,43 @@
+/*
+ * 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.karaf.profile.assembly;
+
+import java.util.concurrent.ScheduledExecutorService;
+
+import org.apache.karaf.features.internal.download.impl.AbstractDownloadTask;
+import org.apache.karaf.features.internal.download.impl.MavenDownloadManager;
+import org.apache.karaf.profile.Profile;
+import org.ops4j.pax.url.mvn.MavenResolver;
+
+public class CustomDownloadManager extends MavenDownloadManager {
+
+    private final Profile profile;
+
+    public CustomDownloadManager(MavenResolver resolver, ScheduledExecutorService executor) {
+        this(resolver, executor, null);
+    }
+
+    public CustomDownloadManager(MavenResolver resolver, ScheduledExecutorService executor, Profile profile) {
+        super(resolver, executor);
+        this.profile = profile;
+    }
+
+    @Override
+    protected AbstractDownloadTask createCustomDownloadTask(String url) {
+        return new CustomSimpleDownloadTask(executorService, profile, url);
+    }
+}


Mime
View raw message