[KARAF-3404] Add a notion of profiles to karaf
[KARAF-3406] Upgrade to pax-url 2.2
[KARAF-3407] Upgrade to felix utils 1.6.2


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

Branch: refs/heads/master
Commit: 90cb480dc0d941fc39af220938bfacb979c8b7ff
Parents: b10a009
Author: Guillaume Nodet <[email protected]>
Authored: Wed Dec 10 23:55:49 2014 +0100
Committer: Guillaume Nodet <[email protected]>
Committed: Thu Dec 11 00:02:07 2014 +0100

----------------------------------------------------------------------
 .../standard/src/main/feature/feature.xml       |   7 +
 features/core/pom.xml                           |  10 +
 .../internal/download/DownloadManagers.java     |  35 +
 .../internal/download/StreamProvider.java       |   7 +-
 .../download/impl/AbstractDownloadTask.java     |  71 ++
 .../impl/AbstractRetryableDownloadTask.java     |  60 ++
 .../internal/download/impl/DefaultFuture.java   | 358 +++++++
 .../download/impl/DownloadManagerHelper.java    |  67 ++
 .../internal/download/impl/FutureListener.java  |  35 +
 .../download/impl/MavenDownloadManager.java     | 208 +++++
 .../download/impl/MavenDownloadTask.java        |  37 +
 .../download/impl/SimpleDownloadTask.java       | 117 +++
 .../download/simple/SimpleDownloader.java       | 127 ---
 .../karaf/features/internal/osgi/Activator.java |   2 +-
 .../features/internal/region/Subsystem.java     |  33 +-
 .../internal/service/FeaturesServiceImpl.java   |  71 +-
 .../internal/support/TestDownloadManager.java   |  61 +-
 instance/pom.xml                                |   6 +
 .../karaf/instance/command/CreateCommand.java   |   8 +-
 .../karaf/instance/core/InstanceSettings.java   |  16 +-
 .../core/internal/InstanceServiceImpl.java      |  95 +-
 pom.xml                                         |  16 +-
 profile/pom.xml                                 | 150 +++
 .../org/apache/karaf/profile/LockHandle.java    |  29 +
 .../karaf/profile/PlaceholderResolver.java      |  40 +
 .../java/org/apache/karaf/profile/Profile.java  | 130 +++
 .../apache/karaf/profile/ProfileBuilder.java    | 109 +++
 .../apache/karaf/profile/ProfileService.java    | 132 +++
 .../profile/command/ProfileChangeParents.java   |  56 ++
 .../karaf/profile/command/ProfileCopy.java      |  57 ++
 .../karaf/profile/command/ProfileCreate.java    |  53 ++
 .../karaf/profile/command/ProfileDelete.java    |  46 +
 .../karaf/profile/command/ProfileDisplay.java   | 175 ++++
 .../karaf/profile/command/ProfileEdit.java      | 611 ++++++++++++
 .../karaf/profile/command/ProfileList.java      |  63 ++
 .../karaf/profile/command/ProfileRename.java    |  62 ++
 .../command/completers/ProfileCompleter.java    |  45 +
 .../profile/impl/PlaceholderResolvers.java      | 103 ++
 .../karaf/profile/impl/ProfileBuilderImpl.java  | 305 ++++++
 .../apache/karaf/profile/impl/ProfileImpl.java  | 246 +++++
 .../karaf/profile/impl/ProfileServiceImpl.java  | 246 +++++
 .../org/apache/karaf/profile/impl/Profiles.java | 411 ++++++++
 .../org/apache/karaf/profile/impl/Utils.java    | 103 ++
 .../karaf/profile/impl/osgi/Activator.java      |  44 +
 .../apache/karaf/profile/impl/ProfilesTest.java |  90 ++
 tooling/karaf-maven-plugin/pom.xml              |   4 +
 .../karaf/tooling/features/InstallKarsMojo.java | 933 +++++++++++--------
 .../features/VerifyFeatureResolutionMojo.java   |  60 +-
 .../CustomBundleURLStreamHandlerFactory.java    |  60 +-
 .../tooling/utils/InternalMavenResolver.java    |  70 ++
 .../org/apache/karaf/tooling/utils/IoUtils.java |  38 +
 .../tracker/GenerateServiceMetadata.java        |   2 -
 .../karaf/util/tracker/BaseActivator.java       |   3 +
 53 files changed, 5326 insertions(+), 597 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/karaf/blob/90cb480d/assemblies/features/standard/src/main/feature/feature.xml
----------------------------------------------------------------------
diff --git a/assemblies/features/standard/src/main/feature/feature.xml 
b/assemblies/features/standard/src/main/feature/feature.xml
index c94f496..f05172f 100644
--- a/assemblies/features/standard/src/main/feature/feature.xml
+++ b/assemblies/features/standard/src/main/feature/feature.xml
@@ -546,4 +546,11 @@
         <bundle start="true" 
start-level="5">mvn:org.ops4j.pax.url/pax-url-wrap/${pax.url.version}/jar/uber</bundle>
     </feature>
 
+    <feature name="profile" description="Profiles support" 
version="${project.version}">
+        <config name="org.apache.karaf.profile">
+            profilesDirectory = ${karaf.home}/profiles
+        </config>
+        
<bundle>mvn:org.apache.karaf.profile/org.apache.karaf.profile.core/${project.version}</bundle>
+    </feature>
+
 </features>

http://git-wip-us.apache.org/repos/asf/karaf/blob/90cb480d/features/core/pom.xml
----------------------------------------------------------------------
diff --git a/features/core/pom.xml b/features/core/pom.xml
index 2a78de9..fc6ccc9 100644
--- a/features/core/pom.xml
+++ b/features/core/pom.xml
@@ -61,6 +61,11 @@
             <scope>provided</scope>
         </dependency>
         <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>jcl-over-slf4j</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
             <groupId>org.apache.karaf</groupId>
             <artifactId>org.apache.karaf.util</artifactId>
             <scope>provided</scope>
@@ -78,6 +83,11 @@
         </dependency>
 
         <dependency>
+            <groupId>org.ops4j.pax.url</groupId>
+            <artifactId>pax-url-aether</artifactId>
+        </dependency>
+
+        <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-jdk14</artifactId>
             <scope>test</scope>

http://git-wip-us.apache.org/repos/asf/karaf/blob/90cb480d/features/core/src/main/java/org/apache/karaf/features/internal/download/DownloadManagers.java
----------------------------------------------------------------------
diff --git 
a/features/core/src/main/java/org/apache/karaf/features/internal/download/DownloadManagers.java
 
b/features/core/src/main/java/org/apache/karaf/features/internal/download/DownloadManagers.java
new file mode 100644
index 0000000..3808bd3
--- /dev/null
+++ 
b/features/core/src/main/java/org/apache/karaf/features/internal/download/DownloadManagers.java
@@ -0,0 +1,35 @@
+/*
+ * 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.features.internal.download;
+
+import java.util.concurrent.ScheduledExecutorService;
+
+import org.apache.karaf.features.internal.download.impl.MavenDownloadManager;
+import org.ops4j.pax.url.mvn.MavenResolver;
+
+public final class DownloadManagers {
+
+    private DownloadManagers() { }
+
+    /**
+     * Creates a DownloadManager
+     */
+    public static DownloadManager createDownloadManager(MavenResolver 
resolver, ScheduledExecutorService executorService) {
+        return new MavenDownloadManager(resolver, executorService);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/90cb480d/features/core/src/main/java/org/apache/karaf/features/internal/download/StreamProvider.java
----------------------------------------------------------------------
diff --git 
a/features/core/src/main/java/org/apache/karaf/features/internal/download/StreamProvider.java
 
b/features/core/src/main/java/org/apache/karaf/features/internal/download/StreamProvider.java
index c25fa43..9f1f686 100644
--- 
a/features/core/src/main/java/org/apache/karaf/features/internal/download/StreamProvider.java
+++ 
b/features/core/src/main/java/org/apache/karaf/features/internal/download/StreamProvider.java
@@ -16,14 +16,17 @@
  */
 package org.apache.karaf.features.internal.download;
 
+import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.Map;
 
 public interface StreamProvider {
 
-    InputStream open() throws IOException;
+    String getUrl();
+
+    File getFile() throws IOException;
 
-    Map<String, String> getMetadata() throws IOException;
+    InputStream open() throws IOException;
 
 }

http://git-wip-us.apache.org/repos/asf/karaf/blob/90cb480d/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/AbstractDownloadTask.java
----------------------------------------------------------------------
diff --git 
a/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/AbstractDownloadTask.java
 
b/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/AbstractDownloadTask.java
new file mode 100644
index 0000000..da00bdf
--- /dev/null
+++ 
b/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/AbstractDownloadTask.java
@@ -0,0 +1,71 @@
+/*
+ * 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.features.internal.download.impl;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.concurrent.ScheduledExecutorService;
+
+import org.apache.karaf.features.internal.download.StreamProvider;
+
+public abstract class AbstractDownloadTask extends 
DefaultFuture<AbstractDownloadTask> implements Runnable, StreamProvider {
+
+    protected final String url;
+    protected ScheduledExecutorService executorService;
+
+    public AbstractDownloadTask(ScheduledExecutorService executorService, 
String url) {
+        this.executorService = executorService;
+        this.url = url;
+    }
+
+    public String getUrl() {
+        return url;
+    }
+
+    public File getFile() throws IOException {
+        Object v = getValue();
+        if (v instanceof File) {
+            return (File) v;
+        } else if (v instanceof IOException) {
+            throw (IOException) v;
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public InputStream open() throws IOException {
+        return new FileInputStream(getFile());
+    }
+
+    public void setFile(File file) {
+        if (file == null) {
+            throw new NullPointerException("file");
+        }
+        setValue(file);
+    }
+
+    public void setException(IOException exception) {
+        if (exception == null) {
+            throw new NullPointerException("exception");
+        }
+        setValue(exception);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/90cb480d/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/AbstractRetryableDownloadTask.java
----------------------------------------------------------------------
diff --git 
a/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/AbstractRetryableDownloadTask.java
 
b/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/AbstractRetryableDownloadTask.java
new file mode 100644
index 0000000..f315e4b
--- /dev/null
+++ 
b/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/AbstractRetryableDownloadTask.java
@@ -0,0 +1,60 @@
+/*
+ * 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.features.internal.download.impl;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public abstract class AbstractRetryableDownloadTask extends 
AbstractDownloadTask {
+
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(AbstractRetryableDownloadTask.class);
+
+    private long scheduleDelay = 250;
+    private int scheduleNbRun = 0;
+
+    public AbstractRetryableDownloadTask(ScheduledExecutorService 
executorService, String url) {
+        super(executorService, url);
+    }
+
+    public void run() {
+        try {
+            try {
+                File file = download();
+                setFile(file);
+            } catch (IOException e) {
+                if (++scheduleNbRun < 5) {
+                    long delay = (long)(scheduleDelay * 3 / 2 + Math.random() 
* scheduleDelay / 2);
+                    LOGGER.debug("Error downloading " + url + ": " + 
e.getMessage() + ". Retrying in approx " + delay + " ms.");
+                    executorService.schedule(this, scheduleDelay, 
TimeUnit.MILLISECONDS);
+                    scheduleDelay *= 2;
+                } else {
+                    setException(new IOException("Error downloading " + url, 
e));
+                }
+            }
+        } catch (Throwable e) {
+            setException(new IOException("Error downloading " + url, e));
+        }
+    }
+
+    protected abstract File download() throws Exception;
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/90cb480d/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/DefaultFuture.java
----------------------------------------------------------------------
diff --git 
a/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/DefaultFuture.java
 
b/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/DefaultFuture.java
new file mode 100644
index 0000000..3460496
--- /dev/null
+++ 
b/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/DefaultFuture.java
@@ -0,0 +1,358 @@
+/*
+ * 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.features.internal.download.impl;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A simple future
+ */
+public class DefaultFuture<T extends DefaultFuture> {
+
+    /**
+     * A default value to indicate the future has been canceled
+     */
+    private static final Object CANCELED = new Object();
+
+    /**
+     * A number of seconds to wait between two deadlock controls ( 5 seconds )
+     */
+    private static final long DEAD_LOCK_CHECK_INTERVAL = 5000L;
+
+    /**
+     * A lock used by the wait() method
+     */
+    private final Object lock;
+    private FutureListener<T> firstListener;
+    private List<FutureListener<T>> otherListeners;
+    private Object result;
+    private boolean ready;
+    private int waiters;
+
+
+    public DefaultFuture() {
+        this(null);
+    }
+
+    /**
+     * Creates a new instance.
+     */
+    public DefaultFuture(Object lock) {
+        this.lock = lock != null ? lock : this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @SuppressWarnings("unchecked")
+    public T await() throws InterruptedException {
+        synchronized (lock) {
+            while (!ready) {
+                waiters++;
+                try {
+                    // Wait for a notify, or if no notify is called,
+                    // assume that we have a deadlock and exit the
+                    // loop to check for a potential deadlock.
+                    lock.wait(DEAD_LOCK_CHECK_INTERVAL);
+                } finally {
+                    waiters--;
+                    if (!ready) {
+                        checkDeadLock();
+                    }
+                }
+            }
+        }
+        return (T) this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean await(long timeout, TimeUnit unit) throws 
InterruptedException {
+        return await(unit.toMillis(timeout));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean await(long timeoutMillis) throws InterruptedException {
+        return await0(timeoutMillis, true);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @SuppressWarnings("unchecked")
+    public T awaitUninterruptibly() {
+        try {
+            await0(Long.MAX_VALUE, false);
+        } catch (InterruptedException ie) {
+            // Do nothing : this catch is just mandatory by contract
+        }
+
+        return (T) this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean awaitUninterruptibly(long timeout, TimeUnit unit) {
+        return awaitUninterruptibly(unit.toMillis(timeout));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean awaitUninterruptibly(long timeoutMillis) {
+        try {
+            return await0(timeoutMillis, false);
+        } catch (InterruptedException e) {
+            throw new InternalError();
+        }
+    }
+
+    /**
+     * Wait for the Future to be ready. If the requested delay is 0 or
+     * negative, this method immediately returns the value of the
+     * 'ready' flag.
+     * Every 5 second, the wait will be suspended to be able to check if
+     * there is a deadlock or not.
+     *
+     * @param timeoutMillis The delay we will wait for the Future to be ready
+     * @param interruptable Tells if the wait can be interrupted or not
+     * @return <code>true</code> if the Future is ready
+     * @throws InterruptedException If the thread has been interrupted
+     *                              when it's not allowed.
+     */
+    private boolean await0(long timeoutMillis, boolean interruptable) throws 
InterruptedException {
+        long endTime = System.currentTimeMillis() + timeoutMillis;
+
+        synchronized (lock) {
+            if (ready) {
+                return true;
+            } else if (timeoutMillis <= 0) {
+                return false;
+            }
+
+            waiters++;
+            try {
+                for (; ;) {
+                    try {
+                        long timeOut = Math.min(timeoutMillis, 
DEAD_LOCK_CHECK_INTERVAL);
+                        lock.wait(timeOut);
+                    } catch (InterruptedException e) {
+                        if (interruptable) {
+                            throw e;
+                        }
+                    }
+
+                    if (ready) {
+                        return true;
+                    } else if (endTime < System.currentTimeMillis()) {
+                        return false;
+                    }
+                }
+            } finally {
+                waiters--;
+                if (!ready) {
+                    checkDeadLock();
+                }
+            }
+        }
+    }
+
+
+    /**
+     * TODO checkDeadLock.
+     */
+    private void checkDeadLock() {
+//        // Only read / write / connect / write future can cause dead lock.
+//        if (!(this instanceof CloseFuture || this instanceof WriteFuture ||
+//              this instanceof ReadFuture || this instanceof ConnectFuture)) {
+//            return;
+//        }
+//
+//        // Get the current thread stackTrace.
+//        // Using Thread.currentThread().getStackTrace() is the best solution,
+//        // even if slightly less efficient than doing a new 
Exception().getStackTrace(),
+//        // as internally, it does exactly the same thing. The advantage of 
using
+//        // this solution is that we may benefit some improvement with some
+//        // future versions of Java.
+//        StackTraceElement[] stackTrace = 
Thread.currentThread().getStackTrace();
+//
+//        // Simple and quick check.
+//        for (StackTraceElement s: stackTrace) {
+//            if 
(AbstractPollingIoProcessor.class.getName().equals(s.getClassName())) {
+//                IllegalStateException e = new IllegalStateException( "t" );
+//                e.getStackTrace();
+//                throw new IllegalStateException(
+//                    "DEAD LOCK: " + IoFuture.class.getSimpleName() +
+//                    ".await() was invoked from an I/O processor thread.  " +
+//                    "Please use " + IoFutureListener.class.getSimpleName() +
+//                    " or configure a proper thread model alternatively.");
+//            }
+//        }
+//
+//        // And then more precisely.
+//        for (StackTraceElement s: stackTrace) {
+//            try {
+//                Class<?> cls = 
DefaultSshFuture.class.getClassLoader().loadClass(s.getClassName());
+//                if (IoProcessor.class.isAssignableFrom(cls)) {
+//                    throw new IllegalStateException(
+//                        "DEAD LOCK: " + IoFuture.class.getSimpleName() +
+//                        ".await() was invoked from an I/O processor thread.  
" +
+//                        "Please use " + 
IoFutureListener.class.getSimpleName() +
+//                        " or configure a proper thread model 
alternatively.");
+//                }
+//            } catch (Exception cnfe) {
+//                // Ignore
+//            }
+//        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isDone() {
+        synchronized (lock) {
+            return ready;
+        }
+    }
+
+    /**
+     * Sets the result of the asynchronous operation, and mark it as finished.
+     */
+    public void setValue(Object newValue) {
+        synchronized (lock) {
+            // Allow only once.
+            if (ready) {
+                return;
+            }
+
+            result = newValue;
+            ready = true;
+            if (waiters > 0) {
+                lock.notifyAll();
+            }
+        }
+
+        notifyListeners();
+    }
+
+    /**
+     * Returns the result of the asynchronous operation.
+     */
+    protected Object getValue() {
+        synchronized (lock) {
+            return result;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @SuppressWarnings("unchecked")
+    public T addListener(FutureListener<T> listener) {
+        if (listener == null) {
+            throw new NullPointerException("listener");
+        }
+
+        boolean notifyNow = false;
+        synchronized (lock) {
+            if (ready) {
+                notifyNow = true;
+            } else {
+                if (firstListener == null) {
+                    firstListener = listener;
+                } else {
+                    if (otherListeners == null) {
+                        otherListeners = new ArrayList<>(1);
+                    }
+                    otherListeners.add(listener);
+                }
+            }
+        }
+
+        if (notifyNow) {
+            notifyListener(listener);
+        }
+        return (T) this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @SuppressWarnings("unchecked")
+    public T removeListener(FutureListener<T> listener) {
+        if (listener == null) {
+            throw new NullPointerException("listener");
+        }
+
+        synchronized (lock) {
+            if (!ready) {
+                if (listener == firstListener) {
+                    if (otherListeners != null && !otherListeners.isEmpty()) {
+                        firstListener = otherListeners.remove(0);
+                    } else {
+                        firstListener = null;
+                    }
+                } else if (otherListeners != null) {
+                    otherListeners.remove(listener);
+                }
+            }
+        }
+
+        return (T) this;
+    }
+
+    private void notifyListeners() {
+        // There won't be any visibility problem or concurrent modification
+        // because 'ready' flag will be checked against both addListener and
+        // removeListener calls.
+        if (firstListener != null) {
+            notifyListener(firstListener);
+            firstListener = null;
+
+            if (otherListeners != null) {
+                for (FutureListener<T> l : otherListeners) {
+                    notifyListener(l);
+                }
+                otherListeners = null;
+            }
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private void notifyListener(FutureListener<T> l) {
+        try {
+            l.operationComplete((T) this);
+        } catch (Throwable t) {
+            // TODO
+            t.printStackTrace();
+        }
+    }
+
+    public boolean isCanceled() {
+        return getValue() == CANCELED;
+    }
+
+    public void cancel() {
+        setValue(CANCELED);
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/90cb480d/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/DownloadManagerHelper.java
----------------------------------------------------------------------
diff --git 
a/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/DownloadManagerHelper.java
 
b/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/DownloadManagerHelper.java
new file mode 100644
index 0000000..531625f
--- /dev/null
+++ 
b/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/DownloadManagerHelper.java
@@ -0,0 +1,67 @@
+/*
+ * 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.features.internal.download.impl;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public final class DownloadManagerHelper {
+
+    private static final Pattern IGNORED_PROTOCOL_PATTERN = 
Pattern.compile("^(jar|war|war-i|warref|webbundle|wrap|spring|blueprint):.*$");
+
+    private DownloadManagerHelper() {
+        //Utility Class
+    }
+
+    /**
+     * Strips download urls from wrapper protocols.
+     */
+    public static String stripUrl(String url) {
+        String strippedUrl = url;
+        Matcher matcher = IGNORED_PROTOCOL_PATTERN.matcher(strippedUrl);
+        while (matcher.matches()) {
+            String protocol = matcher.group(1);
+            strippedUrl = strippedUrl.substring(protocol.length() + 1);
+            matcher = IGNORED_PROTOCOL_PATTERN.matcher(strippedUrl);
+        }
+        if (strippedUrl.contains("?")) {
+            strippedUrl = strippedUrl.substring(0, strippedUrl.indexOf('?'));
+        }
+        if (strippedUrl.contains("$")) {
+            strippedUrl = strippedUrl.substring(0, strippedUrl.indexOf('$'));
+        }
+        if (strippedUrl.contains("#")) {
+            strippedUrl = strippedUrl.substring(0, strippedUrl.indexOf('#'));
+        }
+
+        return strippedUrl;
+    }
+
+    public static String stripInlinedMavenRepositoryUrl(String url) {
+        if (url.startsWith("mvn:") && url.contains("!")) {
+            return url.substring(4, url.indexOf('!'));
+        }
+        return null;
+    }
+
+    public static String removeInlinedMavenRepositoryUrl(String url) {
+        if (url.startsWith("mvn:") && url.contains("!")) {
+            return "mvn:" + url.substring(url.indexOf('!') + 1);
+        }
+        return url;
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/90cb480d/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/FutureListener.java
----------------------------------------------------------------------
diff --git 
a/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/FutureListener.java
 
b/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/FutureListener.java
new file mode 100644
index 0000000..1a7ae99
--- /dev/null
+++ 
b/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/FutureListener.java
@@ -0,0 +1,35 @@
+/*
+ * 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.features.internal.download.impl;
+
+/**
+ * Something interested in being notified when the completion
+ * of an asynchronous download operation : {@link DefaultFuture}.
+ *
+ */
+public interface FutureListener<T extends DefaultFuture> {
+
+    /**
+     * Invoked when the operation associated with the {@link DefaultFuture}
+     * has been completed even if you add the listener after the completion.
+     *
+     * @param future The source {@link DefaultFuture} which called this
+     *               callback.
+     */
+    void operationComplete(T future);
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/90cb480d/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
new file mode 100644
index 0000000..82f3eea
--- /dev/null
+++ 
b/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/MavenDownloadManager.java
@@ -0,0 +1,208 @@
+/*
+ * 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.features.internal.download.impl;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ScheduledExecutorService;
+
+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.util.MultiException;
+import org.ops4j.pax.url.mvn.MavenResolver;
+
+public class MavenDownloadManager implements DownloadManager {
+
+    private final MavenResolver mavenResolver;
+
+    private final ScheduledExecutorService executorService;
+
+    private File tmpPath;
+
+    private final Map<String, AbstractDownloadTask> downloaded = new 
HashMap<>();
+
+    private final Map<String, AbstractDownloadTask> downloading = new 
HashMap<>();
+
+    private final Object lock = new Object();
+
+    private volatile int allPending = 0;
+
+    public MavenDownloadManager(MavenResolver mavenResolver, 
ScheduledExecutorService executorService) {
+        this.mavenResolver = mavenResolver;
+        this.executorService = executorService;
+
+        String karafRoot = System.getProperty("karaf.home", "karaf");
+        String karafData = System.getProperty("karaf.data", karafRoot + 
"/data");
+        this.tmpPath = new File(karafData, "tmp");
+    }
+
+    public int getPending() {
+        return allPending;
+    }
+
+    @Override
+    public Downloader createDownloader() {
+        return new MavenDownloader();
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public Map<String, StreamProvider> getProviders() {
+        return (Map) Collections.synchronizedMap(downloaded);
+    }
+
+    class MavenDownloader implements Downloader {
+
+        private volatile int pending = 0;
+        private final MultiException exception = new MultiException("Error");
+
+        public int pending() {
+            return pending;
+        }
+
+        @Override
+        public void await() throws InterruptedException, MultiException {
+            synchronized (lock) {
+                while (pending != 0) {
+                    lock.wait();
+                }
+            }
+            exception.throwIfExceptions();
+        }
+
+        @Override
+        public void download(final String location, final DownloadCallback 
downloadCallback) throws MalformedURLException {
+            AbstractDownloadTask task;
+            synchronized (lock) {
+                task = downloaded.get(location);
+                if (task == null) {
+                    task = downloading.get(location);
+                }
+            }
+            if (task == null) {
+                task = createDownloadTask(location);
+            }
+            synchronized (lock) {
+                AbstractDownloadTask prev = downloaded.get(location);
+                if (prev == null) {
+                    prev = downloading.get(location);
+                }
+                if (prev == null) {
+                    downloading.put(location, task);
+                    executorService.execute(task);
+                } else {
+                    task = prev;
+                }
+                pending++;
+                allPending++;
+            }
+            final AbstractDownloadTask downloadTask = task;
+            task.addListener(new FutureListener<AbstractDownloadTask>() {
+                @Override
+                public void operationComplete(AbstractDownloadTask future) {
+                    try {
+                        // Call the callback
+                        if (downloadCallback != null) {
+                            downloadCallback.downloaded(downloadTask);
+                        }
+                        // Make sure we log any download error if the callback 
suppressed it
+                        downloadTask.getFile();
+                    } catch (Throwable e) {
+                        exception.addSuppressed(e);
+                    } finally {
+                        synchronized (lock) {
+                            downloading.remove(location);
+                            downloaded.put(location, downloadTask);
+                            --allPending;
+                            if (--pending == 0) {
+                                lock.notifyAll();
+                            }
+                        }
+                    }
+                }
+            });
+        }
+
+        private AbstractDownloadTask createDownloadTask(final String url) {
+            final String mvnUrl = DownloadManagerHelper.stripUrl(url);
+            if (mvnUrl.startsWith("mvn:")) {
+                if (!mvnUrl.equals(url)) {
+                    return new ChainedDownloadTask(executorService, url, 
mvnUrl);
+                } else {
+                    return new MavenDownloadTask(executorService, 
mavenResolver, mvnUrl);
+                }
+            } else {
+                return new SimpleDownloadTask(executorService, url, tmpPath);
+            }
+        }
+
+        class ChainedDownloadTask extends AbstractDownloadTask {
+
+            private String innerUrl;
+
+            public ChainedDownloadTask(ScheduledExecutorService 
executorService, String url, String innerUrl) {
+                super(executorService, url);
+                this.innerUrl = innerUrl;
+            }
+
+            @Override
+            public void run() {
+                try {
+                    MavenDownloader.this.download(innerUrl, new 
DownloadCallback() {
+                        @Override
+                        public void downloaded(StreamProvider provider) throws 
Exception {
+                            try {
+                                AbstractDownloadTask future = 
(AbstractDownloadTask) provider;
+                                String file = 
future.getFile().toURI().toURL().toExternalForm();
+                                String real = url.replace(innerUrl, file);
+                                MavenDownloader.this.download(real, new 
DownloadCallback() {
+                                    @Override
+                                    public void downloaded(StreamProvider 
provider) throws Exception {
+                                        try {
+                                            setFile(provider.getFile());
+                                        } catch (IOException e) {
+                                            setException(e);
+                                        } catch (Throwable t) {
+                                            setException(new IOException(t));
+                                        }
+                                    }
+                                });
+                            } catch (IOException e) {
+                                setException(e);
+                            } catch (Throwable t) {
+                                setException(new IOException(t));
+                            }
+                        }
+                    });
+                } catch (IOException e) {
+                    setException(e);
+                } catch (Throwable t) {
+                    setException(new IOException(t));
+                }
+            }
+
+        }
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/90cb480d/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/MavenDownloadTask.java
----------------------------------------------------------------------
diff --git 
a/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/MavenDownloadTask.java
 
b/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/MavenDownloadTask.java
new file mode 100644
index 0000000..0959e77
--- /dev/null
+++ 
b/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/MavenDownloadTask.java
@@ -0,0 +1,37 @@
+/*
+ * 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.features.internal.download.impl;
+
+import java.io.File;
+import java.util.concurrent.ScheduledExecutorService;
+
+import org.ops4j.pax.url.mvn.MavenResolver;
+
+public class MavenDownloadTask extends AbstractRetryableDownloadTask {
+
+    private final MavenResolver resolver;
+
+    public MavenDownloadTask(ScheduledExecutorService executor, MavenResolver 
resolver, String url) {
+        super(executor, url);
+        this.resolver = resolver;
+    }
+
+    protected File download() throws Exception {
+        return resolver.resolve(url);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/90cb480d/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/SimpleDownloadTask.java
----------------------------------------------------------------------
diff --git 
a/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/SimpleDownloadTask.java
 
b/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/SimpleDownloadTask.java
new file mode 100644
index 0000000..0767f91
--- /dev/null
+++ 
b/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/SimpleDownloadTask.java
@@ -0,0 +1,117 @@
+/*
+ * 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.features.internal.download.impl;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URL;
+import java.util.concurrent.ScheduledExecutorService;
+
+import org.apache.karaf.util.StreamUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class SimpleDownloadTask extends AbstractRetryableDownloadTask {
+
+    private static final String BLUEPRINT_PREFIX = "blueprint:";
+    private static final String SPRING_PREFIX = "spring:";
+
+    /**
+     * Logger.
+     */
+    private static final Logger LOG = 
LoggerFactory.getLogger(AbstractDownloadTask.class);
+
+    private File basePath;
+
+    public SimpleDownloadTask(ScheduledExecutorService executorService, String 
url, File basePath) {
+        super(executorService, url);
+        this.basePath = basePath;
+    }
+
+    @Override
+    protected File download() throws Exception {
+        LOG.trace("Downloading [" + url + "]");
+
+        if (url.startsWith(BLUEPRINT_PREFIX) || url.startsWith(SPRING_PREFIX)) 
{
+            return downloadBlueprintOrSpring();
+        }
+
+        try {
+            basePath.mkdirs();
+            if (!basePath.isDirectory()) {
+                throw new IOException("Unable to create directory " + 
basePath.toString());
+            }
+
+            URL urlObj = new URL(url);
+            File file = new File(basePath, getFileName(urlObj.getFile()));
+            if (file.exists()) {
+                return file;
+            }
+
+            File dir = new File(System.getProperty("karaf.data"), "tmp");
+            dir.mkdirs();
+            if (!dir.isDirectory()) {
+                throw new IOException("Unable to create directory " + 
dir.toString());
+            }
+
+            File tmpFile = File.createTempFile("download-", null, dir);
+
+            try (InputStream is = urlObj.openStream();
+                 OutputStream os = new FileOutputStream(tmpFile)) {
+                StreamUtils.copy(is, os);
+            }
+
+            if (file.exists() && !file.delete()) {
+                throw new IOException("Unable to delete file: " + 
file.toString());
+            }
+            // check: this will move the file to CHILD_HOME root directory...
+            if (!tmpFile.renameTo(file)) {
+                throw new IOException("Unable to rename file " + 
tmpFile.toString() + " to " + file.toString());
+            }
+            return file;
+        } catch (IOException ignore) {
+            throw new IOException("Could not download [" + this.url + "]", 
ignore);
+        }
+    }
+
+    // we only want the filename itself, not the whole path
+    private String getFileName(String url) {
+        // ENTESB-1394: we do not want all these decorators from wrap: protocol
+        // or any inlined maven repos
+        url = DownloadManagerHelper.stripUrl(url);
+        url = DownloadManagerHelper.removeInlinedMavenRepositoryUrl(url);
+        int unixPos = url.lastIndexOf('/');
+        int windowsPos = url.lastIndexOf('\\');
+        return url.substring(Math.max(unixPos, windowsPos) + 1);
+    }
+
+    protected File downloadBlueprintOrSpring() throws Exception {
+        // when downloading an embedded blueprint or spring xml file, then it 
must be as a temporary file
+        File dir = new File(System.getProperty("karaf.data"), "tmp");
+        dir.mkdirs();
+        File tmpFile = File.createTempFile("download-", null, dir);
+        try (InputStream is = new URL(url).openStream();
+             OutputStream os = new FileOutputStream(tmpFile))
+        {
+            StreamUtils.copy(is, os);
+        }
+        return tmpFile;
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/90cb480d/features/core/src/main/java/org/apache/karaf/features/internal/download/simple/SimpleDownloader.java
----------------------------------------------------------------------
diff --git 
a/features/core/src/main/java/org/apache/karaf/features/internal/download/simple/SimpleDownloader.java
 
b/features/core/src/main/java/org/apache/karaf/features/internal/download/simple/SimpleDownloader.java
deleted file mode 100644
index 670b63e..0000000
--- 
a/features/core/src/main/java/org/apache/karaf/features/internal/download/simple/SimpleDownloader.java
+++ /dev/null
@@ -1,127 +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.features.internal.download.simple;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.jar.Attributes;
-import java.util.jar.Manifest;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipInputStream;
-
-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.util.MultiException;
-
-import static java.util.jar.JarFile.MANIFEST_NAME;
-
-public class SimpleDownloader implements DownloadManager, Downloader {
-
-    protected final MultiException exception = new MultiException("Error");
-
-    protected final ConcurrentMap<String, StreamProvider> providers = new 
ConcurrentHashMap<String, StreamProvider>();
-
-    @Override
-    public Downloader createDownloader() {
-        return this;
-    }
-
-    @Override
-    public void await() throws InterruptedException, MultiException {
-        exception.throwIfExceptions();
-    }
-
-    @Override
-    public void download(final String location, final DownloadCallback 
downloadCallback) throws MalformedURLException {
-        if (!providers.containsKey(location)) {
-            providers.putIfAbsent(location, createProvider(location));
-        }
-        try {
-            if (downloadCallback != null) {
-                downloadCallback.downloaded(providers.get(location));
-            }
-        } catch (Exception e) {
-            exception.addSuppressed(e);
-        }
-    }
-
-    protected StreamProvider createProvider(String location) throws 
MalformedURLException {
-        try {
-            return new UrlProvider(new URL(location));
-        } catch (MalformedURLException e) {
-            throw new IllegalStateException("Error opening URL " + location, 
e);
-        }
-    }
-
-    public Map<String, StreamProvider> getProviders() {
-        return providers;
-    }
-
-    static class UrlProvider implements StreamProvider {
-        private final URL url;
-        private volatile Map<String, String> metadata;
-
-        UrlProvider(URL url) {
-            this.url = url;
-        }
-
-        @Override
-        public InputStream open() throws IOException {
-            return url.openStream();
-        }
-
-        @Override
-        public Map<String, String> getMetadata() throws IOException {
-            if (metadata == null) {
-                synchronized (this) {
-                    if (metadata == null) {
-                        metadata = doGetMetadata();
-                    }
-                }
-            }
-            return metadata;
-        }
-
-        protected Map<String, String> doGetMetadata() throws IOException {
-            try (
-                    InputStream is = 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<String, 
String>();
-                        for (Map.Entry attr : attributes.entrySet()) {
-                            headers.put(attr.getKey().toString(), 
attr.getValue().toString());
-                        }
-                        return headers;
-                    }
-                }
-            }
-            throw new IllegalArgumentException("Resource " + url + " does not 
contain a manifest");
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/karaf/blob/90cb480d/features/core/src/main/java/org/apache/karaf/features/internal/osgi/Activator.java
----------------------------------------------------------------------
diff --git 
a/features/core/src/main/java/org/apache/karaf/features/internal/osgi/Activator.java
 
b/features/core/src/main/java/org/apache/karaf/features/internal/osgi/Activator.java
index 3be20e5..9a89494 100644
--- 
a/features/core/src/main/java/org/apache/karaf/features/internal/osgi/Activator.java
+++ 
b/features/core/src/main/java/org/apache/karaf/features/internal/osgi/Activator.java
@@ -204,7 +204,7 @@ public class Activator extends BaseActivator {
                 stateStorage,
                 featureFinder,
                 eventAdminListener,
-                configInstaller,
+                configurationAdmin,
                 digraph,
                 overrides,
                 featureResolutionRange,

http://git-wip-us.apache.org/repos/asf/karaf/blob/90cb480d/features/core/src/main/java/org/apache/karaf/features/internal/region/Subsystem.java
----------------------------------------------------------------------
diff --git 
a/features/core/src/main/java/org/apache/karaf/features/internal/region/Subsystem.java
 
b/features/core/src/main/java/org/apache/karaf/features/internal/region/Subsystem.java
index c7cc22d..a958e8a 100644
--- 
a/features/core/src/main/java/org/apache/karaf/features/internal/region/Subsystem.java
+++ 
b/features/core/src/main/java/org/apache/karaf/features/internal/region/Subsystem.java
@@ -16,6 +16,8 @@
  */
 package org.apache.karaf.features.internal.region;
 
+import java.io.IOException;
+import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -25,6 +27,10 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.jar.Attributes;
+import java.util.jar.Manifest;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
 
 import org.apache.felix.resolver.Util;
 import org.apache.felix.utils.manifest.Clause;
@@ -50,6 +56,7 @@ import org.osgi.framework.Version;
 import org.osgi.resource.Requirement;
 import org.osgi.resource.Resource;
 
+import static java.util.jar.JarFile.MANIFEST_NAME;
 import static 
org.apache.karaf.features.internal.resolver.ResourceUtils.TYPE_FEATURE;
 import static 
org.apache.karaf.features.internal.resolver.ResourceUtils.TYPE_SUBSYSTEM;
 import static 
org.apache.karaf.features.internal.resolver.ResourceUtils.addIdentityRequirement;
@@ -342,7 +349,7 @@ public class Subsystem extends ResourceImpl {
             downloader.download(loc, new DownloadCallback() {
                 @Override
                 public void downloaded(StreamProvider provider) throws 
Exception {
-                    ResourceImpl res = createResource(loc, 
provider.getMetadata());
+                    ResourceImpl res = createResource(loc, 
getMetadata(provider));
                     bundles.put(loc, res);
                 }
             });
@@ -352,7 +359,7 @@ public class Subsystem extends ResourceImpl {
             downloader.download(loc, new DownloadCallback() {
                 @Override
                 public void downloaded(StreamProvider provider) throws 
Exception {
-                    ResourceImpl res = createResource(loc, 
provider.getMetadata());
+                    ResourceImpl res = createResource(loc, 
getMetadata(provider));
                     bundles.put(loc, res);
                 }
             });
@@ -362,7 +369,7 @@ public class Subsystem extends ResourceImpl {
             downloader.download(loc, new DownloadCallback() {
                 @Override
                 public void downloaded(StreamProvider provider) throws 
Exception {
-                    ResourceImpl res = createResource(loc, 
provider.getMetadata());
+                    ResourceImpl res = createResource(loc, 
getMetadata(provider));
                     bundles.put(loc, res);
                 }
             });
@@ -423,6 +430,26 @@ public class Subsystem extends ResourceImpl {
         }
     }
 
+    Map<String, String> getMetadata(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<java.lang.String, java.lang.String> headers = new 
HashMap<java.lang.String, java.lang.String>();
+                    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");
+    }
+
     void addDependency(ResourceImpl resource, boolean mandatory, boolean 
start, int startLevel) {
         if (isAcceptDependencies()) {
             doAddDependency(resource, mandatory, start, startLevel);

http://git-wip-us.apache.org/repos/asf/karaf/blob/90cb480d/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java
----------------------------------------------------------------------
diff --git 
a/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java
 
b/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java
index af84db2..d357f0f 100644
--- 
a/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java
+++ 
b/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java
@@ -25,9 +25,12 @@ import java.net.URI;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Dictionary;
 import java.util.EnumSet;
+import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Hashtable;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
@@ -39,6 +42,7 @@ import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
 
 import org.apache.felix.utils.version.VersionCleaner;
 import org.apache.felix.utils.version.VersionRange;
@@ -49,13 +53,16 @@ import org.apache.karaf.features.FeaturesListener;
 import org.apache.karaf.features.FeaturesService;
 import org.apache.karaf.features.Repository;
 import org.apache.karaf.features.RepositoryEvent;
-import org.apache.karaf.features.internal.download.simple.SimpleDownloader;
+import org.apache.karaf.features.internal.download.DownloadManager;
+import org.apache.karaf.features.internal.download.DownloadManagers;
 import org.apache.karaf.features.internal.util.JsonReader;
 import org.apache.karaf.features.internal.util.JsonWriter;
 import org.apache.karaf.util.collections.CopyOnWriteArrayIdentityList;
 import org.eclipse.equinox.region.Region;
 import org.eclipse.equinox.region.RegionDigraph;
 import org.eclipse.equinox.region.RegionFilterBuilder;
+import org.ops4j.pax.url.mvn.MavenResolver;
+import org.ops4j.pax.url.mvn.MavenResolvers;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.BundleException;
@@ -66,6 +73,8 @@ import org.osgi.framework.Version;
 import org.osgi.framework.startlevel.BundleStartLevel;
 import org.osgi.framework.startlevel.FrameworkStartLevel;
 import org.osgi.framework.wiring.FrameworkWiring;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.cm.ConfigurationAdmin;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -101,6 +110,7 @@ public class FeaturesServiceImpl implements 
FeaturesService, Deployer.DeployCall
     private final StateStorage storage;
     private final FeatureFinder featureFinder;
     private final EventAdminListener eventAdminListener;
+    private final ConfigurationAdmin configurationAdmin;
     private final FeatureConfigInstaller configInstaller;
     private final RegionDigraph digraph;
     private final String overrides;
@@ -143,7 +153,7 @@ public class FeaturesServiceImpl implements 
FeaturesService, Deployer.DeployCall
                                StateStorage storage,
                                FeatureFinder featureFinder,
                                EventAdminListener eventAdminListener,
-                               FeatureConfigInstaller configInstaller,
+                               ConfigurationAdmin configurationAdmin,
                                RegionDigraph digraph,
                                String overrides,
                                String featureResolutionRange,
@@ -155,7 +165,8 @@ public class FeaturesServiceImpl implements 
FeaturesService, Deployer.DeployCall
         this.storage = storage;
         this.featureFinder = featureFinder;
         this.eventAdminListener = eventAdminListener;
-        this.configInstaller = configInstaller;
+        this.configurationAdmin = configurationAdmin;
+        this.configInstaller = configurationAdmin != null ? new 
FeatureConfigInstaller(configurationAdmin) : null;
         this.digraph = digraph;
         this.overrides = overrides;
         this.featureResolutionRange = featureResolutionRange;
@@ -944,22 +955,50 @@ public class FeaturesServiceImpl implements 
FeaturesService, Deployer.DeployCall
                             EnumSet<Option> options                            
    // installation options
     ) throws Exception {
 
-        Set<String> prereqs = new HashSet<>();
-        while (true) {
-            try {
-                Deployer.DeploymentState dstate = getDeploymentState(state);
-                Deployer.DeploymentRequest request = 
getDeploymentRequest(requirements, stateChanges, options);
-                new Deployer(new SimpleDownloader(), this).deploy(dstate, 
request);
-                break;
-            } catch (Deployer.PartialDeploymentException e) {
-                if (!prereqs.containsAll(e.getMissing())) {
-                    prereqs.addAll(e.getMissing());
-                    state = copyState();
-                } else {
-                    throw new Exception("Deployment aborted due to loop in 
missing prerequisites: " + e.getMissing());
+        Dictionary<String, String> props = getMavenConfig();
+        MavenResolver resolver = MavenResolvers.createMavenResolver(props, 
"org.ops4j.pax.url.mvn");
+        ScheduledExecutorService executor = 
Executors.newScheduledThreadPool(8);
+        DownloadManager manager = 
DownloadManagers.createDownloadManager(resolver, executor);
+        try {
+            Set<String> prereqs = new HashSet<>();
+            while (true) {
+                try {
+                    Deployer.DeploymentState dstate = 
getDeploymentState(state);
+                    Deployer.DeploymentRequest request = 
getDeploymentRequest(requirements, stateChanges, options);
+                    new Deployer(manager, this).deploy(dstate, request);
+                    break;
+                } catch (Deployer.PartialDeploymentException e) {
+                    if (!prereqs.containsAll(e.getMissing())) {
+                        prereqs.addAll(e.getMissing());
+                        state = copyState();
+                    } else {
+                        throw new Exception("Deployment aborted due to loop in 
missing prerequisites: " + e.getMissing());
+                    }
+                }
+            }
+        } finally {
+            executor.shutdown();
+        }
+    }
+
+    private Dictionary<String, String> getMavenConfig() throws IOException {
+        Hashtable<String, String> props = new Hashtable<>();
+        if (configurationAdmin != null) {
+            Configuration config = 
configurationAdmin.getConfiguration("org.ops4j.pax.url.mvn", null);
+            if (config != null) {
+                Dictionary<String, Object> cfg = config.getProperties();
+                if (cfg != null) {
+                    for (Enumeration<String> e = cfg.keys(); 
e.hasMoreElements(); ) {
+                        String key = e.nextElement();
+                        Object val = cfg.get(key);
+                        if (key != null) {
+                            props.put(key, val.toString());
+                        }
+                    }
                 }
             }
         }
+        return props;
     }
 
     public void print(String message, boolean verbose) {

http://git-wip-us.apache.org/repos/asf/karaf/blob/90cb480d/features/core/src/test/java/org/apache/karaf/features/internal/support/TestDownloadManager.java
----------------------------------------------------------------------
diff --git 
a/features/core/src/test/java/org/apache/karaf/features/internal/support/TestDownloadManager.java
 
b/features/core/src/test/java/org/apache/karaf/features/internal/support/TestDownloadManager.java
index 999c212..7c0deed 100644
--- 
a/features/core/src/test/java/org/apache/karaf/features/internal/support/TestDownloadManager.java
+++ 
b/features/core/src/test/java/org/apache/karaf/features/internal/support/TestDownloadManager.java
@@ -18,19 +18,26 @@ package org.apache.karaf.features.internal.support;
 
 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.util.HashMap;
 import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
 import java.util.jar.JarOutputStream;
 import java.util.jar.Manifest;
 
+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.simple.SimpleDownloader;
+import org.apache.karaf.features.internal.util.MultiException;
 
-public class TestDownloadManager extends SimpleDownloader {
+public class TestDownloadManager implements DownloadManager, Downloader {
 
+    private final MultiException exception = new MultiException("Error");
+    private final ConcurrentMap<String, StreamProvider> providers = new 
ConcurrentHashMap<>();
     private final Class loader;
     private final String dir;
 
@@ -40,17 +47,44 @@ public class TestDownloadManager extends SimpleDownloader {
     }
 
     @Override
+    public Downloader createDownloader() {
+        return this;
+    }
+
+    @Override
+    public Map<String, StreamProvider> getProviders() {
+        return providers;
+    }
+
+    @Override
+    public void await() throws InterruptedException, MultiException {
+        exception.throwIfExceptions();
+    }
+
+    @Override
+    public void download(final String location, final DownloadCallback 
downloadCallback) throws MalformedURLException {
+        if (!providers.containsKey(location)) {
+            providers.putIfAbsent(location, createProvider(location));
+        }
+        try {
+            if (downloadCallback != null) {
+                downloadCallback.downloaded(providers.get(location));
+            }
+        } catch (Exception e) {
+            exception.addSuppressed(e);
+        }
+    }
+
     protected StreamProvider createProvider(String location) throws 
MalformedURLException {
         return new TestProvider(location);
     }
 
     class TestProvider implements StreamProvider {
+        private final String location;
         private final IOException exception;
-        private final Map<String, String> headers;
         private final byte[] data;
 
         TestProvider(String location) {
-            Map<String, String> headers = null;
             byte[] data = null;
             IOException exception = null;
             try {
@@ -59,18 +93,18 @@ public class TestDownloadManager extends SimpleDownloader {
                 JarOutputStream jos = new JarOutputStream(baos, man);
                 jos.close();
                 data = baos.toByteArray();
-                headers = new HashMap<>();
-                for (Map.Entry attr : man.getMainAttributes().entrySet()) {
-                    headers.put(attr.getKey().toString(), 
attr.getValue().toString());
-                }
             } catch (IOException e) {
                 exception = e;
             }
-            this.headers = headers;
+            this.location = location;
             this.data = data;
             this.exception = exception;
         }
 
+        public String getUrl() {
+            return location;
+        }
+
         @Override
         public InputStream open() throws IOException {
             if (exception != null)
@@ -79,10 +113,9 @@ public class TestDownloadManager extends SimpleDownloader {
         }
 
         @Override
-        public Map<String, String> getMetadata() throws IOException {
-            if (exception != null)
-                throw exception;
-            return headers;
+        public File getFile() throws IOException {
+            throw new UnsupportedOperationException();
         }
+
     }
 }

http://git-wip-us.apache.org/repos/asf/karaf/blob/90cb480d/instance/pom.xml
----------------------------------------------------------------------
diff --git a/instance/pom.xml b/instance/pom.xml
index e3a5cab..e78ab9a 100644
--- a/instance/pom.xml
+++ b/instance/pom.xml
@@ -82,6 +82,12 @@
         </dependency>
 
         <dependency>
+            <groupId>org.apache.karaf.profile</groupId>
+            <artifactId>org.apache.karaf.profile.core</artifactId>
+            <optional>true</optional>
+        </dependency>
+
+        <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-jdk14</artifactId>
             <scope>test</scope>

http://git-wip-us.apache.org/repos/asf/karaf/blob/90cb480d/instance/src/main/java/org/apache/karaf/instance/command/CreateCommand.java
----------------------------------------------------------------------
diff --git 
a/instance/src/main/java/org/apache/karaf/instance/command/CreateCommand.java 
b/instance/src/main/java/org/apache/karaf/instance/command/CreateCommand.java
index 4a302f3..5173066 100644
--- 
a/instance/src/main/java/org/apache/karaf/instance/command/CreateCommand.java
+++ 
b/instance/src/main/java/org/apache/karaf/instance/command/CreateCommand.java
@@ -27,6 +27,7 @@ import org.apache.felix.utils.properties.Properties;
 import org.apache.karaf.features.command.completers.AllFeatureCompleter;
 import org.apache.karaf.features.command.completers.InstalledRepoUriCompleter;
 import org.apache.karaf.instance.core.InstanceSettings;
+import org.apache.karaf.profile.command.completers.ProfileCompleter;
 import org.apache.karaf.shell.api.action.Argument;
 import org.apache.karaf.shell.api.action.Command;
 import org.apache.karaf.shell.api.action.Completion;
@@ -71,6 +72,11 @@ public class CreateCommand extends InstanceCommandSupport
     @Completion(InstalledRepoUriCompleter.class)
     List<String> featureURLs;
 
+    @Option(name = "-p", aliases = {"--profiles"},
+            description = "Profiles to install on the instance", required = 
false, multiValued = true)
+    @Completion(ProfileCompleter.class)
+    List<String> profiles;
+
     @Option(name = "-v", aliases = {"--verbose"}, description = "Display 
actions performed by the command (disabled by default)", required = false, 
multiValued = false)
     boolean verbose = false;
 
@@ -116,7 +122,7 @@ public class CreateCommand extends InstanceCommandSupport
         }
         Map<String, URL> textResources = getResources(textResourceLocation);
         Map<String, URL> binaryResources = 
getResources(binaryResourceLocations);
-        InstanceSettings settings = new InstanceSettings(sshPort, 
rmiRegistryPort, rmiServerPort, location, javaOpts, featureURLs, features, 
address, textResources, binaryResources);
+        InstanceSettings settings = new InstanceSettings(sshPort, 
rmiRegistryPort, rmiServerPort, location, javaOpts, featureURLs, features, 
address, textResources, binaryResources, profiles);
         getInstanceService().createInstance(instance, settings, verbose);
         return null;
     }

http://git-wip-us.apache.org/repos/asf/karaf/blob/90cb480d/instance/src/main/java/org/apache/karaf/instance/core/InstanceSettings.java
----------------------------------------------------------------------
diff --git 
a/instance/src/main/java/org/apache/karaf/instance/core/InstanceSettings.java 
b/instance/src/main/java/org/apache/karaf/instance/core/InstanceSettings.java
index 13ba69a..c8a0063 100644
--- 
a/instance/src/main/java/org/apache/karaf/instance/core/InstanceSettings.java
+++ 
b/instance/src/main/java/org/apache/karaf/instance/core/InstanceSettings.java
@@ -35,16 +35,21 @@ public class InstanceSettings {
     private final String address;
     private final Map<String, URL> textResources;
     private final Map<String, URL> binaryResources;
+    private final List<String> profiles;
 
     public InstanceSettings(int sshPort, int rmiRegistryPort, int 
rmiServerPort, String location, String javaOpts, List<String> featureURLs, 
List<String> features) {
         this(sshPort, rmiRegistryPort, rmiServerPort, location, javaOpts, 
featureURLs, features, "0.0.0.0");
     }
 
     public InstanceSettings(int sshPort, int rmiRegistryPort, int 
rmiServerPort, String location, String javaOpts, List<String> featureURLs, 
List<String> features, String address) {
-       this(sshPort, rmiRegistryPort, rmiServerPort, location, javaOpts, 
featureURLs, features, address, new HashMap<String, URL>(), new HashMap<String, 
URL>());
+        this(sshPort, rmiRegistryPort, rmiServerPort, location, javaOpts, 
featureURLs, features, address, new HashMap<String, URL>(), new HashMap<String, 
URL>());
     }
 
     public InstanceSettings(int sshPort, int rmiRegistryPort, int 
rmiServerPort, String location, String javaOpts, List<String> featureURLs, 
List<String> features, String address, Map<String, URL> textResources, 
Map<String, URL> binaryResources) {
+        this(sshPort, rmiRegistryPort, rmiServerPort, location, javaOpts, 
featureURLs, features, address, textResources, binaryResources, null);
+    }
+
+    public InstanceSettings(int sshPort, int rmiRegistryPort, int 
rmiServerPort, String location, String javaOpts, List<String> featureURLs, 
List<String> features, String address, Map<String, URL> textResources, 
Map<String, URL> binaryResources, List<String> profiles) {
         this.sshPort = sshPort;
         this.rmiRegistryPort = rmiRegistryPort;
         this.rmiServerPort = rmiServerPort;
@@ -55,6 +60,7 @@ public class InstanceSettings {
         this.address = address;
         this.textResources = textResources != null ? textResources : new 
HashMap<String, URL>();
         this.binaryResources = binaryResources != null ? binaryResources : new 
HashMap<String, URL>();
+        this.profiles = profiles != null ? profiles : new ArrayList<String>();
     }
 
 
@@ -98,6 +104,10 @@ public class InstanceSettings {
         return this.address;
     }
 
+    public List<String> getProfiles() {
+        return profiles;
+    }
+
     @Override
     public boolean equals(Object o) {
         if (o == this) {
@@ -114,7 +124,8 @@ public class InstanceSettings {
                (javaOpts == null ? is.javaOpts == null : 
javaOpts.equals(is.javaOpts)) &&
                (featureURLs == null ? is.featureURLs == null : 
featureURLs.equals(is.featureURLs)) &&
                (features == null ? is.features == null : 
features.equals(is.features)) &&
-               (address == null ? is.address == null : 
address.equals(is.address));
+               (address == null ? is.address == null : 
address.equals(is.address)) &&
+               (profiles == null ? is.profiles == null : 
profiles.equals(is.profiles));
     }
 
     @Override
@@ -125,6 +136,7 @@ public class InstanceSettings {
         result = 31 * result + (featureURLs != null ? featureURLs.hashCode() : 
0);
         result = 31 * result + (features != null ? features.hashCode() : 0);
         result = 31 * result + (address != null ? address.hashCode() : 0);
+        result = 31 * result + (profiles != null ? profiles.hashCode() : 0);
         return result;
     }
 

http://git-wip-us.apache.org/repos/asf/karaf/blob/90cb480d/instance/src/main/java/org/apache/karaf/instance/core/internal/InstanceServiceImpl.java
----------------------------------------------------------------------
diff --git 
a/instance/src/main/java/org/apache/karaf/instance/core/internal/InstanceServiceImpl.java
 
b/instance/src/main/java/org/apache/karaf/instance/core/internal/InstanceServiceImpl.java
index 83169ef..ecf563f 100644
--- 
a/instance/src/main/java/org/apache/karaf/instance/core/internal/InstanceServiceImpl.java
+++ 
b/instance/src/main/java/org/apache/karaf/instance/core/internal/InstanceServiceImpl.java
@@ -16,19 +16,6 @@
  */
 package org.apache.karaf.instance.core.internal;
 
-import org.apache.karaf.instance.core.Instance;
-import org.apache.karaf.instance.core.InstanceService;
-import org.apache.karaf.instance.core.InstanceSettings;
-import org.apache.karaf.instance.main.Execute;
-import org.apache.karaf.jpm.Process;
-import org.apache.karaf.jpm.impl.ProcessBuilderFactoryImpl;
-import org.apache.karaf.jpm.impl.ScriptUtils;
-import org.apache.karaf.util.StreamUtils;
-import org.apache.karaf.util.locks.FileLockUtils;
-import org.fusesource.jansi.Ansi;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 import java.io.BufferedReader;
 import java.io.Closeable;
 import java.io.File;
@@ -43,6 +30,9 @@ import java.io.OutputStream;
 import java.io.PrintStream;
 import java.net.Socket;
 import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Enumeration;
 import java.util.HashMap;
@@ -53,6 +43,25 @@ import java.util.Properties;
 import java.util.Scanner;
 import java.util.TreeMap;
 
+import org.apache.karaf.instance.core.Instance;
+import org.apache.karaf.instance.core.InstanceService;
+import org.apache.karaf.instance.core.InstanceSettings;
+import org.apache.karaf.instance.main.Execute;
+import org.apache.karaf.jpm.Process;
+import org.apache.karaf.jpm.impl.ProcessBuilderFactoryImpl;
+import org.apache.karaf.jpm.impl.ScriptUtils;
+import org.apache.karaf.profile.Profile;
+import org.apache.karaf.profile.ProfileBuilder;
+import org.apache.karaf.profile.ProfileService;
+import org.apache.karaf.util.StreamUtils;
+import org.apache.karaf.util.locks.FileLockUtils;
+import org.fusesource.jansi.Ansi;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.ServiceReference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 public class InstanceServiceImpl implements InstanceService {
 
     public static final String STORAGE_FILE = "instance.properties";
@@ -239,7 +248,7 @@ public class InstanceServiceImpl implements InstanceService 
{
         }
     }
 
-    private void logInfo(String message, boolean printOutput, Object... args) {
+    private static void logInfo(String message, boolean printOutput, Object... 
args) {
         if (LOGGER.isInfoEnabled() || printOutput) {
             String formatted = String.format(message, args);
             LOGGER.info(formatted);
@@ -255,6 +264,14 @@ public class InstanceServiceImpl implements 
InstanceService {
                 if (state.instances.get(name) != null) {
                     throw new IllegalArgumentException("Instance '" + name + 
"' already exists");
                 }
+                if (!settings.getProfiles().isEmpty()) {
+                    try {
+                        ProfileApplier.verify();
+                    } catch (NoClassDefFoundError error) {
+                        throw new IllegalArgumentException("Profile service 
package is not available");
+                    }
+                }
+
                 String loc = settings.getLocation() != null ? 
settings.getLocation() : name;
                 File karafBase = new File(loc);
                 if (!karafBase.isAbsolute()) {
@@ -349,6 +366,10 @@ public class InstanceServiceImpl implements 
InstanceService {
                     copyBinaryResourceToDir(resource, karafBase, 
binaryResources, printOutput);
                 }
 
+                if (!settings.getProfiles().isEmpty()) {
+                    ProfileApplier.applyProfiles(karafBase, 
settings.getProfiles(), printOutput);
+                }
+
                 String javaOpts = settings.getJavaOpts();
                 if (javaOpts == null || javaOpts.length() == 0) {
                     javaOpts = DEFAULT_JAVA_OPTS;
@@ -374,7 +395,7 @@ public class InstanceServiceImpl implements InstanceService 
{
         }, true);
     }
 
-    private void appendToPropList(org.apache.felix.utils.properties.Properties 
p, String key, List<String> elements) {
+    private static void 
appendToPropList(org.apache.felix.utils.properties.Properties p, String key, 
List<String> elements) {
         if (elements == null) {
             return;
         }
@@ -761,7 +782,7 @@ public class InstanceServiceImpl implements InstanceService 
{
                 Socket s = new Socket(host, port);
                 s.getOutputStream().write(shutdown.getBytes());
                 s.close();
-                long stopTimeout = 
Long.parseLong(props.getProperty(KARAF_SHUTDOWN_TIMEOUT, 
+                long stopTimeout = 
Long.parseLong(props.getProperty(KARAF_SHUTDOWN_TIMEOUT,
                                                                     
Long.toString(getStopTimeout())));
                 long t = System.currentTimeMillis() + stopTimeout;
                 do {
@@ -1000,7 +1021,7 @@ public class InstanceServiceImpl implements 
InstanceService {
         }
     }
 
-    private void println(String st) {
+    private static void println(String st) {
         System.out.println(st);
     }
 
@@ -1275,4 +1296,44 @@ public class InstanceServiceImpl implements 
InstanceService {
         }, true);
     }
 
+    private static class ProfileApplier {
+
+        // Verify that profile package is wired correctly
+        static void verify() {
+            Profile.class.getName();
+        }
+
+        static void applyProfiles(File karafBase, List<String> profiles, 
boolean printOutput) throws IOException {
+            BundleContext bundleContext = 
FrameworkUtil.getBundle(ProfileApplier.class).getBundleContext();
+            ServiceReference<ProfileService> reference = 
bundleContext.getServiceReference(ProfileService.class);
+            ProfileService service = bundleContext.getService(reference);
+
+            Profile profile = ProfileBuilder.Factory.create("temp")
+                    .addParents(profiles)
+                    .getProfile();
+            Profile overlay = service.getOverlayProfile(profile);
+            final Profile effective = service.getEffectiveProfile(overlay, 
false);
+
+            Map<String, byte[]> configs = effective.getFileConfigurations();
+            for (Map.Entry<String, byte[]> config : configs.entrySet()) {
+                String pid = config.getKey();
+                if (!pid.equals(Profile.INTERNAL_PID + 
Profile.PROPERTIES_SUFFIX)) {
+                    Path configFile = Paths.get(karafBase.toString(), "etc", 
pid);
+                    logInfo("Creating file: %s", printOutput, 
configFile.toString());
+                    Files.write(configFile, config.getValue());
+                }
+            }
+            FileLockUtils.execute(new File(karafBase, FEATURES_CFG), new 
FileLockUtils.RunnableWithProperties() {
+                public void run(org.apache.felix.utils.properties.Properties 
properties) throws IOException {
+                    appendToPropList(properties, "featuresBoot", 
effective.getFeatures());
+                    appendToPropList(properties, "featuresRepositories", 
effective.getRepositories());
+                }
+            }, true);
+
+            bundleContext.ungetService(reference);
+        }
+
+
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/karaf/blob/90cb480d/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 2bc1b80..84342d3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -65,6 +65,7 @@
         <module>jdbc</module>
         <module>jms</module>
         <module>jpa</module>
+        <module>profile</module>
         <module>tooling</module>
         <module>assemblies</module>
         <module>demos</module>
@@ -164,7 +165,7 @@
         
<felix.framework.security.version>2.4.0</felix.framework.security.version>
         <felix.gogo.version>0.14.0</felix.gogo.version>
         <felix.plugin.version>2.5.3</felix.plugin.version>
-        <felix.utils.version>1.6.0</felix.utils.version>
+        <felix.utils.version>1.6.1-SNAPSHOT</felix.utils.version>
         <felix.webconsole.version>4.2.2</felix.webconsole.version>
         <felix.webconsole.api.version>3.1.2</felix.webconsole.api.version>
         <felix.metatype.version>1.0.10</felix.metatype.version>
@@ -232,7 +233,7 @@
         <pax.exam.version>4.3.0</pax.exam.version>
         <pax.logging.version>1.8.1</pax.logging.version>
         <pax.base.version>1.5.0</pax.base.version>
-        <pax.url.version>2.2.0</pax.url.version>
+        <pax.url.version>2.2.1-SNAPSHOT</pax.url.version>
         <pax.web.version>4.0.0</pax.web.version>
         <pax.tinybundle.version>2.1.0</pax.tinybundle.version>
         <portlet-api.version>2.0</portlet-api.version>
@@ -541,6 +542,12 @@
             </dependency>
 
             <dependency>
+                <groupId>org.apache.karaf.profile</groupId>
+                <artifactId>org.apache.karaf.profile.core</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+
+            <dependency>
                 <groupId>org.apache.karaf.diagnostic</groupId>
                 <artifactId>org.apache.karaf.diagnostic.boot</artifactId>
                 <version>${project.version}</version>
@@ -1269,6 +1276,11 @@
                 <artifactId>slf4j-simple</artifactId>
                 <version>${slf4j.version}</version>
             </dependency>
+            <dependency>
+                <groupId>org.slf4j</groupId>
+                <artifactId>jcl-over-slf4j</artifactId>
+                <version>${slf4j.version}</version>
+            </dependency>
 
             <dependency>
                 <groupId>org.ops4j.pax.logging</groupId>

Reply via email to