You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@karaf.apache.org by gn...@apache.org on 2015/02/27 12:54:56 UTC

[1/2] karaf git commit: [KARAF-3546] Provide a programmatic way to build custom karaf distributions outside of maven

Repository: karaf
Updated Branches:
  refs/heads/master 3f6bca010 -> e920dde01


http://git-wip-us.apache.org/repos/asf/karaf/blob/e920dde0/profile/src/main/java/org/apache/karaf/profile/assembly/CustomSimpleDownloadTask.java
----------------------------------------------------------------------
diff --git a/profile/src/main/java/org/apache/karaf/profile/assembly/CustomSimpleDownloadTask.java b/profile/src/main/java/org/apache/karaf/profile/assembly/CustomSimpleDownloadTask.java
new file mode 100644
index 0000000..94f5475
--- /dev/null
+++ b/profile/src/main/java/org/apache/karaf/profile/assembly/CustomSimpleDownloadTask.java
@@ -0,0 +1,161 @@
+/*
+ * 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.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLStreamHandler;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.concurrent.ScheduledExecutorService;
+
+import org.apache.karaf.deployer.blueprint.BlueprintTransformer;
+import org.apache.karaf.deployer.spring.SpringTransformer;
+import org.apache.karaf.features.internal.download.impl.AbstractRetryableDownloadTask;
+import org.apache.karaf.profile.Profile;
+
+public class CustomSimpleDownloadTask extends AbstractRetryableDownloadTask {
+
+    private static final String WRAP_URI_PREFIX = "wrap";
+    private static final String SPRING_URI_PREFIX = "spring";
+    private static final String BLUEPRINT_URI_PREFIX = "blueprint";
+    private static final String WAR_URI_PREFIX = "war";
+    private static final String PROFILE_URI_PREFIX = "profile";
+
+    private final Profile profile;
+
+    public CustomSimpleDownloadTask(ScheduledExecutorService executorService, Profile profile, String url) {
+        super(executorService, url);
+        this.profile = profile;
+    }
+
+    @Override
+    protected File download() throws Exception {
+        URL url = createUrl(getUrl());
+        Path path = Files.createTempFile("download-", null);
+        try (InputStream is = url.openStream()) {
+            Files.copy(is, path, StandardCopyOption.REPLACE_EXISTING);
+        }
+        return path.toFile();
+    }
+
+    private URL createUrl(String url) throws MalformedURLException, URISyntaxException {
+        URLStreamHandler handler = getUrlStreamHandler(url);
+        if (handler != null) {
+            return new URL(null, url, handler);
+        } else {
+            return new URL(url);
+        }
+    }
+
+    private URLStreamHandler getUrlStreamHandler(String url) throws URISyntaxException {
+        String scheme = new URI(url).getScheme();
+        switch (scheme) {
+        case WRAP_URI_PREFIX:
+            return new org.ops4j.pax.url.wrap.Handler();
+        case WAR_URI_PREFIX:
+            return new org.ops4j.pax.url.war.Handler();
+        case SPRING_URI_PREFIX:
+            return new SpringURLHandler();
+        case BLUEPRINT_URI_PREFIX:
+            return new BlueprintURLHandler();
+        case PROFILE_URI_PREFIX:
+            if (profile != null) {
+                return new ProfileURLHandler();
+            }
+        default:
+            return null;
+        }
+    }
+
+    public class SpringURLHandler extends URLStreamHandler {
+        @Override
+        protected URLConnection openConnection(URL u) throws IOException {
+            return new URLConnection(u) {
+                @Override
+                public void connect() throws IOException {
+                }
+
+                @Override
+                public InputStream getInputStream() throws IOException {
+                    try {
+                        ByteArrayOutputStream os = new ByteArrayOutputStream();
+                        SpringTransformer.transform(createUrl(url.getPath()), os);
+                        os.close();
+                        return new ByteArrayInputStream(os.toByteArray());
+                    } catch (Exception e) {
+                        throw new IOException("Error opening spring xml url", e);
+                    }
+                }
+            };
+        }
+    }
+
+    public class BlueprintURLHandler extends URLStreamHandler {
+        @Override
+        protected URLConnection openConnection(URL u) throws IOException {
+            return new URLConnection(u) {
+                @Override
+                public void connect() throws IOException {
+                }
+
+                @Override
+                public InputStream getInputStream() throws IOException {
+                    try {
+                        ByteArrayOutputStream os = new ByteArrayOutputStream();
+                        BlueprintTransformer.transform(createUrl(url.getPath()), os);
+                        os.close();
+                        return new ByteArrayInputStream(os.toByteArray());
+                    } catch (Exception e) {
+                        throw new IOException("Error opening blueprint xml url", e);
+                    }
+                }
+            };
+        }
+    }
+
+    public class ProfileURLHandler extends URLStreamHandler {
+        @Override
+        protected URLConnection openConnection(URL u) throws IOException {
+            return new URLConnection(u) {
+                @Override
+                public void connect() throws IOException {
+                }
+
+                @Override
+                public InputStream getInputStream() throws IOException {
+                    String path = url.getPath();
+                    byte[] data = profile.getFileConfiguration(path);
+                    if (data == null) {
+                        throw new FileNotFoundException(url.toExternalForm());
+                    }
+                    return new ByteArrayInputStream(data);
+                }
+            };
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e920dde0/profile/src/main/java/org/apache/karaf/profile/assembly/FakeBundleRevision.java
----------------------------------------------------------------------
diff --git a/profile/src/main/java/org/apache/karaf/profile/assembly/FakeBundleRevision.java b/profile/src/main/java/org/apache/karaf/profile/assembly/FakeBundleRevision.java
new file mode 100644
index 0000000..7b797d6
--- /dev/null
+++ b/profile/src/main/java/org/apache/karaf/profile/assembly/FakeBundleRevision.java
@@ -0,0 +1,157 @@
+/*
+ * 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.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.Hashtable;
+import java.util.List;
+
+import org.apache.karaf.features.internal.resolver.ResourceBuilder;
+import org.apache.karaf.features.internal.resolver.ResourceImpl;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.Constants;
+import org.osgi.framework.Version;
+import org.osgi.framework.startlevel.BundleStartLevel;
+import org.osgi.framework.wiring.BundleCapability;
+import org.osgi.framework.wiring.BundleRequirement;
+import org.osgi.framework.wiring.BundleRevision;
+import org.osgi.framework.wiring.BundleWiring;
+
+/**
+ * Fake bundle revision implementation for resolution simulations without OSGi.
+ */
+public class FakeBundleRevision extends ResourceImpl implements BundleRevision, BundleStartLevel {
+
+    private final Bundle bundle;
+    private int startLevel;
+
+    public FakeBundleRevision(final Hashtable<String, String> headers, final String location, final long bundleId) throws BundleException {
+        ResourceBuilder.build(this, location, headers);
+        this.bundle = (Bundle) Proxy.newProxyInstance(
+                getClass().getClassLoader(),
+                new Class[]{Bundle.class},
+                new BundleRevisionInvocationHandler(headers, location, bundleId));
+    }
+
+    @Override
+    public int getStartLevel() {
+        return startLevel;
+    }
+
+    @Override
+    public void setStartLevel(int startLevel) {
+        this.startLevel = startLevel;
+    }
+
+    @Override
+    public boolean isPersistentlyStarted() {
+        return true;
+    }
+
+    @Override
+    public boolean isActivationPolicyUsed() {
+        return false;
+    }
+
+    @Override
+    public String getSymbolicName() {
+        return bundle.getSymbolicName();
+    }
+
+    @Override
+    public Version getVersion() {
+        return bundle.getVersion();
+    }
+
+    @Override
+    public List<BundleCapability> getDeclaredCapabilities(String namespace) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public List<BundleRequirement> getDeclaredRequirements(String namespace) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int getTypes() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public BundleWiring getWiring() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Bundle getBundle() {
+        return bundle;
+    }
+
+    private class BundleRevisionInvocationHandler implements InvocationHandler {
+
+        private final Hashtable<String, String> headers;
+        private final String location;
+        private final long bundleId;
+
+        public BundleRevisionInvocationHandler(Hashtable<String, String> headers, String location, long bundleId) {
+            this.headers = headers;
+            this.location = location;
+            this.bundleId = bundleId;
+        }
+
+        @Override
+        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+            if (method.getName().equals("hashCode")) {
+                return FakeBundleRevision.this.hashCode();
+            } else if (method.getName().equals("equals")) {
+                return proxy == args[0];
+            } else if (method.getName().equals("toString")) {
+                return bundle.getSymbolicName() + "/" + bundle.getVersion();
+            } else if (method.getName().equals("adapt")) {
+                if (args.length == 1 && args[0] == BundleRevision.class) {
+                    return FakeBundleRevision.this;
+                } else if (args.length == 1 && args[0] == BundleStartLevel.class) {
+                    return FakeBundleRevision.this;
+                }
+            } else if (method.getName().equals("getHeaders")) {
+                return headers;
+            } else if (method.getName().equals("getBundleId")) {
+                return bundleId;
+            } else if (method.getName().equals("getLocation")) {
+                return location;
+            } else if (method.getName().equals("getSymbolicName")) {
+                String name = headers.get(Constants.BUNDLE_SYMBOLICNAME);
+                int idx = name.indexOf(';');
+                if (idx > 0) {
+                    name = name.substring(0, idx).trim();
+                }
+                return name;
+            } else if (method.getName().equals("getVersion")) {
+                return new Version(headers.get(Constants.BUNDLE_VERSION));
+            } else if (method.getName().equals("getState")) {
+                return Bundle.ACTIVE;
+            } else if (method.getName().equals("getLastModified")) {
+                return 0l;
+            }
+            return null;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e920dde0/profile/src/main/java/org/apache/karaf/profile/impl/ProfileImpl.java
----------------------------------------------------------------------
diff --git a/profile/src/main/java/org/apache/karaf/profile/impl/ProfileImpl.java b/profile/src/main/java/org/apache/karaf/profile/impl/ProfileImpl.java
index d2111fd..48b064e 100644
--- a/profile/src/main/java/org/apache/karaf/profile/impl/ProfileImpl.java
+++ b/profile/src/main/java/org/apache/karaf/profile/impl/ProfileImpl.java
@@ -40,7 +40,7 @@ final class ProfileImpl implements Profile {
     private static final Pattern ALLOWED_PROFILE_NAMES_PATTERN = Pattern.compile("^[A-Za-z0-9]+[\\.A-Za-z0-9_-]*$");
 
     private final String profileId;
-    private final Map<String, String> attributes = new HashMap<>();
+    private final Map<String, String> attributes;
     private final List<String> parents = new ArrayList<>();
     private final Map<String, byte[]> fileConfigurations = new HashMap<>();
     private final Map<String, Map<String, String>> configurations = new HashMap<>();
@@ -72,17 +72,8 @@ final class ProfileImpl implements Profile {
             }
         }
 
-        // Attributes are agent configuration with prefix 'attribute.'  
-        Map<String, String> agentConfig = configurations.get(Profile.INTERNAL_PID);
-        if (agentConfig != null) {
-            int prefixLength = Profile.ATTRIBUTE_PREFIX.length();
-            for (Entry<String, String> entry : agentConfig.entrySet()) {
-                String key = entry.getKey();
-                if (key.startsWith(Profile.ATTRIBUTE_PREFIX)) {
-                    attributes.put(key.substring(prefixLength), entry.getValue());
-                }
-            }
-        }
+        // Attributes are agent configuration with prefix 'attribute.'
+        attributes = getPrefixedMap(ATTRIBUTE_PREFIX);
     }
 
     public String getId() {
@@ -95,6 +86,31 @@ final class ProfileImpl implements Profile {
     }
 
     @Override
+    public Map<String, String> getConfig() {
+        return getPrefixedMap(CONFIG_PREFIX);
+    }
+
+    @Override
+    public Map<String, String> getSystem() {
+        return getPrefixedMap(SYSTEM_PREFIX);
+    }
+
+    private Map<String, String> getPrefixedMap(String prefix) {
+        Map<String, String> map = new HashMap<>();
+        Map<String, String> agentConfig = configurations.get(Profile.INTERNAL_PID);
+        if (agentConfig != null) {
+            int prefixLength = prefix.length();
+            for (Entry<String, String> entry : agentConfig.entrySet()) {
+                String key = entry.getKey();
+                if (key.startsWith(prefix)) {
+                    map.put(key.substring(prefixLength), entry.getValue());
+                }
+            }
+        }
+        return map;
+    }
+
+    @Override
     public List<String> getLibraries() {
         return getContainerConfigList(ConfigListType.LIBRARIES);
     }

http://git-wip-us.apache.org/repos/asf/karaf/blob/e920dde0/profile/src/test/java/org/apache/karaf/profile/assembly/BuilderTest.java
----------------------------------------------------------------------
diff --git a/profile/src/test/java/org/apache/karaf/profile/assembly/BuilderTest.java b/profile/src/test/java/org/apache/karaf/profile/assembly/BuilderTest.java
new file mode 100644
index 0000000..dc7fbcd
--- /dev/null
+++ b/profile/src/test/java/org/apache/karaf/profile/assembly/BuilderTest.java
@@ -0,0 +1,68 @@
+/*
+ * 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.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import org.junit.Test;
+
+public class BuilderTest {
+
+    @Test
+    public void testBuilder() throws Exception {
+
+        Path workDir = Paths.get("target/distrib");
+        recursiveDelete(workDir);
+
+        Builder builder = Builder.newInstance()
+                .staticFramework()
+                .profilesUris("jar:mvn:org.apache.karaf.demos.profiles/registry/4.0.0-SNAPSHOT!/")
+                .environment("static")
+                .profiles("karaf",
+                          "example-loanbroker-bank1",
+                          "example-loanbroker-bank2",
+                          "example-loanbroker-bank3",
+                          "example-loanbroker-broker",
+                          "activemq-broker")
+                .homeDirectory(workDir);
+
+        try {
+            builder.generateAssembly();
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw e;
+        }
+    }
+
+    private static void recursiveDelete(Path path) throws IOException {
+        if (Files.exists(path)) {
+            if (Files.isDirectory(path)) {
+                try (DirectoryStream<Path> children = Files.newDirectoryStream(path)) {
+                    for (Path child : children) {
+                        recursiveDelete(child);
+                    }
+                }
+            }
+            Files.delete(path);
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e920dde0/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/features/InstallKarsMojo.java
----------------------------------------------------------------------
diff --git a/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/features/InstallKarsMojo.java b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/features/InstallKarsMojo.java
index e36e0ed..c4b451d 100644
--- a/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/features/InstallKarsMojo.java
+++ b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/features/InstallKarsMojo.java
@@ -18,52 +18,11 @@
  */
 package org.apache.karaf.tooling.features;
 
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
 import java.io.File;
-import java.io.FileOutputStream;
-import java.io.InputStream;
-import java.net.URI;
-import java.net.URL;
-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.Collections;
-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 org.apache.felix.utils.properties.InterpolationHelper;
-import org.apache.felix.utils.properties.Properties;
-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.Config;
-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.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.tooling.url.CustomBundleURLStreamHandlerFactory;
-import org.apache.karaf.tooling.utils.InternalMavenResolver;
+
+import org.apache.karaf.profile.assembly.Builder;
 import org.apache.karaf.tooling.utils.IoUtils;
 import org.apache.karaf.tooling.utils.MojoSupport;
 import org.apache.maven.artifact.Artifact;
@@ -73,7 +32,6 @@ import org.apache.maven.plugins.annotations.LifecyclePhase;
 import org.apache.maven.plugins.annotations.Mojo;
 import org.apache.maven.plugins.annotations.Parameter;
 import org.apache.maven.plugins.annotations.ResolutionScope;
-import org.ops4j.pax.url.mvn.MavenResolver;
 
 /**
  * Installs kar dependencies into a server-under-construction in target/assembly
@@ -182,16 +140,9 @@ public class InstallKarsMojo extends MojoSupport {
     // an access layer for available Aether implementation
     protected DependencyHelper dependencyHelper;
 
-    private static final String FEATURES_REPOSITORIES = "featuresRepositories";
-    private static final String FEATURES_BOOT = "featuresBoot";
-
     @Override
     public void execute() throws MojoExecutionException, MojoFailureException {
-
         this.dependencyHelper = DependencyHelperFactory.createDependencyHelper(this.container, this.project, this.mavenSession, getLog());
-        MavenResolver resolver = new InternalMavenResolver(dependencyHelper, getLog());
-        CustomBundleURLStreamHandlerFactory.install(resolver);
-
         try {
             doExecute();
         }
@@ -201,20 +152,9 @@ public class InstallKarsMojo extends MojoSupport {
         catch (Exception e) {
             throw new MojoExecutionException("Unable to build assembly", e);
         }
-        finally {
-            CustomBundleURLStreamHandlerFactory.uninstall();
-        }
     }
 
     protected void doExecute() throws Exception {
-        this.dependencyHelper = DependencyHelperFactory.createDependencyHelper(this.container, this.project, this.mavenSession, getLog());
-
-        // creating system directory
-        getLog().info("Creating system directory");
-        systemDirectory.mkdirs();
-
-        IoUtils.deleteRecursive(new File(systemDirectory, "generated"));
-
         startupRepositories = nonNullList(startupRepositories);
         bootRepositories = nonNullList(bootRepositories);
         installedRepositories = nonNullList(installedRepositories);
@@ -241,576 +181,97 @@ public class InstallKarsMojo extends MojoSupport {
             installedRepositories.addAll(featureRepositories);
         }
 
-        // 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"));
+        Builder builder = Builder.newInstance();
+
+        // creating system directory
+        getLog().info("Creating work directory");
+        builder.homeDirectory(workDirectory.toPath());
+        IoUtils.deleteRecursive(workDirectory);
+        workDirectory.mkdirs();
 
+        List<String> startupKars = new ArrayList<>();
+        List<String> bootKars = new ArrayList<>();
+        List<String> installedKars = new ArrayList<>();
 
         // Loading kars and features repositories
         getLog().info("Loading kar and features repositories dependencies");
         for (Artifact artifact : project.getDependencyArtifacts()) {
+            Builder.Stage stage;
+            switch (artifact.getScope()) {
+            case "compile":
+                stage = Builder.Stage.Startup;
+                break;
+            case "runtime":
+                stage = Builder.Stage.Boot;
+                break;
+            case "provided":
+                stage = Builder.Stage.Installed;
+                break;
+            default:
+                continue;
+            }
             if ("kar".equals(artifact.getType())) {
-                File karFile = artifact.getFile();
-                getLog().info("Extracting " + artifact.toString() + " kar");
-                try {
-                    Kar kar = new Kar(karFile.toURI());
-                    kar.extract(systemDirectory, workDirectory);
-                    for (URI repositoryUri : kar.getFeatureRepos()) {
-                        switch (artifact.getScope()) {
-                        case "compile":
-                            startupRepositories.add(repositoryUri.getPath());
-                            break;
-                        case "runtime":
-                            bootRepositories.add(repositoryUri.getPath());
-                            break;
-                        case "provided":
-                            installedRepositories.add(repositoryUri.getPath());
-                            break;
-                        }
-                    }
-                } catch (Exception e) {
-                    throw new RuntimeException("Can not install " + artifact.toString() + " kar", e);
+                String uri = dependencyHelper.artifactToMvn(artifact);
+                switch (stage) {
+                case Startup:   startupKars.add(uri); break;
+                case Boot:      bootKars.add(uri); break;
+                case Installed: installedKars.add(uri); break;
                 }
             } else if ("features".equals(artifact.getClassifier())) {
-                String repositoryUri = dependencyHelper.artifactToMvn(artifact);
-                switch (artifact.getScope()) {
-                case "compile":
-                    startupRepositories.add(repositoryUri);
-                    break;
-                case "runtime":
-                    bootRepositories.add(repositoryUri);
-                    break;
-                case "provided":
-                    installedRepositories.add(repositoryUri);
-                    break;
+                String uri = dependencyHelper.artifactToMvn(artifact);
+                switch (stage) {
+                case Startup:   startupRepositories.add(uri); break;
+                case Boot:      bootRepositories.add(uri); break;
+                case Installed: installedRepositories.add(uri); break;
                 }
             } else if ("jar".equals(artifact.getType()) || "bundle".equals(artifact.getType())) {
-                String bundleUri = dependencyHelper.artifactToMvn(artifact);
-                switch (artifact.getScope()) {
-                case "compile":
-                    startupBundles.add(bundleUri);
-                    break;
-                case "runtime":
-                    bootBundles.add(bundleUri);
-                    break;
-                case "provided":
-                    installedBundles.add(bundleUri);
-                    break;
+                String uri = dependencyHelper.artifactToMvn(artifact);
+                switch (stage) {
+                case Startup:   startupBundles.add(uri); break;
+                case Boot:      bootBundles.add(uri); break;
+                case Installed: installedBundles.add(uri); break;
                 }
             }
         }
 
-        // Load profiles
-        Map<String, Profile> allProfiles;
+        builder.use24SyntaxForStartup(use24SyntaxForStartup)
+               .useReferenceUrls(useReferenceUrls)
+               .defaultAddAll(installAllFeaturesByDefault)
+               .ignoreDependencyFlag(ignoreDependencyFlag);
         if (profilesUri != null) {
-            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>(), InstallKarsMojo.class.getClassLoader());
-                profilePath = fs.provider().getPath(profileURI);
-            }
-            allProfiles = Profiles.loadProfiles(profilePath);
-        } else {
-            allProfiles = new HashMap<>();
-        }
-        // Generate profiles
-        Profile startupProfile = generateProfile(allProfiles, startupProfiles, startupRepositories, startupFeatures, startupBundles);
-        Profile bootProfile = generateProfile(allProfiles, bootProfiles, bootRepositories, bootFeatures, bootBundles);
-        Profile installedProfile = generateProfile(allProfiles, installedProfiles, installedRepositories, installedFeatures, installedBundles);
-
-        //
-        // 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);
-        Profile overallEffective = Profiles.getEffective(overallOverlay, false);
-
-        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 all configuration files
-        //
-        for (Map.Entry<String, byte[]> config : overallEffective.getFileConfigurations().entrySet()) {
-            Path configFile = workDirectory.toPath().resolve("etc/" + config.getKey());
-            Files.createDirectories(configFile.getParent());
-            Files.write(configFile, config.getValue());
-        }
-
-        //
-        // Compute startup
-        //
-        Profile startupOverlay = Profiles.getOverlay(startupProfile, allProfiles);
-        Profile startupEffective = Profiles.getEffective(startupOverlay, false);
-        // Load startup repositories
-        Map<String, Features> startupRepositories = loadRepositories(startupEffective.getRepositories());
-        // Compute startup feature dependencies
-        Set<Feature> allStartupFeatures = new HashSet<>();
-        for (Features repo : startupRepositories.values()) {
-            allStartupFeatures.addAll(repo.getFeature());
-        }
-        Set<Feature> startupFeatures = new LinkedHashSet<>();
-        if (startupEffective.getFeatures().isEmpty() && installAllFeaturesByDefault) {
-            startupFeatures.addAll(allStartupFeatures);
-        } else {
-            for (String feature : startupEffective.getFeatures()) {
-                addFeatures(startupFeatures, allStartupFeatures, feature);
-            }
-        }
-        // Compute all bundles
-        Map<String, Integer> allStartupBundles = new LinkedHashMap<>();
-        for (Feature feature : startupFeatures) {
-            for (Bundle bundleInfo : feature.getBundle()) {
-                String bundleLocation = bundleInfo.getLocation().trim();
-                int bundleStartLevel = bundleInfo.getStartLevel() == 0 ? defaultStartLevel : bundleInfo.getStartLevel();
-                if (allStartupBundles.containsKey(bundleLocation)) {
-                    bundleStartLevel = Math.min(bundleStartLevel, allStartupBundles.get(bundleLocation));
-                }
-                allStartupBundles.put(bundleLocation, bundleStartLevel);
-            }
-            // Install config
-            for (Config config : feature.getConfig()) {
-                installConfig(config);
-            }
-            for (Conditional cond : feature.getConditional()) {
-                boolean doInstall = true;
-                for (Dependency dep : cond.getFeature()) {
-                    if (!startupFeatures.contains(dep.getName())) {
-                        doInstall = false;
-                        break;
-                    }
-                }
-                if (doInstall) {
-                    for (Bundle bundleInfo : cond.getBundle()) {
-                        String bundleLocation = bundleInfo.getLocation().trim();
-                        int bundleStartLevel = bundleInfo.getStartLevel() == 0 ? defaultStartLevel : bundleInfo.getStartLevel();
-                        if (allStartupBundles.containsKey(bundleLocation)) {
-                            bundleStartLevel = Math.min(bundleStartLevel, allStartupBundles.get(bundleLocation));
-                        }
-                        allStartupBundles.put(bundleLocation, bundleStartLevel);
-                    }
-                }
-                for (Config config : cond.getConfig()) {
-                    installConfig(config);
-                }
-            }
-            // Install config files
-            for (ConfigFile configFile : feature.getConfigfile()) {
-                try (InputStream is = new URL(configFile.getLocation().trim()).openStream()) {
-                    String path = configFile.getFinalname();
-                    if (path.startsWith("/")) {
-                        path = path.substring(1);
-                    }
-                    Path output = workDirectory.toPath().resolve(path);
-                    Files.copy(is, output, StandardCopyOption.REPLACE_EXISTING);  // TODO: be smarter about overwrites
-                }
-            }
-            for (Conditional cond : feature.getConditional()) {
-                for (ConfigFile configFile : cond.getConfigfile()) {
-                    try (InputStream is = new URL(configFile.getLocation().trim()).openStream()) {
-                        Path output = workDirectory.toPath().resolve(configFile.getFinalname());
-                        Files.copy(is, output, StandardCopyOption.REPLACE_EXISTING); // TODO: be smarter about overwrites
-                    }
-                }
-            }
-        }
-        for (String bundleLocation : startupEffective.getBundles()) {
-            int bundleStartLevel = defaultStartLevel;
-            if (allStartupBundles.containsKey(bundleLocation)) {
-                bundleStartLevel = Math.min(bundleStartLevel, allStartupBundles.get(bundleLocation));
-            }
-            allStartupBundles.put(bundleLocation, bundleStartLevel);
-        }
-        // Load startup.properties
-        startupPropertiesFile.getParentFile().mkdirs();
-        Properties startupProperties = new Properties(startupPropertiesFile);
-        if (!startupPropertiesFile.exists()) {
-            startupProperties.setHeader(Collections.singletonList("# Bundles to be started on startup, with startlevel"));
-        }
-        // Install bundles and update startup.properties
-        Map<Integer, Set<String>> invertedStartupBundles = MapUtils.invert(allStartupBundles);
-        for (Map.Entry<Integer, Set<String>> entry : invertedStartupBundles.entrySet()) {
-            String startLevel = Integer.toString(entry.getKey());
-            for (String location : new TreeSet<>(entry.getValue())) {
-                location = installStartupArtifact(location, useReferenceUrls || use24SyntaxForStartup);
-                if (location.startsWith("file:") && useReferenceUrls) {
-                    location = "reference:" + location;
-                }
-                if (location.startsWith("file:") && use24SyntaxForStartup) {
-                    location = location.substring("file:".length());
-                }
-                startupProperties.put(location, startLevel);
-            }
-        }
-        // generate the startup.properties file
-        startupProperties.save();
-
-
-        //
-        // Handle boot profiles
-        //
-        Profile bootOverlay = Profiles.getOverlay(bootProfile, allProfiles);
-        Profile bootEffective = Profiles.getEffective(bootOverlay, false);
-        // Load startup repositories
-        Map<String, Features> bootRepositories = loadRepositories(bootEffective.getRepositories());
-        // Compute startup feature dependencies
-        Set<Feature> allBootFeatures = new HashSet<>();
-        for (Features repo : bootRepositories.values()) {
-            allBootFeatures.addAll(repo.getFeature());
-        }
-        // Install all repositories
-        for (String repository : bootRepositories.keySet()) {
-            installArtifact(repository);
-        }
-        // Generate a global feature
-        Map<String, Dependency> generatedDep = new HashMap<>();
-        Feature generated = new Feature();
-        generated.setName(UUID.randomUUID().toString());
-        // Add feature dependencies
-        if (bootEffective.getFeatures().isEmpty()) {
-            if (installAllFeaturesByDefault) {
-                for (Features repo : bootRepositories.values()) {
-                    for (Feature feature : repo.getFeature()) {
-                        Dependency dep = generatedDep.get(feature.getName());
-                        if (dep == null) {
-                            dep = new Dependency();
-                            dep.setName(feature.getName());
-                            generated.getFeature().add(dep);
-                            generatedDep.put(dep.getName(), dep);
-                        }
-                        dep.setDependency(false);
-                    }
-                }
-            }
-        } else {
-            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);
-
-        // 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
-            getLog().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());
-                    }
-                }
-            }
-            for (String location : locations) {
-                installArtifact(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(configFile.getLocation().trim());
-            }
-            for (Conditional cond : feature.getConditional()) {
-                for (ConfigFile configFile : cond.getConfigfile()) {
-                    installArtifact(configFile.getLocation().trim());
-                }
-            }
-        }
-
-        // If there are bundles to install, we can't use the boot features only
-        // so keep the generated feature
-        if (!generated.getBundle().isEmpty()) {
-            File output = new File(workDirectory, "etc/" + rep.getName() + ".xml");
-            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())) {
-                                getLog().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);
-            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);
-            featuresProperties.put(FEATURES_REPOSITORIES, repos);
-            featuresProperties.put(FEATURES_BOOT, boot);
-            featuresProperties.save();
-        }
-
-
-        //
-        // Handle installed profiles
-        //
-        Profile installedOverlay = Profiles.getOverlay(installedProfile, allProfiles);
-        Profile installedEffective = Profiles.getEffective(installedOverlay, false);
-
-        // Load startup repositories
-        Map<String, Features> installedRepositories = loadRepositories(installedEffective.getRepositories());
-        // Install all repositories
-        for (String repository : installedRepositories.keySet()) {
-            installArtifact(repository);
-        }
-        // Compute startup feature dependencies
-        Set<Feature> allInstalledFeatures = new HashSet<>();
-        for (Features repo : installedRepositories.values()) {
-            allInstalledFeatures.addAll(repo.getFeature());
-        }
-        Set<Feature> installedFeatures = new LinkedHashSet<>();
-        if (installedEffective.getFeatures().isEmpty() && installAllFeaturesByDefault) {
-            installedFeatures.addAll(allInstalledFeatures);
-        } else {
-            // 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(bundle.getLocation().trim());
-                }
-            }
-            for (Conditional cond : feature.getConditional()) {
-                for (Bundle bundle : cond.getBundle()) {
-                    if (!ignoreDependencyFlag || !bundle.isDependency()) {
-                        installArtifact(bundle.getLocation().trim());
-                    }
-                }
-            }
-        }
-        for (String location : installedEffective.getBundles()) {
-            installArtifact(location);
-        }
-        // TODO: install config files
-    }
-
-    private Map<String, Features> loadRepositories(List<String> repositories) throws Exception {
-        Map<String, Features> loaded = new HashMap<>();
-        for (String repository : repositories) {
-            doLoadRepository(loaded, repository);
-        }
-        return loaded;
+            builder.profilesUris(profilesUri);
+        }
+        // Startup
+        builder.defaultStage(Builder.Stage.Startup)
+               .kars(toArray(startupKars))
+               .repositories(startupFeatures.isEmpty() && startupProfiles.isEmpty() && installAllFeaturesByDefault, toArray(startupRepositories))
+               .features(toArray(startupFeatures))
+               .bundles(toArray(startupBundles))
+               .profiles(toArray(startupProfiles));
+        // Boot
+        builder.defaultStage(Builder.Stage.Boot)
+                .kars(toArray(bootKars))
+                .repositories(bootFeatures.isEmpty() && bootProfiles.isEmpty() && installAllFeaturesByDefault, toArray(bootRepositories))
+                .features(toArray(bootFeatures))
+                .bundles(toArray(bootBundles))
+                .profiles(toArray(bootProfiles));
+        // Installed
+        builder.defaultStage(Builder.Stage.Installed)
+                .kars(toArray(installedKars))
+                .repositories(installedFeatures.isEmpty() && installedProfiles.isEmpty() && installAllFeaturesByDefault, toArray(installedRepositories))
+                .features(toArray(installedFeatures))
+                .bundles(toArray(installedBundles))
+                .profiles(toArray(installedProfiles));
+
+        builder.generateAssembly();
     }
 
-    private void doLoadRepository(Map<String, Features> loaded, String repository) throws Exception {
-        if (!loaded.containsKey(repository)) {
-            Features featuresModel = JaxbUtil.unmarshal(repository, false);
-            loaded.put(repository, featuresModel);
-            // recursively process the inner repositories
-            for (String innerRepository : featuresModel.getRepository()) {
-                doLoadRepository(loaded, innerRepository);
-            }
-        }
-    }
-
-    private Profile generateProfile(Map<String, Profile> allProfiles, List<String> profiles, List<String> repositories, List<String> features, List<String> bundles) {
-        Profile profile = ProfileBuilder.Factory.create(UUID.randomUUID().toString())
-                .setParents(profiles)
-                .setRepositories(repositories)
-                .setFeatures(features)
-                .setBundles(bundles)
-                .getProfile();
-        allProfiles.put(profile.getId(), profile);
-        return profile;
+    private String[] toArray(List<String> strings) {
+        return strings.toArray(new String[strings.size()]);
     }
 
     private List<String> nonNullList(List<String> list) {
         return list == null ? new ArrayList<String>() : list;
     }
 
-    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 String installStartupArtifact(String location, boolean asFile) throws Exception {
-        getLog().info("== Installing artifact " + location);
-        String url;
-        String path;
-        if (location.startsWith("mvn:")) {
-            url = location;
-            path = dependencyHelper.pathFromMaven(location);
-            if (asFile) {
-                location = "file:" + path ;
-            }
-        } else {
-            url = location.replace("profile:", "file:" + workDirectory.getAbsolutePath() + "/etc/");
-            path = "generated/" + location.replaceAll("[^0-9a-zA-Z.\\-_]+", "_");
-            location = "file:" + path;
-        }
-        File bundleSystemFile = new File(systemDirectory, path);
-        if (!bundleSystemFile.exists()) {
-            bundleSystemFile.getParentFile().mkdirs();
-            try (InputStream is = new URL(url).openStream()) {
-                Files.copy(is, bundleSystemFile.toPath());
-            }
-        }
-        return location;
-    }
-
-    private void installArtifact(String location) throws Exception {
-        getLog().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);
-            }
-            File inputFile = dependencyHelper.resolveById(location, getLog());
-            File targetFile = new File(systemDirectory, dependencyHelper.pathFromMaven(location));
-            copy(inputFile, targetFile);
-            // 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 {
-            getLog().warn("Ignoring artifact " + location);
-        }
-    }
-
-    private void installConfig(Config config) throws Exception {
-        getLog().info("== Installing configuration " + config.getName());
-        Path configFile = workDirectory.toPath().resolve("etc/" + config.getName());
-        if (!Files.exists(configFile)) {
-            Files.write(configFile, config.getValue().getBytes());
-        } else if (config.isAppend()) {
-            // TODO
-        }
-    }
-
 }

http://git-wip-us.apache.org/repos/asf/karaf/blob/e920dde0/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/features/VerifyFeatureResolutionMojo.java
----------------------------------------------------------------------
diff --git a/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/features/VerifyFeatureResolutionMojo.java b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/features/VerifyFeatureResolutionMojo.java
index d196c44..b3cb74a 100644
--- a/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/features/VerifyFeatureResolutionMojo.java
+++ b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/features/VerifyFeatureResolutionMojo.java
@@ -29,14 +29,10 @@ import java.lang.reflect.Method;
 import java.lang.reflect.Proxy;
 import java.net.URI;
 import java.net.URL;
-import java.net.URLConnection;
-import java.net.URLStreamHandler;
-import java.net.URLStreamHandlerFactory;
 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.Enumeration;
 import java.util.HashMap;
@@ -81,9 +77,8 @@ import org.apache.karaf.features.internal.util.MultiException;
 import org.apache.karaf.tooling.url.CustomBundleURLStreamHandlerFactory;
 import org.apache.karaf.tooling.utils.InternalMavenResolver;
 import org.apache.karaf.tooling.utils.MojoSupport;
-import org.apache.karaf.tooling.utils.PropertiesLoader;
+import org.apache.karaf.util.config.PropertiesLoader;
 import org.apache.maven.artifact.Artifact;
-import org.apache.maven.plugin.AbstractMojo;
 import org.apache.maven.plugin.MojoExecutionException;
 import org.apache.maven.plugin.MojoFailureException;
 import org.apache.maven.plugins.annotations.Mojo;
@@ -91,11 +86,6 @@ import org.apache.maven.plugins.annotations.Parameter;
 import org.apache.maven.plugins.annotations.ResolutionScope;
 import org.apache.maven.project.MavenProject;
 import org.ops4j.pax.url.mvn.MavenResolver;
-import org.ops4j.pax.url.mvn.MavenResolvers;
-import org.ops4j.pax.url.mvn.ServiceConstants;
-import org.ops4j.pax.url.mvn.internal.AetherBasedResolver;
-import org.ops4j.pax.url.mvn.internal.Connection;
-import org.ops4j.pax.url.mvn.internal.config.MavenConfigurationImpl;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleException;
 import org.osgi.framework.Constants;
@@ -111,7 +101,6 @@ import org.osgi.resource.Requirement;
 import org.osgi.resource.Resource;
 import org.osgi.resource.Wire;
 import org.osgi.service.resolver.ResolutionException;
-import shaded.org.ops4j.util.property.PropertiesPropertyResolver;
 
 import static java.util.jar.JarFile.MANIFEST_NAME;
 

http://git-wip-us.apache.org/repos/asf/karaf/blob/e920dde0/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/utils/PropertiesLoader.java
----------------------------------------------------------------------
diff --git a/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/utils/PropertiesLoader.java b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/utils/PropertiesLoader.java
deleted file mode 100644
index 6507b83..0000000
--- a/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/utils/PropertiesLoader.java
+++ /dev/null
@@ -1,249 +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.tooling.utils;
-
-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 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
-     */
-    public 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
-     */
-    public 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));
-            }
-        }
-    }
-
-    public 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));
-            }
-        }
-    }
-
-    public 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);
-        }
-    }
-
-    public 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 = 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());
-        }
-    }
-
-    private static String nextLocation(StringTokenizer st) {
-        String retVal = null;
-
-        if (st.countTokens() > 0) {
-            String tokenList = "\" ";
-            StringBuffer tokBuf = new StringBuffer(10);
-            String tok;
-            boolean inQuote = false;
-            boolean tokStarted = false;
-            boolean exit = false;
-            while ((st.hasMoreTokens()) && (!exit)) {
-                tok = st.nextToken(tokenList);
-                if (tok.equals("\"")) {
-                    inQuote = !inQuote;
-                    if (inQuote) {
-                        tokenList = "\"";
-                    } else {
-                        tokenList = "\" ";
-                    }
-
-                } else if (tok.equals(" ")) {
-                    if (tokStarted) {
-                        retVal = tokBuf.toString();
-                        tokStarted = false;
-                        tokBuf = new StringBuffer(10);
-                        exit = true;
-                    }
-                } else {
-                    tokStarted = true;
-                    tokBuf.append(tok.trim());
-                }
-            }
-
-            // Handle case where end of token stream and
-            // still got data
-            if ((!exit) && (tokStarted)) {
-                retVal = tokBuf.toString();
-            }
-        }
-
-        return retVal;
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e920dde0/util/src/main/java/org/apache/karaf/util/config/PropertiesLoader.java
----------------------------------------------------------------------
diff --git a/util/src/main/java/org/apache/karaf/util/config/PropertiesLoader.java b/util/src/main/java/org/apache/karaf/util/config/PropertiesLoader.java
new file mode 100644
index 0000000..609957b
--- /dev/null
+++ b/util/src/main/java/org/apache/karaf/util/config/PropertiesLoader.java
@@ -0,0 +1,249 @@
+/*
+ * 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.util.config;
+
+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 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
+     */
+    public 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
+     */
+    public 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));
+            }
+        }
+    }
+
+    public 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));
+            }
+        }
+    }
+
+    public 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);
+        }
+    }
+
+    public 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 = 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());
+        }
+    }
+
+    private static String nextLocation(StringTokenizer st) {
+        String retVal = null;
+
+        if (st.countTokens() > 0) {
+            String tokenList = "\" ";
+            StringBuffer tokBuf = new StringBuffer(10);
+            String tok;
+            boolean inQuote = false;
+            boolean tokStarted = false;
+            boolean exit = false;
+            while ((st.hasMoreTokens()) && (!exit)) {
+                tok = st.nextToken(tokenList);
+                if (tok.equals("\"")) {
+                    inQuote = !inQuote;
+                    if (inQuote) {
+                        tokenList = "\"";
+                    } else {
+                        tokenList = "\" ";
+                    }
+
+                } else if (tok.equals(" ")) {
+                    if (tokStarted) {
+                        retVal = tokBuf.toString();
+                        tokStarted = false;
+                        tokBuf = new StringBuffer(10);
+                        exit = true;
+                    }
+                } else {
+                    tokStarted = true;
+                    tokBuf.append(tok.trim());
+                }
+            }
+
+            // Handle case where end of token stream and
+            // still got data
+            if ((!exit) && (tokStarted)) {
+                retVal = tokBuf.toString();
+            }
+        }
+
+        return retVal;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e920dde0/util/src/main/java/org/apache/karaf/util/maven/Parser.java
----------------------------------------------------------------------
diff --git a/util/src/main/java/org/apache/karaf/util/maven/Parser.java b/util/src/main/java/org/apache/karaf/util/maven/Parser.java
index 232942e..8f6920f 100644
--- a/util/src/main/java/org/apache/karaf/util/maven/Parser.java
+++ b/util/src/main/java/org/apache/karaf/util/maven/Parser.java
@@ -150,6 +150,19 @@ public class Parser
     }
 
     /**
+     * Returns the artifact path from the given maven uri.
+     * @param uri the maven uri
+     * @return the artifact path
+     * @throws MalformedURLException
+     */
+    public static String pathFromMaven(String uri) throws MalformedURLException {
+        if (!uri.startsWith("mvn:")) {
+            return uri;
+        }
+        return new Parser(uri.substring("mvn:".length())).getArtifactPath();
+    }
+
+    /**
      * Parses the artifact part of the url ( without the repository).
      *
      * @param part url part without protocol and repository.


[2/2] karaf git commit: [KARAF-3546] Provide a programmatic way to build custom karaf distributions outside of maven

Posted by gn...@apache.org.
[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 <gn...@gmail.com>
Authored: Tue Feb 24 14:35:15 2015 +0100
Committer: Guillaume Nodet <gn...@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);
+    }
+}