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 2014/04/07 10:52:52 UTC

[01/13] git commit: [KARAF-2805] Refactor Registry#hasCommand to Registry#getCommand

Repository: karaf
Updated Branches:
  refs/heads/master fe2627f22 -> 38502e415


[KARAF-2805] Refactor Registry#hasCommand to Registry#getCommand


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

Branch: refs/heads/master
Commit: 5337778a3e2df088d77157fe876f14103fc9710c
Parents: fe2627f
Author: Guillaume Nodet <gn...@gmail.com>
Authored: Thu Apr 3 09:43:01 2014 +0200
Committer: Guillaume Nodet <gn...@gmail.com>
Committed: Mon Apr 7 10:52:23 2014 +0200

----------------------------------------------------------------------
 .../org/apache/karaf/itests/KarafTestSupport.java     |  2 +-
 .../org/apache/karaf/shell/api/console/Registry.java  |  3 +--
 .../impl/action/osgi/AggregateServiceTracker.java     |  2 +-
 .../shell/impl/action/osgi/CommandExtension.java      |  2 +-
 .../karaf/shell/impl/action/osgi/RegistryImpl.java    | 14 ++++++++++----
 .../apache/karaf/shell/impl/console/RegistryImpl.java | 14 ++++++++++----
 6 files changed, 24 insertions(+), 13 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/karaf/blob/5337778a/itests/src/test/java/org/apache/karaf/itests/KarafTestSupport.java
----------------------------------------------------------------------
diff --git a/itests/src/test/java/org/apache/karaf/itests/KarafTestSupport.java b/itests/src/test/java/org/apache/karaf/itests/KarafTestSupport.java
index ea7ac9c..5062c64 100644
--- a/itests/src/test/java/org/apache/karaf/itests/KarafTestSupport.java
+++ b/itests/src/test/java/org/apache/karaf/itests/KarafTestSupport.java
@@ -291,7 +291,7 @@ public class KarafTestSupport {
             long start = System.currentTimeMillis();
             long cur   = start;
             while (cur - start < SERVICE_TIMEOUT) {
-                if (sessionFactory.getRegistry().hasCommand(scope, name)) {
+                if (sessionFactory.getRegistry().getCommand(scope, name) != null) {
                     return;
                 }
                 Thread.sleep(100);

http://git-wip-us.apache.org/repos/asf/karaf/blob/5337778a/shell/core/src/main/java/org/apache/karaf/shell/api/console/Registry.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/api/console/Registry.java b/shell/core/src/main/java/org/apache/karaf/shell/api/console/Registry.java
index 35c7c75..8df1b22 100644
--- a/shell/core/src/main/java/org/apache/karaf/shell/api/console/Registry.java
+++ b/shell/core/src/main/java/org/apache/karaf/shell/api/console/Registry.java
@@ -40,9 +40,8 @@ public interface Registry {
      * @param scope
      * @param name
      * @return
-     * @throws InterruptedException
      */
-    boolean hasCommand(String scope, String name);
+    Command getCommand(String scope, String name);
 
     /**
      * Register a delayed service (or factory).

http://git-wip-us.apache.org/repos/asf/karaf/blob/5337778a/shell/core/src/main/java/org/apache/karaf/shell/impl/action/osgi/AggregateServiceTracker.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/action/osgi/AggregateServiceTracker.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/action/osgi/AggregateServiceTracker.java
index dad5ab2..8f45115 100644
--- a/shell/core/src/main/java/org/apache/karaf/shell/impl/action/osgi/AggregateServiceTracker.java
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/action/osgi/AggregateServiceTracker.java
@@ -144,7 +144,7 @@ public abstract class AggregateServiceTracker {
         public List<String> getMissingServices() {
             List<String> missing = new ArrayList<String>();
             for (SingleServiceTracker tracker : singleTrackers.values()) {
-                if (single.containsKey(tracker.getTrackedClass())) {
+                if (!single.containsKey(tracker.getTrackedClass())) {
                     missing.add(tracker.getTrackedClass().getName());
                 }
             }

http://git-wip-us.apache.org/repos/asf/karaf/blob/5337778a/shell/core/src/main/java/org/apache/karaf/shell/impl/action/osgi/CommandExtension.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/action/osgi/CommandExtension.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/action/osgi/CommandExtension.java
index 80f9d21..a2cf8a4 100644
--- a/shell/core/src/main/java/org/apache/karaf/shell/impl/action/osgi/CommandExtension.java
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/action/osgi/CommandExtension.java
@@ -132,7 +132,7 @@ public class CommandExtension implements Extension {
         } else if (isSatisfied) {
             action = "Registering";
         } else {
-            action = null;
+            return;
         }
         LOGGER.info("{} commands for bundle {}/{}",
                 action,

http://git-wip-us.apache.org/repos/asf/karaf/blob/5337778a/shell/core/src/main/java/org/apache/karaf/shell/impl/action/osgi/RegistryImpl.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/action/osgi/RegistryImpl.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/action/osgi/RegistryImpl.java
index e1cdd67..ad1a1e5 100644
--- a/shell/core/src/main/java/org/apache/karaf/shell/impl/action/osgi/RegistryImpl.java
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/action/osgi/RegistryImpl.java
@@ -44,14 +44,20 @@ public class RegistryImpl implements Registry {
     }
 
     @Override
-    public boolean hasCommand(String scope, String name) {
-        if (parent != null && parent.hasCommand(scope, name)) {
-            return true;
+    public Command getCommand(String scope, String name) {
+        if (parent != null) {
+            Command command = parent.getCommand(scope, name);
+            if (command != null) {
+                return command;
+            }
         }
         synchronized (services) {
             List<Command> cmds = commands.get(scope + ":" + name);
-            return cmds != null && !cmds.isEmpty();
+            if (cmds != null && !cmds.isEmpty()) {
+                return cmds.get(0);
+            }
         }
+        return null;
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/karaf/blob/5337778a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/RegistryImpl.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/RegistryImpl.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/RegistryImpl.java
index 99113b7..0d39a95 100644
--- a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/RegistryImpl.java
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/RegistryImpl.java
@@ -44,14 +44,20 @@ public class RegistryImpl implements Registry {
     }
 
     @Override
-    public boolean hasCommand(String scope, String name) {
-        if (parent != null && parent.hasCommand(scope, name)) {
-            return true;
+    public Command getCommand(String scope, String name) {
+        if (parent != null) {
+            Command command = parent.getCommand(scope, name);
+            if (command != null) {
+                return command;
+            }
         }
         synchronized (services) {
             List<Command> cmds = commands.get(scope + ":" + name);
-            return cmds != null && !cmds.isEmpty();
+            if (cmds != null && !cmds.isEmpty()) {
+                return cmds.get(0);
+            }
         }
+        return null;
     }
 
     @Override


[07/13] [KARAF-2888] New FeaturesService based on the real OSGi resolver

Posted by gn...@apache.org.
http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/internal/service/Overrides.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/Overrides.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/Overrides.java
new file mode 100644
index 0000000..233a8a2
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/Overrides.java
@@ -0,0 +1,132 @@
+/*
+ * 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.service;
+
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.felix.utils.manifest.Clause;
+import org.apache.felix.utils.manifest.Parser;
+import org.apache.felix.utils.version.VersionRange;
+import org.osgi.framework.Version;
+import org.osgi.resource.Resource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.apache.felix.resolver.Util.getSymbolicName;
+import static org.apache.felix.resolver.Util.getVersion;
+
+/**
+ * Helper class to deal with overriden bundles at feature installation time.
+ */
+public class Overrides {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(Overrides.class);
+
+    protected static final String OVERRIDE_RANGE = "range";
+
+    /**
+     * Compute a list of bundles to install, taking into account overrides.
+     *
+     * The file containing the overrides will be loaded from the given url.
+     * Blank lines and lines starting with a '#' will be ignored, all other lines
+     * are considered as urls to override bundles.
+     *
+     * The list of resources to resolve will be scanned and for each bundle,
+     * if a bundle override matches that resource, it will be used instead.
+     *
+     * Matching is done on bundle symbolic name (they have to be the same)
+     * and version (the bundle override version needs to be greater than the
+     * resource to be resolved, and less than the next minor version.  A range
+     * directive can be added to the override url in which case, the matching
+     * will succeed if the resource to be resolved is within the given range.
+     *
+     * @param resources the list of resources to resolve
+     * @param overrides list of bundle overrides
+     */
+    public static void override(Map<String, Resource> resources, Collection<String> overrides) {
+        // Do override replacement
+        for (Clause override : Parser.parseClauses(overrides.toArray(new String[overrides.size()]))) {
+            String url = override.getName();
+            String vr  = override.getAttribute(OVERRIDE_RANGE);
+            Resource over = resources.get(url);
+            if (over == null) {
+                // Ignore invalid overrides
+                continue;
+            }
+            for (String uri : new ArrayList<String>(resources.keySet())) {
+                Resource res = resources.get(uri);
+                if (getSymbolicName(res).equals(getSymbolicName(over))) {
+                    VersionRange range;
+                    if (vr == null) {
+                        // default to micro version compatibility
+                        Version v1 = getVersion(res);
+                        Version v2 = new Version(v1.getMajor(), v1.getMinor() + 1, 0);
+                        range = new VersionRange(false, v1, v2, true);
+                    } else {
+                        range = VersionRange.parseVersionRange(vr);
+                    }
+                    // The resource matches, so replace it with the overridden resource
+                    // if the override is actually a newer version than what we currently have
+                    if (range.contains(getVersion(over)) && getVersion(res).compareTo(getVersion(over)) < 0) {
+                        resources.put(uri, over);
+                    }
+                }
+            }
+        }
+    }
+
+    public static Set<String> loadOverrides(String overridesUrl) {
+        Set<String> overrides = new HashSet<String>();
+        try {
+            if (overridesUrl != null) {
+                InputStream is = new URL(overridesUrl).openStream();
+                try {
+                    BufferedReader reader = new BufferedReader(new InputStreamReader(is));
+                    String line;
+                    while ((line = reader.readLine()) != null) {
+                        line = line.trim();
+                        if (!line.isEmpty() && !line.startsWith("#")) {
+                            overrides.add(line);
+                        }
+                    }
+                } finally {
+                    is.close();
+                }
+            }
+        } catch (Exception e) {
+            LOGGER.debug("Unable to load overrides bundles list", e);
+        }
+        return overrides;
+    }
+
+    public static String extractUrl(String override) {
+        Clause[] cs = Parser.parseClauses(new String[] { override });
+        if (cs.length != 1) {
+            throw new IllegalStateException("Override contains more than one clause: " + override);
+        }
+        return cs[0].getName();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/internal/service/RepositoryImpl.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/RepositoryImpl.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/RepositoryImpl.java
new file mode 100644
index 0000000..4bf1502
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/RepositoryImpl.java
@@ -0,0 +1,103 @@
+/*
+ * 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.service;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InterruptedIOException;
+import java.net.URI;
+
+import org.apache.karaf.features.Repository;
+import org.apache.karaf.features.internal.model.Features;
+import org.apache.karaf.features.internal.model.JaxbUtil;
+
+/**
+ * The repository implementation.
+ */
+public class RepositoryImpl implements Repository {
+
+    private final URI uri;
+    private Features features;
+
+    public RepositoryImpl(URI uri) {
+        this.uri = uri;
+    }
+
+    public URI getURI() {
+        return uri;
+    }
+
+    public String getName() {
+        // TODO: catching this exception is ugly
+        try {
+            load();
+        } catch (IOException e) {
+            throw new RuntimeException("Unable to load repository", e);
+        }
+        return features.getName();
+    }
+
+    public URI[] getRepositories() throws Exception {
+        load();
+        URI[] result = new URI[features.getRepository().size()];
+        for (int i = 0; i < features.getRepository().size(); i++) {
+            String uri = features.getRepository().get(i);
+            uri = uri.trim();
+            result[i] = URI.create(uri);
+        }
+        return result;
+    }
+
+    public org.apache.karaf.features.Feature[] getFeatures() throws Exception {
+        load();
+        return features.getFeature().toArray(new org.apache.karaf.features.Feature[features.getFeature().size()]);
+    }
+
+
+    public void load() throws IOException {
+        if (features == null) {
+            try {
+                InputStream inputStream = uri.toURL().openStream();
+                inputStream = new FilterInputStream(inputStream) {
+    				@Override
+    				public int read(byte[] b, int off, int len) throws IOException {
+    					if (Thread.currentThread().isInterrupted()) {
+    						throw new InterruptedIOException();
+    					}
+    					return super.read(b, off, len);
+    				}
+    			};
+                try {
+                    features = JaxbUtil.unmarshal(inputStream, false);
+                } finally {
+                    inputStream.close();
+                }
+            } catch (IllegalArgumentException e) {
+                throw (IOException) new IOException(e.getMessage() + " : " + uri).initCause(e);
+            } catch (Exception e) {
+                throw (IOException) new IOException(e.getMessage() + " : " + uri).initCause(e);
+            }
+        }
+    }
+
+    @Override
+    public boolean isValid() {
+        throw new UnsupportedOperationException();
+    }
+}
+

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/internal/service/RequirementSort.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/RequirementSort.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/RequirementSort.java
new file mode 100644
index 0000000..e9ecece
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/RequirementSort.java
@@ -0,0 +1,107 @@
+/*
+ * 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.service;
+
+
+import org.apache.karaf.features.internal.resolver.RequirementImpl;
+import org.apache.karaf.features.internal.resolver.SimpleFilter;
+import org.osgi.framework.Constants;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Requirement;
+import org.osgi.resource.Resource;
+
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+public class RequirementSort  {
+
+    /**
+     * Sorts {@link Resource} based on their {@link Requirement}s and {@link Capability}s.
+     * @param resources
+     * @return
+     */
+    public static <T extends Resource> Collection<T> sort(Collection<T> resources) {
+        Set<T> sorted = new LinkedHashSet<T>();
+        Set<T> visited = new LinkedHashSet<T>();
+        for (T r : resources) {
+            visit(r, resources, visited, sorted);
+        }
+        return sorted;
+    }
+
+
+    private static <T extends Resource> void visit(T resource, Collection<T> resources, Set<T> visited, Set<T> sorted) {
+        if (visited.contains(resource)) {
+            return;
+        }
+        visited.add(resource);
+        for (T r : collectDependencies(resource, resources)) {
+            visit(r, resources, visited, sorted);
+        }
+        sorted.add(resource);
+    }
+
+    /**
+     * Finds the dependencies of the current resource.
+     * @param resource
+     * @param allResources
+     * @return
+     */
+    private static <T extends Resource> Set<T> collectDependencies(T resource, Collection<T> allResources) {
+        Set<T> result = new LinkedHashSet<T>();
+        List<Requirement> requirements = resource.getRequirements(null);
+        for (Requirement requirement : requirements) {
+            boolean isSatisfied = false;
+            for (Resource r : result) {
+                for (Capability capability : r.getCapabilities(null)) {
+                    if (isSatisfied(requirement, capability)) {
+                        isSatisfied = true;
+                        break;
+                    }
+                }
+            }
+
+            for (T r : allResources) {
+                if (!isSatisfied) {
+                    for (Capability capability : r.getCapabilities(null)) {
+                        if (isSatisfied(requirement, capability)) {
+                            result.add(r);
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+        return result;
+    }
+
+    private static boolean isSatisfied(Requirement requirement, Capability capability) {
+        RequirementImpl br;
+        if (requirement instanceof RequirementImpl) {
+            br = (RequirementImpl) requirement;
+        } else {
+            String filter = requirement.getDirectives().get(Constants.FILTER_DIRECTIVE);
+            SimpleFilter sf = (filter != null)
+                    ? SimpleFilter.parse(filter)
+                    : new SimpleFilter(null, null, SimpleFilter.MATCH_ALL);
+            br = new RequirementImpl(null, requirement.getNamespace(), requirement.getDirectives(), requirement.getAttributes(), sf);
+        }
+        return br.matches(capability);
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/internal/service/SimpleDownloader.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/SimpleDownloader.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/SimpleDownloader.java
new file mode 100644
index 0000000..d1f16b9
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/SimpleDownloader.java
@@ -0,0 +1,51 @@
+/*
+ * 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.service;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import org.apache.karaf.features.internal.deployment.Downloader;
+import org.apache.karaf.features.internal.deployment.StreamProvider;
+import org.apache.karaf.features.internal.util.MultiException;
+
+public class SimpleDownloader implements Downloader {
+
+    private final MultiException exception = new MultiException("Error");
+
+    @Override
+    public void await() throws InterruptedException, MultiException {
+        exception.throwIfExceptions();
+    }
+
+    @Override
+    public void download(final String location, final DownloadCallback downloadCallback) throws MalformedURLException {
+        final URL url = new URL(location);
+        try {
+            downloadCallback.downloaded(new StreamProvider() {
+                @Override
+                public InputStream open() throws IOException {
+                    return url.openStream();
+                }
+            });
+        } catch (Exception e) {
+            exception.addException(e);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/internal/service/State.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/State.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/State.java
new file mode 100644
index 0000000..c84f4e0
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/State.java
@@ -0,0 +1,34 @@
+/*
+ * 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.service;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class State {
+
+    public final AtomicBoolean bootDone = new AtomicBoolean();
+    public final Set<String> repositories = new TreeSet<String>();
+    public final Set<String> features = new TreeSet<String>();
+    public final Set<String> installedFeatures = new TreeSet<String>();
+    public final Set<Long> managedBundles = new TreeSet<Long>();
+    public final Map<String, Long> bundleChecksums = new HashMap<String, Long>();
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/internal/service/StateStorage.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/StateStorage.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/StateStorage.java
new file mode 100644
index 0000000..ac54ab0
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/StateStorage.java
@@ -0,0 +1,175 @@
+/*
+ * 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.service;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.apache.karaf.features.Feature;
+
+public abstract class StateStorage {
+
+    public void load(State state) throws IOException {
+        state.repositories.clear();
+        state.features.clear();
+        state.installedFeatures.clear();
+        state.managedBundles.clear();
+        InputStream is = getInputStream();
+        if (is != null) {
+            try {
+                Properties props = new Properties();
+                props.load(is);
+                state.bootDone.set(loadBool(props, "bootDone"));
+                state.repositories.addAll(loadSet(props, "repositories."));
+                state.features.addAll(loadSet(props, "features."));
+                state.installedFeatures.addAll(loadSet(props, "installed."));
+                state.managedBundles.addAll(toLongSet(loadSet(props, "managed.")));
+                state.bundleChecksums.putAll(toStringLongMap(loadMap(props, "checksums.")));
+            } finally {
+                close(is);
+            }
+        }
+    }
+
+    public void save(State state) throws IOException {
+        OutputStream os = getOutputStream();
+        if (os != null) {
+            try {
+                Properties props = new Properties();
+                saveBool(props, "bootDone", state.bootDone.get());
+                saveSet(props, "repositories.", state.repositories);
+                saveSet(props, "features.", state.features);
+                saveSet(props, "installed.", state.installedFeatures);
+                saveSet(props, "managed.", toStringSet(state.managedBundles));
+                saveMap(props, "checksums.", toStringStringMap(state.bundleChecksums));
+                props.store(os, "FeaturesService State");
+            } finally {
+                close(os);
+            }
+        }
+    }
+
+    protected abstract InputStream getInputStream() throws IOException;
+    protected abstract OutputStream getOutputStream() throws IOException;
+
+    protected boolean loadBool(Properties props, String key) {
+        return Boolean.parseBoolean(props.getProperty(key));
+    }
+
+    protected void saveBool(Properties props, String key, boolean val) {
+        props.setProperty(key, Boolean.toString(val));
+    }
+
+    protected Set<String> toStringSet(Set<Long> set) {
+        Set<String> ns = new TreeSet<String>();
+        for (long l : set) {
+            ns.add(Long.toString(l));
+        }
+        return ns;
+    }
+
+    protected Set<Long> toLongSet(Set<String> set) {
+        Set<Long> ns = new TreeSet<Long>();
+        for (String s : set) {
+            ns.add(Long.parseLong(s));
+        }
+        return ns;
+    }
+
+    protected void saveSet(Properties props, String prefix, Set<String> set) {
+        List<String> l = new ArrayList<String>(set);
+        props.put(prefix + "count", Integer.toString(l.size()));
+        for (int i = 0; i < l.size(); i++) {
+            props.put(prefix + "item." + i, l.get(i));
+        }
+    }
+
+    protected Set<String> loadSet(Properties props, String prefix) {
+        Set<String> l = new HashSet<String>();
+        String countStr = (String) props.get(prefix + "count");
+        if (countStr != null) {
+            int count = Integer.parseInt(countStr);
+            for (int i = 0; i < count; i++) {
+                l.add((String) props.get(prefix + "item." + i));
+            }
+        }
+        return l;
+    }
+
+    protected Map<String, String> toStringStringMap(Map<String, Long> map) {
+        Map<String, String> nm = new HashMap<String, String>();
+        for (Map.Entry<String, Long> entry : map.entrySet()) {
+            nm.put(entry.getKey(), Long.toString(entry.getValue()));
+        }
+        return nm;
+    }
+
+    protected Map<String, Long> toStringLongMap(Map<String, String> map) {
+        Map<String, Long> nm = new HashMap<String, Long>();
+        for (Map.Entry<String, String> entry : map.entrySet()) {
+            nm.put(entry.getKey(), Long.parseLong(entry.getValue()));
+        }
+        return nm;
+    }
+
+
+    protected void saveMap(Properties props, String prefix, Map<String, String> map) {
+        List<Map.Entry<String, String>> l = new ArrayList<Map.Entry<String, String>>(map.entrySet());
+        props.put(prefix + "count", Integer.toString(l.size()));
+        for (int i = 0; i < l.size(); i++) {
+            props.put(prefix + "key." + i, l.get(i).getKey());
+            props.put(prefix + "val." + i, l.get(i).getValue());
+        }
+    }
+
+    protected Map<String, String> loadMap(Properties props, String prefix) {
+        Map<String, String> l = new HashMap<String, String>();
+        String countStr = (String) props.get(prefix + "count");
+        if (countStr != null) {
+            int count = Integer.parseInt(countStr);
+            for (int i = 0; i < count; i++) {
+                String key = (String) props.get(prefix + "key." + i);
+                String val = (String) props.get(prefix + "val." + i);
+                l.put(key, val);
+            }
+        }
+        return l;
+    }
+
+
+    protected void close(Closeable closeable) {
+        if (closeable != null) {
+            try {
+                closeable.close();
+            } catch (IOException e) {
+                // Ignore
+            }
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/internal/util/ChecksumUtils.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/util/ChecksumUtils.java b/features/core/src/main/java/org/apache/karaf/features/internal/util/ChecksumUtils.java
new file mode 100644
index 0000000..19fc706
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/util/ChecksumUtils.java
@@ -0,0 +1,56 @@
+/*
+ * 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.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.zip.CRC32;
+
+public class ChecksumUtils {
+
+    private ChecksumUtils() {
+    }
+
+    /**
+     * Compute a cheksum for the file or directory that consists of the name, length and the last modified date
+     * for a file and its children in case of a directory
+     *
+     * @param is the input stream
+     * @return a checksum identifying any change
+     */
+    public static long checksum(InputStream is) throws IOException
+    {
+        try {
+            CRC32 crc = new CRC32();
+            byte[] buffer = new byte[8192];
+            int l;
+            while ((l = is.read(buffer)) > 0) {
+                crc.update(buffer, 0, l);
+            }
+            return crc.getValue();
+        } finally {
+            if (is != null) {
+                try {
+                    is.close();
+                } catch (IOException e) {
+                    // Ignore
+                }
+            }
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/internal/util/JsonReader.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/util/JsonReader.java b/features/core/src/main/java/org/apache/karaf/features/internal/util/JsonReader.java
new file mode 100644
index 0000000..35b7308
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/util/JsonReader.java
@@ -0,0 +1,343 @@
+/*******************************************************************************
+ * Copyright (c) 2013 EclipseSource.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Ralf Sternberg - initial implementation and API
+ ******************************************************************************/
+package org.apache.karaf.features.internal.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ */
+public class JsonReader {
+
+    public static Object read(Reader reader) throws IOException {
+        return new JsonReader(reader).parse();
+    }
+
+    public static Object read(InputStream is) throws IOException {
+        return new JsonReader(new InputStreamReader(is)).parse();
+    }
+
+    //
+    // Implementation
+    //
+
+    private final Reader reader;
+    private final StringBuilder recorder;
+    private int current;
+    private int line = 1;
+    private int column = 0;
+
+    JsonReader(Reader reader) {
+        this.reader = reader;
+        recorder = new StringBuilder();
+    }
+
+    public Object parse() throws IOException {
+        read();
+        skipWhiteSpace();
+        Object result = readValue();
+        skipWhiteSpace();
+        if (!endOfText()) {
+            throw error("Unexpected character");
+        }
+        return result;
+    }
+
+    private Object readValue() throws IOException {
+        switch (current) {
+            case 'n':
+                return readNull();
+            case 't':
+                return readTrue();
+            case 'f':
+                return readFalse();
+            case '"':
+                return readString();
+            case '[':
+                return readArray();
+            case '{':
+                return readObject();
+            case '-':
+            case '0':
+            case '1':
+            case '2':
+            case '3':
+            case '4':
+            case '5':
+            case '6':
+            case '7':
+            case '8':
+            case '9':
+                return readNumber();
+            default:
+                throw expected("value");
+        }
+    }
+
+    private Collection<?> readArray() throws IOException {
+        read();
+        Collection<Object> array = new ArrayList<Object>();
+        skipWhiteSpace();
+        if (readChar(']')) {
+            return array;
+        }
+        do {
+            skipWhiteSpace();
+            array.add(readValue());
+            skipWhiteSpace();
+        } while (readChar(','));
+        if (!readChar(']')) {
+            throw expected("',' or ']'");
+        }
+        return array;
+    }
+
+    private Map<String, Object> readObject() throws IOException {
+        read();
+        Map<String, Object> object = new HashMap<String, Object>();
+        skipWhiteSpace();
+        if (readChar('}')) {
+            return object;
+        }
+        do {
+            skipWhiteSpace();
+            String name = readName();
+            skipWhiteSpace();
+            if (!readChar(':')) {
+                throw expected("':'");
+            }
+            skipWhiteSpace();
+            object.put(name, readValue());
+            skipWhiteSpace();
+        } while (readChar(','));
+        if (!readChar('}')) {
+            throw expected("',' or '}'");
+        }
+        return object;
+    }
+
+    private Object readNull() throws IOException {
+        read();
+        readRequiredChar('u');
+        readRequiredChar('l');
+        readRequiredChar('l');
+        return null;
+    }
+
+    private Boolean readTrue() throws IOException {
+        read();
+        readRequiredChar('r');
+        readRequiredChar('u');
+        readRequiredChar('e');
+        return Boolean.TRUE;
+    }
+
+    private Boolean readFalse() throws IOException {
+        read();
+        readRequiredChar('a');
+        readRequiredChar('l');
+        readRequiredChar('s');
+        readRequiredChar('e');
+        return Boolean.FALSE;
+    }
+
+    private void readRequiredChar(char ch) throws IOException {
+        if (!readChar(ch)) {
+            throw expected("'" + ch + "'");
+        }
+    }
+
+    private String readString() throws IOException {
+        read();
+        recorder.setLength(0);
+        while (current != '"') {
+            if (current == '\\') {
+                readEscape();
+            } else if (current < 0x20) {
+                throw expected("valid string character");
+            } else {
+                recorder.append((char) current);
+                read();
+            }
+        }
+        read();
+        return recorder.toString();
+    }
+
+    private void readEscape() throws IOException {
+        read();
+        switch (current) {
+            case '"':
+            case '/':
+            case '\\':
+                recorder.append((char) current);
+                break;
+            case 'b':
+                recorder.append('\b');
+                break;
+            case 'f':
+                recorder.append('\f');
+                break;
+            case 'n':
+                recorder.append('\n');
+                break;
+            case 'r':
+                recorder.append('\r');
+                break;
+            case 't':
+                recorder.append('\t');
+                break;
+            case 'u':
+                char[] hexChars = new char[4];
+                for (int i = 0; i < 4; i++) {
+                    read();
+                    if (!isHexDigit(current)) {
+                        throw expected("hexadecimal digit");
+                    }
+                    hexChars[i] = (char) current;
+                }
+                recorder.append((char) Integer.parseInt(String.valueOf(hexChars), 16));
+                break;
+            default:
+                throw expected("valid escape sequence");
+        }
+        read();
+    }
+
+    private Number readNumber() throws IOException {
+        recorder.setLength(0);
+        readAndAppendChar('-');
+        int firstDigit = current;
+        if (!readAndAppendDigit()) {
+            throw expected("digit");
+        }
+        if (firstDigit != '0') {
+            while (readAndAppendDigit()) {
+            }
+        }
+        readFraction();
+        readExponent();
+        return Double.parseDouble(recorder.toString());
+    }
+
+    private boolean readFraction() throws IOException {
+        if (!readAndAppendChar('.')) {
+            return false;
+        }
+        if (!readAndAppendDigit()) {
+            throw expected("digit");
+        }
+        while (readAndAppendDigit()) {
+        }
+        return true;
+    }
+
+    private boolean readExponent() throws IOException {
+        if (!readAndAppendChar('e') && !readAndAppendChar('E')) {
+            return false;
+        }
+        if (!readAndAppendChar('+')) {
+            readAndAppendChar('-');
+        }
+        if (!readAndAppendDigit()) {
+            throw expected("digit");
+        }
+        while (readAndAppendDigit()) {
+        }
+        return true;
+    }
+
+    private String readName() throws IOException {
+        if (current != '"') {
+            throw expected("name");
+        }
+        readString();
+        return recorder.toString();
+    }
+
+    private boolean readAndAppendChar(char ch) throws IOException {
+        if (current != ch) {
+            return false;
+        }
+        recorder.append(ch);
+        read();
+        return true;
+    }
+
+    private boolean readChar(char ch) throws IOException {
+        if (current != ch) {
+            return false;
+        }
+        read();
+        return true;
+    }
+
+    private boolean readAndAppendDigit() throws IOException {
+        if (!isDigit(current)) {
+            return false;
+        }
+        recorder.append((char) current);
+        read();
+        return true;
+    }
+
+    private void skipWhiteSpace() throws IOException {
+        while (isWhiteSpace(current) && !endOfText()) {
+            read();
+        }
+    }
+
+    private void read() throws IOException {
+        if (endOfText()) {
+            throw error("Unexpected end of input");
+        }
+        column++;
+        if (current == '\n') {
+            line++;
+            column = 0;
+        }
+        current = reader.read();
+    }
+
+    private boolean endOfText() {
+        return current == -1;
+    }
+
+    private IOException expected(String expected) {
+        if (endOfText()) {
+            return error("Unexpected end of input");
+        }
+        return error("Expected " + expected);
+    }
+
+    private IOException error(String message) {
+        return new IOException(message + " at " + line + ":" + column);
+    }
+
+    private static boolean isWhiteSpace(int ch) {
+        return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r';
+    }
+
+    private static boolean isDigit(int ch) {
+        return ch >= '0' && ch <= '9';
+    }
+
+    private static boolean isHexDigit(int ch) {
+        return ch >= '0' && ch <= '9' || ch >= 'a' && ch <= 'f' || ch >= 'A' && ch <= 'F';
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/internal/util/JsonWriter.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/util/JsonWriter.java b/features/core/src/main/java/org/apache/karaf/features/internal/util/JsonWriter.java
new file mode 100644
index 0000000..8294faa
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/util/JsonWriter.java
@@ -0,0 +1,120 @@
+/**
+ * Copyright (C) FuseSource, Inc.
+ * http://fusesource.com
+ *
+ * Licensed 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.util;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ */
+public class JsonWriter {
+
+    public static void write(Writer writer, Object value) throws IOException {
+        if (value instanceof Map) {
+            writeObject(writer, (Map) value);
+        } else if (value instanceof Collection) {
+            writeArray(writer, (Collection) value);
+        } else if (value instanceof Number) {
+            writeNumber(writer, (Number) value);
+        } else if (value instanceof String) {
+            writeString(writer, (String) value);
+        } else if (value instanceof Boolean) {
+            writeBoolean(writer, (Boolean) value);
+        } else if (value == null) {
+            writeNull(writer);
+        } else {
+            throw new IllegalArgumentException("Unsupported value: " + value);
+        }
+    }
+
+    private static void writeObject(Writer writer, Map<?, ?> value) throws IOException {
+        writer.append('{');
+        boolean first = true;
+        for (Map.Entry entry : value.entrySet()) {
+            if (!first) {
+                writer.append(',');
+            } else {
+                first = false;
+            }
+            writeString(writer, (String) entry.getKey());
+            writer.append(':');
+            write(writer, entry.getValue());
+        }
+        writer.append('}');
+    }
+
+    private static void writeString(Writer writer, String value) throws IOException {
+        writer.append('"');
+        for (int i = 0; i < value.length(); i++) {
+            char c = value.charAt(i);
+            switch (c) {
+                case '\"':
+                case '\\':
+                case '\b':
+                case '\f':
+                case '\n':
+                case '\r':
+                case '\t':
+                    writer.append('\\');
+                    writer.append(c);
+                    break;
+                default:
+                    if (c < ' ' || (c >= '\u0080' && c < '\u00a0') || (c >= '\u2000' && c < '\u2100')) {
+                        String s = Integer.toHexString(c);
+                        writer.append('\\');
+                        writer.append('u');
+                        for (int j = s.length(); j < 4; j++) {
+                            writer.append('0');
+                        }
+                        writer.append(s);
+                    } else {
+                        writer.append(c);
+                    }
+                    break;
+            }
+        }
+        writer.append('"');
+    }
+
+    private static void writeNumber(Writer writer, Number value) throws IOException {
+        writer.append(value.toString());
+    }
+
+    private static void writeBoolean(Writer writer, Boolean value) throws IOException {
+        writer.append(Boolean.toString(value));
+    }
+
+    private static void writeArray(Writer writer, Collection<?> value) throws IOException {
+        writer.append('[');
+        boolean first = true;
+        for (Object obj : value) {
+            if (!first) {
+                writer.append(',');
+            } else {
+                first = false;
+            }
+            write(writer, obj);
+        }
+        writer.append(']');
+    }
+
+    private static void writeNull(Writer writer) throws IOException {
+        writer.append("null");
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/internal/util/Macro.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/util/Macro.java b/features/core/src/main/java/org/apache/karaf/features/internal/util/Macro.java
new file mode 100644
index 0000000..d30b7b5
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/util/Macro.java
@@ -0,0 +1,142 @@
+/*
+ * 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.util;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.felix.utils.version.VersionTable;
+import org.osgi.framework.Version;
+
+public class Macro {
+
+    public static String transform(String macro, String value) {
+        if (macro.startsWith("${") && macro.endsWith("}")) {
+            String[] args = macro.substring(2, macro.length() - 1).split(";");
+            if ("version".equals(args[0])) {
+                if (args.length != 2) {
+                    throw new IllegalArgumentException("Invalid syntax for macro: " + macro);
+                }
+                return version(args[1], VersionTable.getVersion(value));
+            } else if ("range".equals(args[0])) {
+                if (args.length != 2) {
+                    throw new IllegalArgumentException("Invalid syntax for macro: " + macro);
+                }
+                return range(args[1], VersionTable.getVersion(value));
+            } else {
+                throw new IllegalArgumentException("Unknown macro: " + macro);
+            }
+        }
+        return value;
+    }
+
+    /**
+     * Modify a version to set a version policy. Thed policy is a mask that is
+     * mapped to a version.
+     *
+     * <pre>
+     * +           increment
+     * -           decrement
+     * =           maintain
+     * &tilde;           discard
+     *
+     * ==+      = maintain major, minor, increment micro, discard qualifier
+     * &tilde;&tilde;&tilde;=     = just get the qualifier
+     * version=&quot;[${version;==;${@}},${version;=+;${@}})&quot;
+	 * </pre>
+	 *
+	 * @param args
+     * @return
+     */
+    final static String	MASK_STRING			= "[\\-+=~0123456789]{0,3}[=~]?";
+
+    static String version(String mask, Version version) {
+        StringBuilder sb = new StringBuilder();
+        String del = "";
+
+        for (int i = 0; i < mask.length(); i++) {
+            char c = mask.charAt(i);
+            String result = null;
+            if (c != '~') {
+                if (i > 3) {
+                    throw new IllegalArgumentException("Version mask can only specify 3 digits");
+                } else if (i == 3) {
+                    result = version.getQualifier();
+                    if (result.isEmpty()) {
+                        result = null;
+                    }
+                } else if (Character.isDigit(c)) {
+                    // Handle masks like +00, =+0
+                    result = String.valueOf(c);
+                } else {
+                    int x = 0;
+                    switch (i) {
+                        case 0: x = version.getMajor(); break;
+                        case 1: x = version.getMinor(); break;
+                        case 2: x = version.getMicro(); break;
+                    }
+                    switch (c) {
+                        case '+' :
+                            x++;
+                            break;
+                        case '-' :
+                            x--;
+                            break;
+                        case '=' :
+                            break;
+                    }
+                    result = Integer.toString(x);
+                }
+                if (result != null) {
+                    sb.append(del);
+                    del = ".";
+                    sb.append(result);
+                }
+            }
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Schortcut for version policy
+     *
+     * <pre>
+     * -provide-policy : ${policy;[==,=+)}
+     * -consume-policy : ${policy;[==,+)}
+     * </pre>
+     *
+     * @param args
+     * @return
+     */
+
+    static Pattern	RANGE_MASK		= Pattern.compile("(\\[|\\()(" + MASK_STRING + "),(" + MASK_STRING + ")(\\]|\\))");
+
+    static String range(String spec, Version version) {
+        Matcher m = RANGE_MASK.matcher(spec);
+        m.matches();
+        String floor = m.group(1);
+        String floorMask = m.group(2);
+        String ceilingMask = m.group(3);
+        String ceiling = m.group(4);
+
+        String left = version(floorMask, version);
+        String right = version(ceilingMask, version);
+
+        return floor + left + "," + right + ceiling;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/internal/util/MultiException.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/util/MultiException.java b/features/core/src/main/java/org/apache/karaf/features/internal/util/MultiException.java
new file mode 100644
index 0000000..36af452
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/util/MultiException.java
@@ -0,0 +1,95 @@
+/*
+ * 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.util;
+
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+@SuppressWarnings("serial")
+public class MultiException extends Exception {
+
+    private List<Exception> exceptions = new ArrayList<Exception>();
+
+    public MultiException(String message) {
+        super(message);
+    }
+
+    public MultiException(String message, List<Exception> exceptions) {
+        super(message);
+        this.exceptions = exceptions;
+    }
+
+    public void addException(Exception e) {
+        exceptions.add(e);
+    }
+
+    public void throwIfExceptions() throws MultiException {
+        if (!exceptions.isEmpty()) {
+            throw this;
+        }
+    }
+    
+    public Throwable[] getCauses() {
+        return exceptions.toArray(new Throwable[exceptions.size()]);
+    }
+
+    @Override
+    public void printStackTrace()
+    {
+        super.printStackTrace();
+        for (Exception e : exceptions) {
+            e.printStackTrace();
+        }
+    }
+
+
+    /* ------------------------------------------------------------------------------- */
+    /**
+     * @see Throwable#printStackTrace(java.io.PrintStream)
+     */
+    @Override
+    public void printStackTrace(PrintStream out)
+    {
+        super.printStackTrace(out);
+        for (Exception e : exceptions) {
+            e.printStackTrace(out);
+        }
+    }
+
+    @Override
+    public void printStackTrace(PrintWriter out)
+    {
+        super.printStackTrace(out);
+        for (Exception e : exceptions) {
+            e.printStackTrace(out);
+        }
+    }
+
+    public static void throwIf(String message, List<Exception> exceptions) throws MultiException {
+        if (exceptions != null && !exceptions.isEmpty()) {
+            StringBuilder sb = new StringBuilder(message);
+            sb.append(":");
+            for (Exception e : exceptions) {
+                sb.append("\n\t");
+                sb.append(e.getMessage());
+            }
+            throw new MultiException(sb.toString(), exceptions);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/management/FeaturesServiceMBean.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/management/FeaturesServiceMBean.java b/features/core/src/main/java/org/apache/karaf/features/management/FeaturesServiceMBean.java
index da4a36e..284f465 100644
--- a/features/core/src/main/java/org/apache/karaf/features/management/FeaturesServiceMBean.java
+++ b/features/core/src/main/java/org/apache/karaf/features/management/FeaturesServiceMBean.java
@@ -133,6 +133,6 @@ public interface FeaturesServiceMBean {
      * The item names in the CompositeData representing the event raised for
      * feature events within the OSGi container by this bean
      */
-    String[] REPOSITORY_EVENT = { REPOSITORY_NAME, REPOSITORY_URI, REPOSITORY_EVENT_EVENT_TYPE };
+    String[] REPOSITORY_EVENT = { REPOSITORY_URI, REPOSITORY_EVENT_EVENT_TYPE };
 
 }

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/management/codec/JmxRepositoryEvent.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/management/codec/JmxRepositoryEvent.java b/features/core/src/main/java/org/apache/karaf/features/management/codec/JmxRepositoryEvent.java
index bf7910a..e00e85d 100644
--- a/features/core/src/main/java/org/apache/karaf/features/management/codec/JmxRepositoryEvent.java
+++ b/features/core/src/main/java/org/apache/karaf/features/management/codec/JmxRepositoryEvent.java
@@ -36,11 +36,10 @@ public class JmxRepositoryEvent {
         try {
             String[] itemNames = FeaturesServiceMBean.REPOSITORY_EVENT;
             Object[] itemValues = new Object[itemNames.length];
-            itemValues[0] = event.getRepository().getName();
-            itemValues[1] = event.getRepository().getURI().toString();
+            itemValues[0] = event.getRepository().getURI().toString();
             switch (event.getType()) {
-                case RepositoryAdded:   itemValues[2] = FeaturesServiceMBean.REPOSITORY_EVENT_EVENT_TYPE_ADDED; break;
-                case RepositoryRemoved: itemValues[2] = FeaturesServiceMBean.REPOSITORY_EVENT_EVENT_TYPE_REMOVED; break;
+                case RepositoryAdded:   itemValues[1] = FeaturesServiceMBean.REPOSITORY_EVENT_EVENT_TYPE_ADDED; break;
+                case RepositoryRemoved: itemValues[1] = FeaturesServiceMBean.REPOSITORY_EVENT_EVENT_TYPE_REMOVED; break;
                 default: throw new IllegalStateException("Unsupported event type: " + event.getType());
             }
             data = new CompositeDataSupport(REPOSITORY_EVENT, itemNames, itemValues);
@@ -65,11 +64,9 @@ public class JmxRepositoryEvent {
             String[] itemDescriptions = new String[itemNames.length];
             itemTypes[0] = SimpleType.STRING;
             itemTypes[1] = SimpleType.STRING;
-            itemTypes[2] = SimpleType.STRING;
 
-            itemDescriptions[0] = "The name of the repository";
-            itemDescriptions[1] = "The uri of the repository";
-            itemDescriptions[2] = "The type of event";
+            itemDescriptions[0] = "The uri of the repository";
+            itemDescriptions[1] = "The type of event";
 
             return new CompositeType("RepositoryEvent", description, itemNames,
                     itemDescriptions, itemTypes);

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/management/internal/FeaturesServiceMBeanImpl.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/management/internal/FeaturesServiceMBeanImpl.java b/features/core/src/main/java/org/apache/karaf/features/management/internal/FeaturesServiceMBeanImpl.java
deleted file mode 100644
index 700e835..0000000
--- a/features/core/src/main/java/org/apache/karaf/features/management/internal/FeaturesServiceMBeanImpl.java
+++ /dev/null
@@ -1,274 +0,0 @@
-/*
- * Licensed 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.management.internal;
-
-import java.net.URI;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.EnumSet;
-import java.util.Hashtable;
-import java.util.List;
-
-import javax.management.MBeanNotificationInfo;
-import javax.management.MBeanRegistration;
-import javax.management.MBeanServer;
-import javax.management.NotCompliantMBeanException;
-import javax.management.Notification;
-import javax.management.ObjectName;
-import javax.management.openmbean.TabularData;
-
-import org.apache.karaf.features.*;
-import org.apache.karaf.features.management.FeaturesServiceMBean;
-import org.apache.karaf.features.management.codec.JmxFeature;
-import org.apache.karaf.features.management.codec.JmxFeatureEvent;
-import org.apache.karaf.features.management.codec.JmxRepository;
-import org.apache.karaf.features.management.codec.JmxRepositoryEvent;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.ServiceRegistration;
-
-/**
- * Implementation of {@link FeaturesServiceMBean}.
- */
-public class FeaturesServiceMBeanImpl extends StandardEmitterMBean implements
-        MBeanRegistration, FeaturesServiceMBean {
-
-    private ServiceRegistration<FeaturesListener> registration;
-
-    private BundleContext bundleContext;
-
-    private ObjectName objectName;
-
-    private volatile long sequenceNumber = 0;
-
-    private org.apache.karaf.features.FeaturesService featuresService;
-
-    public FeaturesServiceMBeanImpl() throws NotCompliantMBeanException {
-        super(FeaturesServiceMBean.class);
-    }
-
-    public ObjectName preRegister(MBeanServer server, ObjectName name) throws Exception {
-        objectName = name;
-        return name;
-    }
-
-    public void postRegister(Boolean registrationDone) {
-        registration = bundleContext.registerService(FeaturesListener.class,
-                getFeaturesListener(), new Hashtable<String, String>());
-    }
-
-    public void preDeregister() throws Exception {
-        registration.unregister();
-    }
-
-    public void postDeregister() {
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public TabularData getFeatures() throws Exception {
-        try {
-            List<Feature> allFeatures = Arrays.asList(featuresService.listFeatures());
-            List<Feature> insFeatures = Arrays.asList(featuresService.listInstalledFeatures());
-            ArrayList<JmxFeature> features = new ArrayList<JmxFeature>();
-            for (Feature feature : allFeatures) {
-                try {
-                    features.add(new JmxFeature(feature, insFeatures.contains(feature)));
-                } catch (Throwable t) {
-                    t.printStackTrace();
-                }
-            }
-            TabularData table = JmxFeature.tableFrom(features);
-            return table;
-        } catch (Throwable t) {
-            t.printStackTrace();
-            return null;
-        }
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public TabularData getRepositories() throws Exception {
-        try {
-            List<Repository> allRepositories = Arrays.asList(featuresService.listRepositories());
-            ArrayList<JmxRepository> repositories = new ArrayList<JmxRepository>();
-            for (Repository repository : allRepositories) {
-                try {
-                    repositories.add(new JmxRepository(repository));
-                } catch (Throwable t) {
-                    t.printStackTrace();
-                }
-            }
-            TabularData table = JmxRepository.tableFrom(repositories);
-            return table;
-        } catch (Throwable t) {
-            t.printStackTrace();
-            return null;
-        }
-    }
-
-    public void addRepository(String uri) throws Exception {
-        featuresService.addRepository(new URI(uri));
-    }
-
-    public void addRepository(String uri, boolean install) throws Exception {
-        featuresService.addRepository(new URI(uri), install);
-    }
-
-    public void removeRepository(String uri) throws Exception {
-        featuresService.removeRepository(new URI(uri));
-    }
-
-    public void removeRepository(String uri, boolean uninstall) throws Exception {
-        featuresService.removeRepository(new URI(uri), uninstall);
-    }
-
-    public void installFeature(String name) throws Exception {
-        featuresService.installFeature(name);
-    }
-
-    public void installFeature(String name, boolean noClean, boolean noRefresh) throws Exception {
-        EnumSet<org.apache.karaf.features.FeaturesService.Option> options = EnumSet.noneOf(org.apache.karaf.features.FeaturesService.Option.class);
-        if (noClean) {
-            options.add(org.apache.karaf.features.FeaturesService.Option.NoCleanIfFailure);
-        }
-        if (noRefresh) {
-            options.add(org.apache.karaf.features.FeaturesService.Option.NoAutoRefreshBundles);
-        }
-        featuresService.installFeature(name, options);
-    }
-
-    public void installFeature(String name, String version) throws Exception {
-        featuresService.installFeature(name, version);
-    }
-
-    public void installFeature(String name, String version, boolean noClean, boolean noRefresh) throws Exception {
-        EnumSet<org.apache.karaf.features.FeaturesService.Option> options = EnumSet.noneOf(org.apache.karaf.features.FeaturesService.Option.class);
-        if (noClean) {
-            options.add(org.apache.karaf.features.FeaturesService.Option.NoCleanIfFailure);
-        }
-        if (noRefresh) {
-            options.add(org.apache.karaf.features.FeaturesService.Option.NoAutoRefreshBundles);
-        }
-        featuresService.installFeature(name, version, options);
-    }
-
-    public TabularData infoFeature(String name) throws Exception {
-        try {
-            Feature feature = featuresService.getFeature(name);
-            return infoFeature(feature);
-        } catch (Throwable t) {
-            t.printStackTrace();
-            return null;
-        }
-    }
-
-    public TabularData infoFeature(String name, String version) throws Exception {
-        try {
-            Feature feature = featuresService.getFeature(name, version);
-            return infoFeature(feature);
-        } catch (Throwable t) {
-            t.printStackTrace();
-            return null;
-        }
-    }
-
-    private TabularData infoFeature(Feature feature) throws Exception {
-        JmxFeature jmxFeature = null;
-        if (featuresService.isInstalled(feature)) {
-            jmxFeature = new JmxFeature(feature, true);
-        } else {
-            jmxFeature = new JmxFeature(feature, false);
-        }
-        ArrayList<JmxFeature> features = new ArrayList<JmxFeature>();
-        features.add(jmxFeature);
-        TabularData table = JmxFeature.tableFrom(features);
-        return table;
-    }
-
-    public void uninstallFeature(String name) throws Exception {
-        featuresService.uninstallFeature(name);
-    }
-
-    public void uninstallFeature(String name, boolean noRefresh) throws Exception {
-        EnumSet<org.apache.karaf.features.FeaturesService.Option> options = EnumSet.noneOf(org.apache.karaf.features.FeaturesService.Option.class);
-        if (noRefresh) {
-            options.add(org.apache.karaf.features.FeaturesService.Option.NoAutoRefreshBundles);
-        }
-        featuresService.uninstallFeature(name, options);
-    }
-
-    public void uninstallFeature(String name, String version) throws Exception {
-        featuresService.uninstallFeature(name, version);
-    }
-
-    public void uninstallFeature(String name, String version, boolean noRefresh) throws Exception {
-        EnumSet<org.apache.karaf.features.FeaturesService.Option> options = EnumSet.noneOf(org.apache.karaf.features.FeaturesService.Option.class);
-        if (noRefresh) {
-            options.add(org.apache.karaf.features.FeaturesService.Option.NoAutoRefreshBundles);
-        }
-        featuresService.uninstallFeature(name, version, options);
-    }
-
-    public void setBundleContext(BundleContext bundleContext) {
-        this.bundleContext = bundleContext;
-    }
-
-    public void setFeaturesService(org.apache.karaf.features.FeaturesService featuresService) {
-        this.featuresService = featuresService;
-    }
-
-    public FeaturesListener getFeaturesListener() {
-        return new FeaturesListener() {
-            public void featureEvent(FeatureEvent event) {
-                if (!event.isReplay()) {
-                    Notification notification = new Notification(FEATURE_EVENT_TYPE, objectName, sequenceNumber++);
-                    notification.setUserData(new JmxFeatureEvent(event).asCompositeData());
-                    sendNotification(notification);
-                }
-            }
-
-            public void repositoryEvent(RepositoryEvent event) {
-                if (!event.isReplay()) {
-                    Notification notification = new Notification(REPOSITORY_EVENT_TYPE, objectName, sequenceNumber++);
-                    notification.setUserData(new JmxRepositoryEvent(event).asCompositeData());
-                    sendNotification(notification);
-                }
-            }
-
-            public boolean equals(Object o) {
-                if (this == o) {
-                    return true;
-                }
-                return o.equals(this);
-            }
-
-        };
-    }
-
-    public MBeanNotificationInfo[] getNotificationInfo() {
-        return getBroadcastInfo();
-    }
-
-    private static MBeanNotificationInfo[] getBroadcastInfo() {
-        String type = Notification.class.getCanonicalName();
-        MBeanNotificationInfo info1 = new MBeanNotificationInfo(new String[]{FEATURE_EVENT_EVENT_TYPE},
-                type, "Some features notification");
-        MBeanNotificationInfo info2 = new MBeanNotificationInfo(new String[]{REPOSITORY_EVENT_EVENT_TYPE},
-                type, "Some repository notification");
-        return new MBeanNotificationInfo[]{info1, info2};
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/management/internal/StandardEmitterMBean.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/management/internal/StandardEmitterMBean.java b/features/core/src/main/java/org/apache/karaf/features/management/internal/StandardEmitterMBean.java
deleted file mode 100644
index 9777897..0000000
--- a/features/core/src/main/java/org/apache/karaf/features/management/internal/StandardEmitterMBean.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Licensed 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.management.internal;
-
-import javax.management.*;
-
-public class StandardEmitterMBean extends StandardMBean implements NotificationEmitter {
-
-    private final NotificationBroadcasterSupport emitter;
-
-    @SuppressWarnings("rawtypes")
-	public StandardEmitterMBean(Class mbeanInterface) throws NotCompliantMBeanException {
-        super(mbeanInterface);
-        this.emitter = new NotificationBroadcasterSupport() {
-            @Override
-            public MBeanNotificationInfo[] getNotificationInfo() {
-                return StandardEmitterMBean.this.getNotificationInfo();
-            }
-        };
-    }
-
-    public void sendNotification(Notification notification) {
-        emitter.sendNotification(notification);
-    }
-
-
-    public void removeNotificationListener(NotificationListener listener, NotificationFilter filter, Object handback) throws ListenerNotFoundException {
-        emitter.removeNotificationListener(listener, filter, handback);
-    }
-
-    public void addNotificationListener(NotificationListener listener, NotificationFilter filter, Object handback) throws IllegalArgumentException {
-        emitter.addNotificationListener(listener, filter, handback);
-    }
-
-    public void removeNotificationListener(NotificationListener listener) throws ListenerNotFoundException {
-        emitter.removeNotificationListener(listener);
-    }
-
-    public MBeanNotificationInfo[] getNotificationInfo() {
-        return new MBeanNotificationInfo[0];
-    }
-
-    @Override
-    public MBeanInfo getMBeanInfo() {
-        MBeanInfo mbeanInfo = super.getMBeanInfo();
-        if (mbeanInfo != null) {
-            MBeanNotificationInfo[] notificationInfo = getNotificationInfo();
-            mbeanInfo = new MBeanInfo(mbeanInfo.getClassName(), mbeanInfo.getDescription(), mbeanInfo.getAttributes(),
-                    mbeanInfo.getConstructors(), mbeanInfo.getOperations(), notificationInfo);
-        }
-        return mbeanInfo;
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/resources/org/apache/karaf/features/karaf-features-1.3.0.xsd
----------------------------------------------------------------------
diff --git a/features/core/src/main/resources/org/apache/karaf/features/karaf-features-1.3.0.xsd b/features/core/src/main/resources/org/apache/karaf/features/karaf-features-1.3.0.xsd
new file mode 100644
index 0000000..60ec8d2
--- /dev/null
+++ b/features/core/src/main/resources/org/apache/karaf/features/karaf-features-1.3.0.xsd
@@ -0,0 +1,280 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+
+-->
+<xs:schema elementFormDefault="qualified"
+    targetNamespace="http://karaf.apache.org/xmlns/features/v1.3.0"
+    xmlns:tns="http://karaf.apache.org/xmlns/features/v1.3.0"
+    xmlns:xs="http://www.w3.org/2001/XMLSchema">
+
+    <xs:annotation>
+        <xs:documentation><![CDATA[
+Karaf features mechanism. For documentation please visit the
+<a href="http://karaf.apache.org/">Karaf website</a>.
+        ]]></xs:documentation>
+    </xs:annotation>
+
+    <xs:complexType name="features">
+        <xs:annotation>
+            <xs:documentation><![CDATA[
+Root element of Feature definition. It contains an required attribute for
+designating from which repository this feature should be loaded. The Karaf
+shell will show the repository name when displaying information about the feature.
+            ]]></xs:documentation>
+        </xs:annotation>
+        <xs:choice minOccurs="0" maxOccurs="unbounded">
+            <xs:element name="repository" type="xs:anyURI">
+                <xs:annotation>
+                    <xs:documentation><![CDATA[
+Additional repositories where dependencies are stored.
+                    ]]></xs:documentation>
+                </xs:annotation>
+            </xs:element>
+            <xs:element name="feature" type="tns:feature">
+                <xs:annotation>
+                    <xs:documentation><![CDATA[
+Feature definition.
+                    ]]></xs:documentation>
+                </xs:annotation>
+            </xs:element>
+        </xs:choice>
+        <xs:attribute name="name" type="xs:string" use="required"/>
+    </xs:complexType>
+
+    <xs:complexType name="feature">
+        <xs:annotation>
+            <xs:documentation><![CDATA[
+Definition of the Feature.
+            ]]></xs:documentation>
+        </xs:annotation>
+        <xs:choice minOccurs="0" maxOccurs="unbounded">
+            <xs:element name="details" minOccurs="0" type="xs:string">
+                <xs:annotation>
+                    <xs:documentation><![CDATA[
+The help text shown for this feature when using feature:info console command.
+                    ]]>
+                    </xs:documentation>
+                </xs:annotation>
+            </xs:element>
+            <xs:element name="config" type="tns:config" />
+            <xs:element name="configfile" type="tns:configFile" />
+            <xs:element name="feature" type="tns:dependency" />
+            <xs:element name="bundle" type="tns:bundle" />
+            <xs:element name="conditional" type="tns:conditional" />
+            <xs:element name="requirement" type="tns:requirement" />
+            <xs:element name="capability" type="tns:capability" />
+        </xs:choice>
+        <xs:attribute name="name" type="tns:featureName" use="required" />
+        <xs:attribute name="version" type="xs:string" default="0.0.0" />
+        <xs:attribute name="description" type="xs:string" />
+        <xs:attribute name="resolver" type="tns:resolver">
+            <xs:annotation>
+                <xs:documentation><![CDATA[
+Optional alternative resolver to use for determining the list of bundles to install for a given feature.
+                ]]>
+                </xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="install" type="tns:install">
+            <xs:annotation>
+                <xs:documentation><![CDATA[
+Marks if the feaute will be automatically started when thrown to the deploy folder.
+                ]]>
+                </xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="start-level" type="xs:int">
+             <xs:annotation>
+                <xs:documentation><![CDATA[
+Set this attribute to have an OSGi start level for this feature different
+from the default start level defined in Karaf's config.properties.
+                ]]>
+                </xs:documentation>
+             </xs:annotation>
+         </xs:attribute>
+    </xs:complexType>
+
+    <xs:complexType name="conditional">
+        <xs:annotation>
+            <xs:documentation><![CDATA[
+Definition of the Conditional.
+            ]]></xs:documentation>
+        </xs:annotation>
+        <xs:choice minOccurs="0" maxOccurs="unbounded">
+            <xs:element name="config" type="tns:config" />
+            <xs:element name="configfile" type="tns:configFile" />
+            <xs:element name="feature" type="tns:dependency" />
+            <xs:element name="bundle" type="tns:bundle" />
+            <xs:element name="condition" type="tns:dependency" minOccurs="0" maxOccurs="1" />
+        </xs:choice>
+    </xs:complexType>
+
+
+    <xs:complexType name="bundle">
+        <xs:annotation>
+            <xs:documentation><![CDATA[
+Deployable element to install.
+            ]]></xs:documentation>
+        </xs:annotation>
+        <xs:simpleContent>
+            <xs:extension base="xs:anyURI">
+                <xs:attribute name="start-level" type="xs:int">
+                    <xs:annotation>
+                        <xs:documentation><![CDATA[
+Set this attribute to have an OSGi start level for this bundle different
+from the default start level defined in the Karaf's config.properties.
+                        ]]>
+                        </xs:documentation>
+                    </xs:annotation>
+                </xs:attribute>
+                <xs:attribute name="start" type="xs:boolean" default="true">
+                    <xs:annotation>
+                        <xs:documentation><![CDATA[
+If false, leaves bundle in resolved state rather than the default active state.
+                        ]]>
+                        </xs:documentation>
+                    </xs:annotation>
+                </xs:attribute>
+                <xs:attribute name="dependency" type="xs:boolean">
+                    <xs:annotation>
+                        <xs:documentation><![CDATA[
+Mark this bundle as a dependency for the resolver.
+                        ]]>
+                        </xs:documentation>
+                    </xs:annotation>
+                </xs:attribute>
+            </xs:extension>
+        </xs:simpleContent>
+    </xs:complexType>
+
+    <xs:complexType name="dependency">
+        <xs:annotation>
+            <xs:documentation><![CDATA[
+Dependency of feature.
+            ]]></xs:documentation>
+        </xs:annotation>
+        <xs:simpleContent>
+            <xs:extension base="tns:featureName">
+                <xs:attribute name="version" type="xs:string" default="0.0.0" />
+            </xs:extension>
+        </xs:simpleContent>
+    </xs:complexType>
+
+    <xs:complexType name="config">
+        <xs:annotation>
+            <xs:documentation><![CDATA[
+Configuration entries which should be created during feature installation. This
+configuration may be used with OSGi Configuration Admin. The element content is
+read in as a properties file.
+            ]]></xs:documentation>
+        </xs:annotation>
+        <xs:simpleContent>
+            <xs:extension base="xs:string">
+                <xs:attribute name="name" type="xs:string" use="required" />
+            </xs:extension>
+        </xs:simpleContent>
+    </xs:complexType>
+
+    <xs:complexType name="configFile">
+        <xs:annotation>
+            <xs:documentation><![CDATA[
+Additional configuration files which should be created during feature installation.
+            ]]></xs:documentation>
+        </xs:annotation>
+        <xs:simpleContent>
+            <xs:extension base="xs:anyURI">
+                <xs:attribute name="finalname" type="xs:string" use="required">
+                    <xs:annotation>
+                        <xs:documentation><![CDATA[
+The final destination path and name for the configuration file.
+                        ]]></xs:documentation>
+                    </xs:annotation>
+                </xs:attribute>
+                <xs:attribute name="override" type="xs:boolean">
+                    <xs:annotation>
+                        <xs:documentation><![CDATA[
+If the configFile already exists at the finalname location, whether or not to replace it.
+                        ]]></xs:documentation>
+                    </xs:annotation>
+                </xs:attribute>
+
+            </xs:extension>
+        </xs:simpleContent>
+    </xs:complexType>
+
+    <xs:complexType name="requirement">
+        <xs:annotation>
+            <xs:documentation><![CDATA[
+Additional requirements of this feature.
+            ]]></xs:documentation>
+        </xs:annotation>
+        <xs:simpleContent>
+            <xs:extension base="xs:string">
+            </xs:extension>
+        </xs:simpleContent>
+    </xs:complexType>
+
+    <xs:complexType name="capability">
+        <xs:annotation>
+            <xs:documentation><![CDATA[
+Additional capability of this feature.
+            ]]></xs:documentation>
+        </xs:annotation>
+        <xs:simpleContent>
+            <xs:extension base="xs:string">
+            </xs:extension>
+        </xs:simpleContent>
+    </xs:complexType>
+
+    <xs:simpleType name="featureName">
+        <xs:annotation>
+            <xs:documentation><![CDATA[
+Feature name should be non empty string.
+            ]]></xs:documentation>
+        </xs:annotation>
+        <xs:restriction base="xs:string">
+            <xs:minLength value="1" />
+        </xs:restriction>
+    </xs:simpleType>
+
+    <xs:simpleType name="resolver">
+        <xs:annotation>
+            <xs:documentation><![CDATA[
+Resolver to use. Karaf will look for OSGi service which have following properties:
+objectClass: org.apache.karaf.features.Resolver
+name: the value
+            ]]></xs:documentation>
+        </xs:annotation>
+        <xs:restriction base="xs:string">
+            <xs:minLength value="1" />
+        </xs:restriction>
+    </xs:simpleType>
+    <xs:simpleType name="install">
+        <xs:annotation>
+            <xs:documentation><![CDATA[
+Installation mode. Can be either manual or auto. Specifies whether the feature should be automatically installed when
+dropped inside the deploy folder. Note: This attribute doesn't affect feature descriptors that are installed from the
+command line or as part of the org.apache.karaf.features.cfg.
+            ]]></xs:documentation>
+        </xs:annotation>
+        <xs:restriction base="xs:string">
+            <xs:minLength value="1" />
+        </xs:restriction>
+    </xs:simpleType>
+    <xs:element name="features" type="tns:features" />
+
+</xs:schema>

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/test/java/org/apache/karaf/features/ConditionalTest.java
----------------------------------------------------------------------
diff --git a/features/core/src/test/java/org/apache/karaf/features/ConditionalTest.java b/features/core/src/test/java/org/apache/karaf/features/ConditionalTest.java
index 21bd919..d5e7e46 100644
--- a/features/core/src/test/java/org/apache/karaf/features/ConditionalTest.java
+++ b/features/core/src/test/java/org/apache/karaf/features/ConditionalTest.java
@@ -16,15 +16,14 @@
  */
 package org.apache.karaf.features;
 
-import java.net.URI;
 import junit.framework.TestCase;
-import org.apache.karaf.features.internal.RepositoryImpl;
+import org.apache.karaf.features.internal.service.RepositoryImpl;
 
 
 public class ConditionalTest extends TestCase {
 
     public void testLoad() throws Exception {
-        RepositoryImpl r = new RepositoryImpl(getClass().getResource("internal/f06.xml").toURI());
+        RepositoryImpl r = new RepositoryImpl(getClass().getResource("internal/service/f06.xml").toURI());
         // Check repo
         Feature[] features = r.getFeatures();
         assertNotNull(features);


[03/13] git commit: Slightly more informative error reporting when resolution fails in the karaf-maven-plugin

Posted by gn...@apache.org.
Slightly more informative error reporting when resolution fails in the karaf-maven-plugin


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

Branch: refs/heads/master
Commit: 9605df3365d9d1f6309a7e3140aaaeb582fde4f0
Parents: 0d87c62
Author: Guillaume Nodet <gn...@gmail.com>
Authored: Thu Apr 3 09:53:23 2014 +0200
Committer: Guillaume Nodet <gn...@gmail.com>
Committed: Mon Apr 7 10:52:34 2014 +0200

----------------------------------------------------------------------
 .../karaf/tooling/features/AbstractFeatureMojo.java   | 14 +++++++++++---
 1 file changed, 11 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/karaf/blob/9605df33/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/features/AbstractFeatureMojo.java
----------------------------------------------------------------------
diff --git a/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/features/AbstractFeatureMojo.java b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/features/AbstractFeatureMojo.java
index 76e6492..1da45f9 100644
--- a/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/features/AbstractFeatureMojo.java
+++ b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/features/AbstractFeatureMojo.java
@@ -239,8 +239,12 @@ public abstract class AbstractFeatureMojo extends MojoSupport {
     
             getLog().info("Base repo: " + localRepo.getUrl());
             for (Feature feature : featuresSet) {
-                resolveArtifacts(feature.getBundles());
-                resolveArtifacts(feature.getConfigFiles());
+                try {
+                    resolveArtifacts(feature.getBundles());
+                    resolveArtifacts(feature.getConfigFiles());
+                } catch (RuntimeException e) {
+                    throw new RuntimeException("Error resolving feature " + feature.getName() + "/" + feature.getVersion(), e);
+                }
             }            
         } catch (Exception e) {
             throw new MojoExecutionException("Error populating repository", e);
@@ -253,7 +257,11 @@ public abstract class AbstractFeatureMojo extends MojoSupport {
             Artifact artifact = resourceToArtifact(artifactRef.getUrl(), skipNonMavenProtocols);
             if (artifact != null) {
                 artifactRef.setArtifact(artifact);
-                resolveArtifact(artifact, remoteRepos);
+                try {
+                    resolveArtifact(artifact, remoteRepos);
+                } catch (RuntimeException e) {
+                    throw new RuntimeException("Error resolving artifact " + artifactRef.getUrl(), e);
+                }
             }
             checkDoGarbageCollect();
         }


[04/13] [KARAF-2888] New FeaturesService based on the real OSGi resolver

Posted by gn...@apache.org.
http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/obr/src/test/java/org/apache/karaf/features/obr/internal/ObrResolverTest.java
----------------------------------------------------------------------
diff --git a/features/obr/src/test/java/org/apache/karaf/features/obr/internal/ObrResolverTest.java b/features/obr/src/test/java/org/apache/karaf/features/obr/internal/ObrResolverTest.java
deleted file mode 100644
index 0c2676b..0000000
--- a/features/obr/src/test/java/org/apache/karaf/features/obr/internal/ObrResolverTest.java
+++ /dev/null
@@ -1,174 +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.obr.internal;
-
-import static org.easymock.EasyMock.createMock;
-import static org.easymock.EasyMock.expect;
-import static org.easymock.EasyMock.replay;
-import static org.easymock.EasyMock.verify;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-
-import java.io.IOException;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.List;
-
-import org.apache.felix.bundlerepository.Reason;
-import org.apache.felix.bundlerepository.RepositoryAdmin;
-import org.apache.felix.bundlerepository.Requirement;
-import org.apache.felix.bundlerepository.Resolver;
-import org.apache.felix.bundlerepository.Resource;
-import org.apache.felix.bundlerepository.impl.DataModelHelperImpl;
-import org.apache.felix.bundlerepository.impl.ReasonImpl;
-import org.apache.karaf.features.BundleInfo;
-import org.apache.karaf.features.internal.model.Bundle;
-import org.easymock.Capture;
-import org.easymock.EasyMock;
-import org.easymock.IAnswer;
-import org.junit.Test;
-
-public class ObrResolverTest {
-
-    @Test
-    public void testResolver() throws Exception {
-        final String requirement = "bundle:(&(symbolicname=org.apache.camel.camel-blueprint)(version>=2.4.0)(version<2.4.1))";
-
-        final org.apache.karaf.features.internal.model.Feature f = new org.apache.karaf.features.internal.model.Feature("f1", "1.0");
-        f.setResolver("obr");
-        f.getBundle().add(new Bundle(requirement));
-        final RepositoryAdmin admin = createMock(RepositoryAdmin.class);
-        final Resolver resolver = createMock(Resolver.class);
-        final Resource resource = createMock(Resource.class);
-        final ObrResolver obrResolver = new ObrResolver();
-        obrResolver.setRepositoryAdmin(admin);
-
-        final Capture<Requirement> captureReq = new Capture<Requirement>();
-
-        expect(admin.getHelper()).andReturn(new DataModelHelperImpl()).anyTimes();
-        expect(admin.getSystemRepository()).andReturn(createMock(org.apache.felix.bundlerepository.Repository.class));
-        expect(admin.getLocalRepository()).andReturn(createMock(org.apache.felix.bundlerepository.Repository.class));
-        expect(admin.listRepositories()).andReturn(new org.apache.felix.bundlerepository.Repository[0]);
-        expect(admin.resolver(EasyMock.<org.apache.felix.bundlerepository.Repository[]>anyObject())).andReturn(resolver);
-        resolver.add(EasyMock.capture(captureReq));
-        expect(resolver.resolve(Resolver.NO_OPTIONAL_RESOURCES)).andReturn(true);
-        expect(resolver.getAddedResources()).andReturn(new Resource[] { });
-        expect(resolver.getRequiredResources()).andReturn(new Resource[] { resource });
-        expect(resolver.getReason(resource)).andAnswer(new IAnswer<Reason[]>() {
-            public Reason[] answer() throws Throwable {
-                return new Reason[] { new ReasonImpl( resource, captureReq.getValue()) };
-            }
-        });
-        expect(resource.getURI()).andReturn("foo:bar");
-        replay(admin, resolver, resource);
-
-        List<BundleInfo> bundles = obrResolver.resolve(f);
-        assertNotNull(bundles);
-        assertEquals(1, bundles.size());
-        assertEquals("foo:bar", bundles.get(0).getLocation());
-        assertEquals(obrResolver.parseRequirement(requirement).toString(), captureReq.getValue().toString());
-        verify(admin, resolver, resource);
-    }
-
-    @Test
-    public void testResolverWithOptionalImports() throws Exception {
-        final String requirement = "bundle:(&(symbolicname=org.apache.camel.camel-blueprint)(version>=2.4.0)(version<2.4.1))";
-
-        final org.apache.karaf.features.internal.model.Feature f = new org.apache.karaf.features.internal.model.Feature("f1", "1.0");
-        f.setResolver("obr");
-        f.getBundle().add(new Bundle(requirement));
-        final RepositoryAdmin admin = createMock(RepositoryAdmin.class);
-        final Resolver resolver = createMock(Resolver.class);
-        final Resource resource = createMock(Resource.class);
-        final Resource optionalResource = createMock(Resource.class);
-        final ObrResolver obrResolver = new ObrResolver();
-        obrResolver.setRepositoryAdmin(admin);
-        obrResolver.setResolveOptionalImports(true);
-
-        final Capture<Requirement> captureReq = new Capture<Requirement>();
-
-        expect(admin.getHelper()).andReturn(new DataModelHelperImpl()).anyTimes();
-        expect(admin.getSystemRepository()).andReturn(createMock(org.apache.felix.bundlerepository.Repository.class));
-        expect(admin.getLocalRepository()).andReturn(createMock(org.apache.felix.bundlerepository.Repository.class));
-        expect(admin.listRepositories()).andReturn(new org.apache.felix.bundlerepository.Repository[0]);
-        expect(admin.resolver(EasyMock.<org.apache.felix.bundlerepository.Repository[]>anyObject())).andReturn(resolver);
-        resolver.add(EasyMock.capture(captureReq));
-        expect(resolver.resolve()).andReturn(true);
-        expect(resolver.getAddedResources()).andReturn(new Resource[] { });
-        expect(resolver.getRequiredResources()).andReturn(new Resource[] { resource });
-        expect(resolver.getOptionalResources()).andReturn(new Resource[] { optionalResource});
-        expect(resolver.getReason(resource)).andAnswer(new IAnswer<Reason[]>() {
-            public Reason[] answer() throws Throwable {
-                return new Reason[] { new ReasonImpl( resource, captureReq.getValue()) };
-            }
-        });
-        expect(resolver.getReason(optionalResource)).andAnswer(new IAnswer<Reason[]>() {
-            public Reason[] answer() throws Throwable {
-                return new Reason[] { new ReasonImpl( optionalResource, captureReq.getValue()) };
-            }
-        });
-        expect(resource.getURI()).andReturn("foo:bar");
-        expect(optionalResource.getURI()).andReturn("foo:optional:baz");
-        replay(admin, resolver, resource, optionalResource);
-
-        List<BundleInfo> bundles = obrResolver.resolve(f);
-        assertNotNull(bundles);
-        assertEquals(2, bundles.size());
-        assertEquals("foo:bar", bundles.get(0).getLocation());
-        assertEquals("foo:optional:baz", bundles.get(1).getLocation());
-        assertEquals(obrResolver.parseRequirement(requirement).toString(), captureReq.getValue().toString());
-        verify(admin, resolver, resource);
-    }
-    
-    /**
-     * Test resolving a mvn url when pax url is configured with a repo that contains no protocol like: "test"
-     * We expect to get a MalFormedUrlException not a IllegalArgumentException as in the issue 
-     * @throws Exception
-     */
-    @Test(expected=MalformedURLException.class)
-    public void testResolverWithInvalidMvnRepoIssueKaraf667() throws Exception {
-    	final org.apache.karaf.features.internal.model.Feature f = new org.apache.karaf.features.internal.model.Feature("f1", "1.0");
-        f.setResolver("obr");
-        // Using file instead of mvn: as we do not want to mess with URL handlers
-        f.getBundle().add(new Bundle("file:org.foo/foo/1.0"));
-
-        final RepositoryAdmin admin = createMock(RepositoryAdmin.class);
-        final Resolver resolver = createMock(Resolver.class);
-        final Resource resource = createMock(Resource.class);
-        final ObrResolver obrResolver = new ObrResolver();
-        obrResolver.setRepositoryAdmin(admin);
-
-        expect(admin.getHelper()).andReturn(new DummyDataModelHelper()).anyTimes();
-        replay(admin, resolver, resource);
-
-       	obrResolver.resolve(f);
-    }
-    
-    /**
-     * Recreates behaviour of DataModelHelper when an invalid maven url is set for pax url repos
-     * TODO Remove as soon as DataModelHelper does not throw this exception any more  
-     */
-    class DummyDataModelHelper extends DataModelHelperImpl {
-
-		@Override
-		public Resource createResource(URL bundleUrl) throws IOException {
-			throw new MalformedURLException("Missing protocol");
-		}
-
-    	
-    }
-}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/pom.xml
----------------------------------------------------------------------
diff --git a/features/pom.xml b/features/pom.xml
index 7597051..8cfede1 100644
--- a/features/pom.xml
+++ b/features/pom.xml
@@ -36,7 +36,6 @@
     <modules>
         <module>core</module>
         <module>command</module>
-        <module>obr</module>
     </modules>
 
 </project>

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/instance/core/pom.xml
----------------------------------------------------------------------
diff --git a/instance/core/pom.xml b/instance/core/pom.xml
index 4c745ec..0180bd3 100644
--- a/instance/core/pom.xml
+++ b/instance/core/pom.xml
@@ -148,6 +148,9 @@
                         <Export-Package>
                             org.apache.karaf.instance.core,
                         </Export-Package>
+                        <Provide-Capability>
+                            service-reference;effective:=active;objectClass=org.apache.karaf.instance.core.InstanceService
+                        </Provide-Capability>
                         <Private-Package>
                             org.apache.karaf.jpm,
                             org.apache.karaf.jpm.impl,

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/itests/src/test/java/org/apache/karaf/itests/ConditionalFeaturesTest.java
----------------------------------------------------------------------
diff --git a/itests/src/test/java/org/apache/karaf/itests/ConditionalFeaturesTest.java b/itests/src/test/java/org/apache/karaf/itests/ConditionalFeaturesTest.java
index 46908d1..9045b82 100644
--- a/itests/src/test/java/org/apache/karaf/itests/ConditionalFeaturesTest.java
+++ b/itests/src/test/java/org/apache/karaf/itests/ConditionalFeaturesTest.java
@@ -70,7 +70,7 @@ public class ConditionalFeaturesTest extends KarafTestSupport {
     @Test
     public void testWebconsole() throws Exception {
         try {
-            featureService.uninstallFeature("eventadmin");
+            featureService.uninstallFeature("scr");
         } catch (Exception e) {
         }
         featureService.installFeature("webconsole");

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/itests/src/test/java/org/apache/karaf/itests/FeatureSshCommandSecurityTest.java
----------------------------------------------------------------------
diff --git a/itests/src/test/java/org/apache/karaf/itests/FeatureSshCommandSecurityTest.java b/itests/src/test/java/org/apache/karaf/itests/FeatureSshCommandSecurityTest.java
index 76350c0..223c9a2 100644
--- a/itests/src/test/java/org/apache/karaf/itests/FeatureSshCommandSecurityTest.java
+++ b/itests/src/test/java/org/apache/karaf/itests/FeatureSshCommandSecurityTest.java
@@ -24,31 +24,32 @@ public class FeatureSshCommandSecurityTest extends SshCommandTestBase {
     @Test
     public void testFeatureCommandSecurityViaSsh() throws Exception {
         String vieweruser = "viewer" + System.nanoTime() + "_features";
+        String feature = "wrapper";
 
         addViewer(vieweruser);
 
         String r = assertCommand(vieweruser, "feature:list -i --no-format", Result.OK);
-        Assert.assertFalse("Precondition failed, this test uses the eventadmin subsystem to test features with...",
-                r.contains("eventadmin"));
+        Assert.assertFalse("Precondition failed, this test uses the " + feature + " subsystem to test features with...",
+                r.contains(feature));
 
-        assertCommand(vieweruser, "feature:install eventadmin", Result.NOT_FOUND);
+        assertCommand(vieweruser, "feature:install " + feature, Result.NOT_FOUND);
         String r2 = assertCommand("karaf", "feature:list -i --no-format", Result.OK);
-        Assert.assertFalse("eventadmin features should not have been installed, as viewer doesn't have credentials",
-                r2.contains("eventadmin"));
+        Assert.assertFalse(feature + " features should not have been installed, as viewer doesn't have credentials",
+                r2.contains(feature));
 
-        assertCommand("karaf", "feature:install eventadmin", Result.OK);
+        assertCommand("karaf", "feature:install " + feature, Result.OK);
         String r3 = assertCommand(vieweruser, "feature:list -i --no-format", Result.OK);
-        Assert.assertTrue("eventadmin feature should have been installed by 'karaf' user",
-                r3.contains("eventadmin"));
+        Assert.assertTrue(feature + " feature should have been installed by 'karaf' user",
+                r3.contains(feature));
 
-        assertCommand(vieweruser, "feature:uninstall eventadmin", Result.NOT_FOUND);
+        assertCommand(vieweruser, "feature:uninstall " + feature, Result.NOT_FOUND);
         String r4 = assertCommand("karaf", "feature:list -i --no-format", Result.OK);
-        Assert.assertTrue("eventadmin feature should still be there, as viewer doesn't have credentials",
-                r4.contains("eventadmin"));
+        Assert.assertTrue(feature + " feature should still be there, as viewer doesn't have credentials",
+                r4.contains(feature));
 
-        assertCommand("karaf", "feature:uninstall eventadmin", Result.OK);
+        assertCommand("karaf", "feature:uninstall " + feature, Result.OK);
         String r5 = assertCommand(vieweruser, "feature:list -i --no-format", Result.OK);
-        Assert.assertFalse("The eventadmin subsystem should have been uninstalled",
-                r5.contains("eventadmin"));
+        Assert.assertFalse(feature + " feature should have been uninstalled",
+                r5.contains(feature));
     }
 }

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/itests/src/test/java/org/apache/karaf/itests/FeatureTest.java
----------------------------------------------------------------------
diff --git a/itests/src/test/java/org/apache/karaf/itests/FeatureTest.java b/itests/src/test/java/org/apache/karaf/itests/FeatureTest.java
index 2098683..c9d9c77 100644
--- a/itests/src/test/java/org/apache/karaf/itests/FeatureTest.java
+++ b/itests/src/test/java/org/apache/karaf/itests/FeatureTest.java
@@ -64,14 +64,14 @@ public class FeatureTest extends KarafTestSupport {
 
     @Test
     public void installUninstallCommand() throws Exception {
-        String featureInstallOutput = executeCommand("feature:install -v eventadmin", new RolePrincipal("admin"));
+        String featureInstallOutput = executeCommand("feature:install -v wrapper", new RolePrincipal("admin"));
         System.out.println(featureInstallOutput);
         assertFalse(featureInstallOutput.isEmpty());
-        String featureListOutput = executeCommand("feature:list -i | grep eventadmin");
+        String featureListOutput = executeCommand("feature:list -i | grep wrapper");
         System.out.println(featureListOutput);
         assertFalse(featureListOutput.isEmpty());
-        System.out.println(executeCommand("feature:uninstall eventadmin", new RolePrincipal("admin")));
-        featureListOutput = executeCommand("feature:list -i | grep eventadmin");
+        System.out.println(executeCommand("feature:uninstall wrapper", new RolePrincipal("admin")));
+        featureListOutput = executeCommand("feature:list -i | grep wrapper");
         System.out.println(featureListOutput);
         assertTrue(featureListOutput.isEmpty());
     }
@@ -83,8 +83,8 @@ public class FeatureTest extends KarafTestSupport {
             connector = this.getJMXConnector();
             MBeanServerConnection connection = connector.getMBeanServerConnection();
             ObjectName name = new ObjectName("org.apache.karaf:type=feature,name=root");
-            connection.invoke(name, "installFeature", new Object[] { "eventadmin" }, new String[]{ "java.lang.String" });
-            connection.invoke(name, "uninstallFeature", new Object[] { "eventadmin" }, new String[]{ "java.lang.String" });
+            connection.invoke(name, "installFeature", new Object[] { "wrapper" }, new String[]{ "java.lang.String" });
+            connection.invoke(name, "uninstallFeature", new Object[] { "wrapper" }, new String[]{ "java.lang.String" });
         } finally {
         	close(connector);
         }

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/itests/src/test/java/org/apache/karaf/itests/HttpTest.java
----------------------------------------------------------------------
diff --git a/itests/src/test/java/org/apache/karaf/itests/HttpTest.java b/itests/src/test/java/org/apache/karaf/itests/HttpTest.java
index 459301b..66ea463 100644
--- a/itests/src/test/java/org/apache/karaf/itests/HttpTest.java
+++ b/itests/src/test/java/org/apache/karaf/itests/HttpTest.java
@@ -26,6 +26,7 @@ import org.junit.runner.RunWith;
 import org.ops4j.pax.exam.junit.PaxExam;
 import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
 import org.ops4j.pax.exam.spi.reactors.PerClass;
+import org.osgi.framework.Constants;
 
 @RunWith(PaxExam.class)
 @ExamReactorStrategy(PerClass.class)
@@ -38,6 +39,7 @@ public class HttpTest extends KarafTestSupport {
 
     @Test
     public void list() throws Exception {
+        waitForService("(objectClass=javax.servlet.ServletContext)", 5000);
         assertContains("/system/console", executeCommand("http:list"));
     }
 

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/itests/src/test/java/org/apache/karaf/itests/KarafTestSupport.java
----------------------------------------------------------------------
diff --git a/itests/src/test/java/org/apache/karaf/itests/KarafTestSupport.java b/itests/src/test/java/org/apache/karaf/itests/KarafTestSupport.java
index 5062c64..207d75a 100644
--- a/itests/src/test/java/org/apache/karaf/itests/KarafTestSupport.java
+++ b/itests/src/test/java/org/apache/karaf/itests/KarafTestSupport.java
@@ -302,7 +302,7 @@ public class KarafTestSupport {
         }
     }
 
-    private void waitForService(String filter, long timeout) throws InvalidSyntaxException, InterruptedException {
+    protected void waitForService(String filter, long timeout) throws InvalidSyntaxException, InterruptedException {
         ServiceTracker<Object, Object> st = new ServiceTracker<Object, Object>(bundleContext, bundleContext.createFilter(filter), null);
         try {
             st.open();
@@ -351,23 +351,27 @@ public class KarafTestSupport {
     }
 
     public void assertFeatureInstalled(String featureName) throws Exception {
-        Feature[] features = featureService.listInstalledFeatures();
-        for (Feature feature : features) {
-            if (featureName.equals(feature.getName())) {
-                return;
-            }
+        String name;
+        String version;
+        if (featureName.contains("/")) {
+            name = featureName.substring(0, featureName.indexOf("/"));
+            version = featureName.substring(featureName.indexOf("/") + 1);
+        } else {
+            name = featureName;
+            version = null;
         }
-        Assert.fail("Feature " + featureName + " should be installed but is not");
+        assertFeatureInstalled(name, version);
     }
 
     public void assertFeatureInstalled(String featureName, String featureVersion) throws Exception {
+        Feature featureToAssert = featureService.getFeature(featureName, featureVersion);
         Feature[] features = featureService.listInstalledFeatures();
         for (Feature feature : features) {
-            if (featureName.equals(feature.getName()) && featureVersion.equals(feature.getVersion())) {
+            if (featureToAssert.equals(feature)) {
                 return;
             }
         }
-        Assert.fail("Feature " + featureName + "/" + featureVersion + " should be installed but is not");
+        Assert.fail("Feature " + featureName + (featureVersion != null ? "/" + featureVersion : "") + " should be installed but is not");
     }
 
     public void assertFeaturesInstalled(String ... expectedFeatures) throws Exception {
@@ -412,24 +416,28 @@ public class KarafTestSupport {
     }
 
     protected void installAssertAndUninstallFeature(String feature, String version) throws Exception {
-        Set<Feature> featuresBefore = new HashSet<Feature>(Arrays.asList(featureService.listInstalledFeatures()));
-        try {
-            featureService.installFeature(feature, version);
-            assertFeatureInstalled(feature, version);
-        } finally {
-            uninstallNewFeatures(featuresBefore);
-        }
+        installAssertAndUninstallFeatures(feature + "/" + version);
     }
 
     protected void installAssertAndUninstallFeatures(String... feature) throws Exception {
-    	Set<Feature> featuresBefore = new HashSet<Feature>(Arrays.asList(featureService.listInstalledFeatures()));
+        boolean success = false;
     	try {
 			for (String curFeature : feature) {
 				featureService.installFeature(curFeature);
 			    assertFeatureInstalled(curFeature);
 			}
+            success = true;
 		} finally {
-			uninstallNewFeatures(featuresBefore);
+            for (String curFeature : feature) {
+                System.out.println("Uninstalling " + curFeature);
+                try {
+                    featureService.uninstallFeature(curFeature);
+                } catch (Exception e) {
+                    if (success) {
+                        throw e;
+                    }
+                }
+            }
 		}
     }
 

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/itests/src/test/java/org/apache/karaf/itests/features/Spring3FeaturesTest.java
----------------------------------------------------------------------
diff --git a/itests/src/test/java/org/apache/karaf/itests/features/Spring3FeaturesTest.java b/itests/src/test/java/org/apache/karaf/itests/features/Spring3FeaturesTest.java
index 22e1113..19b6f43 100644
--- a/itests/src/test/java/org/apache/karaf/itests/features/Spring3FeaturesTest.java
+++ b/itests/src/test/java/org/apache/karaf/itests/features/Spring3FeaturesTest.java
@@ -14,6 +14,7 @@
 package org.apache.karaf.itests.features;
 
 import org.apache.karaf.itests.KarafTestSupport;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.ops4j.pax.exam.junit.PaxExam;
@@ -70,6 +71,7 @@ public class Spring3FeaturesTest extends KarafTestSupport {
     }
 
     @Test
+    @Ignore
     public void installSpringOrmFeature() throws Exception {
         installAssertAndUninstallFeature("spring-orm", System.getProperty("spring32.version"));
     }
@@ -85,11 +87,13 @@ public class Spring3FeaturesTest extends KarafTestSupport {
     }
 
     @Test
+    @Ignore
     public void installSpringWebFeature() throws Exception {
         installAssertAndUninstallFeature("spring-web", System.getProperty("spring32.version"));
     }
 
     @Test
+    @Ignore
     public void installSpringWebPortletFeature() throws Exception {
         installAssertAndUninstallFeature("spring-web-portlet", System.getProperty("spring32.version"));
     }

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/management/server/pom.xml
----------------------------------------------------------------------
diff --git a/management/server/pom.xml b/management/server/pom.xml
index 4c92be9..c522874 100644
--- a/management/server/pom.xml
+++ b/management/server/pom.xml
@@ -117,6 +117,9 @@
                             org.apache.karaf.service.guard.tools,
                             org.apache.karaf.util.tracker
                         </Private-Package>
+                        <Provide-Capability>
+                            service-reference;effective:=active;objectClass=javax.management.MBeanServer
+                        </Provide-Capability>
                         <Bundle-Activator>
                             org.apache.karaf.management.internal.Activator
                         </Bundle-Activator>

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 4f78489..3d90475 100644
--- a/pom.xml
+++ b/pom.xml
@@ -172,6 +172,7 @@
         <felix.scr.version>1.8.2</felix.scr.version>
         <felix.scr.webconsole.plugin.version>1.0.0</felix.scr.webconsole.plugin.version>
         <felix.scr.annotation.version>1.6.0</felix.scr.annotation.version>
+        <felix.resolver.version>1.0.0</felix.resolver.version>
 
         <aries.application.version>1.0.0</aries.application.version>
         <aries.application.api.version>1.0.0</aries.application.api.version>
@@ -998,6 +999,11 @@
                 <artifactId>org.apache.felix.scr</artifactId>
                 <version>${felix.scr.version}</version>
             </dependency>
+            <dependency>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>org.apache.felix.resolver</artifactId>
+                <version>${felix.resolver.version}</version>
+            </dependency>
 
             <dependency>
                 <groupId>org.ow2.asm</groupId>

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/shell/console/pom.xml
----------------------------------------------------------------------
diff --git a/shell/console/pom.xml b/shell/console/pom.xml
index 69f1dbb..90cdeb5 100644
--- a/shell/console/pom.xml
+++ b/shell/console/pom.xml
@@ -148,27 +148,27 @@
                             *
                         </Import-Package>
                         <Export-Package>
-                        	org.apache.karaf.shell.commands;version=${project.version},
-                            org.apache.karaf.shell.commands;version=2.3.0,
-                            org.apache.karaf.shell.commands.basic;version=${project.version},
-                            org.apache.karaf.shell.commands.basic;version=2.3.0,
-                            org.apache.karaf.shell.commands.converter;version=${project.version},
-                            org.apache.karaf.shell.commands.converter;version=2.3.0,
-                            org.apache.karaf.shell.commands.meta;version=${project.version},
-                            org.apache.karaf.shell.commands.meta;version=2.3.0,
-                        	org.apache.karaf.shell.console;version=${project.version},
-                            org.apache.karaf.shell.console;version=2.3.0,
-                        	org.apache.karaf.shell.console.commands;version=${project.version},
-                            org.apache.karaf.shell.console.commands;version=2.3.0,
-                        	org.apache.karaf.shell.console.completer;version=${project.version},
-                            org.apache.karaf.shell.console.completer;version=2.3.0,
-                            org.apache.karaf.shell.console.factory;version=${project.version},
-                            org.apache.karaf.shell.inject;version=${project.version},
-                        	org.apache.karaf.shell.util;version=${project.version},
-                            org.apache.karaf.shell.util;version=2.3.0,
-                            org.apache.felix.gogo*;version=${felix.gogo.version},
-                            org.apache.felix.service.command;version=${felix.gogo.version};status=provisional;mandatory:=status,
-                            org.apache.felix.service.threadio;version=${felix.gogo.version};status=provisional;mandatory:=status,
+                        	org.apache.karaf.shell.commands;version=${project.version};-noimport:=true,
+                            org.apache.karaf.shell.commands;version=2.3.0;-noimport:=true,
+                            org.apache.karaf.shell.commands.basic;version=${project.version};-noimport:=true,
+                            org.apache.karaf.shell.commands.basic;version=2.3.0;-noimport:=true,
+                            org.apache.karaf.shell.commands.converter;version=${project.version};-noimport:=true,
+                            org.apache.karaf.shell.commands.converter;version=2.3.0;-noimport:=true,
+                            org.apache.karaf.shell.commands.meta;version=${project.version};-noimport:=true,
+                            org.apache.karaf.shell.commands.meta;version=2.3.0;-noimport:=true,
+                        	org.apache.karaf.shell.console;version=${project.version};-noimport:=true,
+                            org.apache.karaf.shell.console;version=2.3.0;-noimport:=true,
+                        	org.apache.karaf.shell.console.commands;version=${project.version};-noimport:=true,
+                            org.apache.karaf.shell.console.commands;version=2.3.0;-noimport:=true,
+                        	org.apache.karaf.shell.console.completer;version=${project.version};-noimport:=true,
+                            org.apache.karaf.shell.console.completer;version=2.3.0;-noimport:=true,
+                            org.apache.karaf.shell.console.factory;version=${project.version};-noimport:=true,
+                            org.apache.karaf.shell.inject;version=${project.version};-noimport:=true,
+                        	org.apache.karaf.shell.util;version=${project.version};-noimport:=true,
+                            org.apache.karaf.shell.util;version=2.3.0;-noimport:=true,
+                            org.apache.felix.gogo*;version=${felix.gogo.version};-noimport:=true,
+                            org.apache.felix.service.command;version=${felix.gogo.version};status=provisional;mandatory:=status;-noimport:=true,
+                            org.apache.felix.service.threadio;version=${felix.gogo.version};status=provisional;mandatory:=status;-noimport:=true,
                         </Export-Package>
                         <Private-Package>
                         	org.apache.karaf.shell.commands.ansi,

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/shell/core/pom.xml
----------------------------------------------------------------------
diff --git a/shell/core/pom.xml b/shell/core/pom.xml
index c5a0bac..92c7555 100644
--- a/shell/core/pom.xml
+++ b/shell/core/pom.xml
@@ -165,6 +165,9 @@
                             org.apache.felix.service.command,
                             org.apache.felix.service.threadio,
                         </Private-Package>
+                        <Provide-Capability>
+                            service-reference;effective:=active;objectClass=org.apache.karaf.shell.api.console.SessionFactory
+                        </Provide-Capability>
                         <Bundle-Activator>
                             org.apache.karaf.shell.impl.console.osgi.Activator
                         </Bundle-Activator>

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/tooling/karaf-maven-plugin/src/it/test-aggregate-features/control.xml
----------------------------------------------------------------------
diff --git a/tooling/karaf-maven-plugin/src/it/test-aggregate-features/control.xml b/tooling/karaf-maven-plugin/src/it/test-aggregate-features/control.xml
index 7c50b96..01a6721 100644
--- a/tooling/karaf-maven-plugin/src/it/test-aggregate-features/control.xml
+++ b/tooling/karaf-maven-plugin/src/it/test-aggregate-features/control.xml
@@ -18,7 +18,7 @@
   ~ under the License.
   -->
 
-<features xmlns="http://karaf.apache.org/xmlns/features/v1.2.0" name="aggregate-features">
+<features xmlns="http://karaf.apache.org/xmlns/features/v1.3.0" name="aggregate-features">
     <feature description="aggregate-recursive-module-c" version="1.0-SNAPSHOT" name="aggregate-recursive-module-c">
         <details>Test Description</details>
         <bundle>mvn:test/aggregate-recursive-module-b/1.0-SNAPSHOT</bundle>

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/tooling/karaf-maven-plugin/src/it/test-basic-generation/control.xml
----------------------------------------------------------------------
diff --git a/tooling/karaf-maven-plugin/src/it/test-basic-generation/control.xml b/tooling/karaf-maven-plugin/src/it/test-basic-generation/control.xml
index 06139fe..2318b17 100644
--- a/tooling/karaf-maven-plugin/src/it/test-basic-generation/control.xml
+++ b/tooling/karaf-maven-plugin/src/it/test-basic-generation/control.xml
@@ -18,4 +18,4 @@
   ~ under the License.
   -->
 
-<features xmlns="http://karaf.apache.org/xmlns/features/v1.2.0" name="test-basic-generation"/>
+<features xmlns="http://karaf.apache.org/xmlns/features/v1.3.0" name="test-basic-generation"/>

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/tooling/karaf-maven-plugin/src/it/test-check-dependencies-failure/control.xml
----------------------------------------------------------------------
diff --git a/tooling/karaf-maven-plugin/src/it/test-check-dependencies-failure/control.xml b/tooling/karaf-maven-plugin/src/it/test-check-dependencies-failure/control.xml
index a1a81c4..223d090 100644
--- a/tooling/karaf-maven-plugin/src/it/test-check-dependencies-failure/control.xml
+++ b/tooling/karaf-maven-plugin/src/it/test-check-dependencies-failure/control.xml
@@ -18,7 +18,7 @@
   ~ under the License.
   -->
 
-<features xmlns="http://karaf.apache.org/xmlns/features/v1.2.0" name="check-dependencies-features">
+<features xmlns="http://karaf.apache.org/xmlns/features/v1.3.0" name="check-dependencies-features">
     <feature description="dependency-module-c" version="1.0-SNAPSHOT" name="dependency-module-c">
         <bundle>mvn:test/dependency-module-a/1.0-SNAPSHOT</bundle>
         <bundle>mvn:test/dependency-module-b/1.0-SNAPSHOT</bundle>

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/tooling/karaf-maven-plugin/src/it/test-check-dependencies/control.xml
----------------------------------------------------------------------
diff --git a/tooling/karaf-maven-plugin/src/it/test-check-dependencies/control.xml b/tooling/karaf-maven-plugin/src/it/test-check-dependencies/control.xml
index a1a81c4..223d090 100644
--- a/tooling/karaf-maven-plugin/src/it/test-check-dependencies/control.xml
+++ b/tooling/karaf-maven-plugin/src/it/test-check-dependencies/control.xml
@@ -18,7 +18,7 @@
   ~ under the License.
   -->
 
-<features xmlns="http://karaf.apache.org/xmlns/features/v1.2.0" name="check-dependencies-features">
+<features xmlns="http://karaf.apache.org/xmlns/features/v1.3.0" name="check-dependencies-features">
     <feature description="dependency-module-c" version="1.0-SNAPSHOT" name="dependency-module-c">
         <bundle>mvn:test/dependency-module-a/1.0-SNAPSHOT</bundle>
         <bundle>mvn:test/dependency-module-b/1.0-SNAPSHOT</bundle>

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/tooling/karaf-maven-plugin/src/it/test-input-file/control.xml
----------------------------------------------------------------------
diff --git a/tooling/karaf-maven-plugin/src/it/test-input-file/control.xml b/tooling/karaf-maven-plugin/src/it/test-input-file/control.xml
index 25a2acb..08e8f0e 100644
--- a/tooling/karaf-maven-plugin/src/it/test-input-file/control.xml
+++ b/tooling/karaf-maven-plugin/src/it/test-input-file/control.xml
@@ -18,7 +18,7 @@
   ~ under the License.
   -->
 
-<features xmlns="http://karaf.apache.org/xmlns/features/v1.2.0" name="test-input-file">
+<features xmlns="http://karaf.apache.org/xmlns/features/v1.3.0" name="test-input-file">
     <feature description="Test Description" version="1.0-SNAPSHOT" name="test-input-file">
         <details>Test Description</details>
         <bundle>mvn:test/test-input-file/1.0-SNAPSHOT</bundle>

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/tooling/karaf-maven-plugin/src/it/test-type-classifier/control.xml
----------------------------------------------------------------------
diff --git a/tooling/karaf-maven-plugin/src/it/test-type-classifier/control.xml b/tooling/karaf-maven-plugin/src/it/test-type-classifier/control.xml
index 9b84d85..a378636 100644
--- a/tooling/karaf-maven-plugin/src/it/test-type-classifier/control.xml
+++ b/tooling/karaf-maven-plugin/src/it/test-type-classifier/control.xml
@@ -18,4 +18,4 @@
   ~ under the License.
   -->
 
-<features xmlns="http://karaf.apache.org/xmlns/features/v1.2.0" name="test-type-classifier"/>
+<features xmlns="http://karaf.apache.org/xmlns/features/v1.3.0" name="test-type-classifier"/>

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/features/ValidateDescriptorMojo.java
----------------------------------------------------------------------
diff --git a/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/features/ValidateDescriptorMojo.java b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/features/ValidateDescriptorMojo.java
index bcf608a..f5cb55f 100644
--- a/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/features/ValidateDescriptorMojo.java
+++ b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/features/ValidateDescriptorMojo.java
@@ -22,8 +22,8 @@ import org.apache.karaf.features.BundleInfo;
 import org.apache.karaf.features.Dependency;
 import org.apache.karaf.features.Feature;
 import org.apache.karaf.features.Repository;
-import org.apache.karaf.features.internal.FeatureValidationUtil;
-import org.apache.karaf.features.internal.RepositoryImpl;
+import org.apache.karaf.features.internal.service.FeatureValidationUtil;
+import org.apache.karaf.features.internal.service.RepositoryImpl;
 import org.apache.karaf.tooling.url.CustomBundleURLStreamHandlerFactory;
 import org.apache.karaf.tooling.utils.MojoSupport;
 import org.apache.maven.artifact.Artifact;

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/util/src/main/java/org/apache/karaf/util/tracker/BaseActivator.java
----------------------------------------------------------------------
diff --git a/util/src/main/java/org/apache/karaf/util/tracker/BaseActivator.java b/util/src/main/java/org/apache/karaf/util/tracker/BaseActivator.java
index 65a1bdd..4f34b06 100644
--- a/util/src/main/java/org/apache/karaf/util/tracker/BaseActivator.java
+++ b/util/src/main/java/org/apache/karaf/util/tracker/BaseActivator.java
@@ -45,11 +45,21 @@ public class BaseActivator implements BundleActivator, SingleServiceTracker.Sing
             new LinkedBlockingQueue<Runnable>());
     private AtomicBoolean scheduled = new AtomicBoolean();
 
+    private long schedulerStopTimeout = TimeUnit.MILLISECONDS.convert(30, TimeUnit.SECONDS);
+
     private List<ServiceRegistration> registrations;
     private Map<String, SingleServiceTracker> trackers = new HashMap<String, SingleServiceTracker>();
     private ServiceRegistration managedServiceRegistration;
     private Dictionary<String, ?> configuration;
 
+    public long getSchedulerStopTimeout() {
+        return schedulerStopTimeout;
+    }
+
+    public void setSchedulerStopTimeout(long schedulerStopTimeout) {
+        this.schedulerStopTimeout = schedulerStopTimeout;
+    }
+
     @Override
     public void start(BundleContext context) throws Exception {
         bundleContext = context;
@@ -72,7 +82,7 @@ public class BaseActivator implements BundleActivator, SingleServiceTracker.Sing
         scheduled.set(true);
         doClose();
         executor.shutdown();
-        executor.awaitTermination(30, TimeUnit.SECONDS);
+        executor.awaitTermination(schedulerStopTimeout, TimeUnit.MILLISECONDS);
         doStop();
     }
 

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/webconsole/branding/pom.xml
----------------------------------------------------------------------
diff --git a/webconsole/branding/pom.xml b/webconsole/branding/pom.xml
index 30359c6..5ec04c6 100644
--- a/webconsole/branding/pom.xml
+++ b/webconsole/branding/pom.xml
@@ -79,7 +79,7 @@
                 <configuration>
                     <instructions>
                         <Bundle-DocURL>http://felix.apache.org/site/apache-karaf.html</Bundle-DocURL>
-                        <Fragment-Host>org.apache.karaf.webconsole.console;bundle-version="[3,4)"</Fragment-Host>
+                        <Fragment-Host>org.apache.karaf.webconsole.console;bundle-version="[4,5)"</Fragment-Host>
                         <Export-Package>!*</Export-Package>
                         <Import-Package>
                             javax.servlet;version=2.4,

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/webconsole/features/src/main/java/org/apache/karaf/webconsole/features/ExtendedFeature.java
----------------------------------------------------------------------
diff --git a/webconsole/features/src/main/java/org/apache/karaf/webconsole/features/ExtendedFeature.java b/webconsole/features/src/main/java/org/apache/karaf/webconsole/features/ExtendedFeature.java
index 401cf29..47052ab 100644
--- a/webconsole/features/src/main/java/org/apache/karaf/webconsole/features/ExtendedFeature.java
+++ b/webconsole/features/src/main/java/org/apache/karaf/webconsole/features/ExtendedFeature.java
@@ -20,10 +20,12 @@ import java.util.List;
 import java.util.Map;
 
 import org.apache.karaf.features.BundleInfo;
+import org.apache.karaf.features.Capability;
 import org.apache.karaf.features.Conditional;
 import org.apache.karaf.features.ConfigFileInfo;
 import org.apache.karaf.features.Dependency;
 import org.apache.karaf.features.Feature;
+import org.apache.karaf.features.Requirement;
 
 public class ExtendedFeature implements Feature {
 
@@ -69,6 +71,16 @@ public class ExtendedFeature implements Feature {
     }
 
     @Override
+    public List<? extends Capability> getCapabilities() {
+        return feature.getCapabilities();
+    }
+
+    @Override
+    public List<? extends Requirement> getRequirements() {
+        return feature.getRequirements();
+    }
+
+    @Override
     public List<Dependency> getDependencies() {
         return this.feature.getDependencies();
     }

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/webconsole/features/src/main/java/org/apache/karaf/webconsole/features/FeaturesPlugin.java
----------------------------------------------------------------------
diff --git a/webconsole/features/src/main/java/org/apache/karaf/webconsole/features/FeaturesPlugin.java b/webconsole/features/src/main/java/org/apache/karaf/webconsole/features/FeaturesPlugin.java
index 41cb7b3..8e0af3a 100644
--- a/webconsole/features/src/main/java/org/apache/karaf/webconsole/features/FeaturesPlugin.java
+++ b/webconsole/features/src/main/java/org/apache/karaf/webconsole/features/FeaturesPlugin.java
@@ -272,8 +272,6 @@ public class FeaturesPlugin extends AbstractWebConsolePlugin {
             jw.array();
             for (Repository r : repositories) {
                 jw.object();
-                jw.key("name");
-                jw.value(r.getName());
                 jw.key("url");
                 String uri = r.getURI().toString();
                 jw.value(uri);


[02/13] git commit: [KARAF-2833] Remove unused imports in ssh activator

Posted by gn...@apache.org.
[KARAF-2833] Remove unused imports in ssh activator


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

Branch: refs/heads/master
Commit: 0d87c62dfb5c56b99a7195716a851c30f22bb72c
Parents: 5337778
Author: Guillaume Nodet <gn...@gmail.com>
Authored: Thu Apr 3 09:43:45 2014 +0200
Committer: Guillaume Nodet <gn...@gmail.com>
Committed: Mon Apr 7 10:52:30 2014 +0200

----------------------------------------------------------------------
 .../java/org/apache/karaf/shell/ssh/Activator.java    | 14 --------------
 1 file changed, 14 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/karaf/blob/0d87c62d/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/Activator.java
----------------------------------------------------------------------
diff --git a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/Activator.java b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/Activator.java
index 16f08ab..b87cefb 100644
--- a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/Activator.java
+++ b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/Activator.java
@@ -20,32 +20,18 @@ package org.apache.karaf.shell.ssh;
 
 import java.io.File;
 import java.io.IOException;
-import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Dictionary;
-import java.util.Hashtable;
-import java.util.List;
-import java.util.concurrent.Callable;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.locks.ReadWriteLock;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
 
 import org.apache.karaf.shell.api.action.lifecycle.Manager;
 import org.apache.karaf.shell.api.console.Session;
 import org.apache.karaf.shell.api.console.SessionFactory;
 import org.apache.karaf.util.tracker.BaseActivator;
-import org.apache.karaf.util.tracker.SingleServiceTracker;
 import org.apache.sshd.SshServer;
 import org.apache.sshd.common.NamedFactory;
 import org.apache.sshd.server.command.ScpCommandFactory;
 import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
 import org.apache.sshd.server.sftp.SftpSubsystem;
-import org.osgi.framework.BundleActivator;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.Constants;
 import org.osgi.framework.ServiceReference;
-import org.osgi.framework.ServiceRegistration;
-import org.osgi.service.cm.ConfigurationException;
 import org.osgi.service.cm.ManagedService;
 import org.osgi.util.tracker.ServiceTracker;
 import org.slf4j.Logger;


[09/13] [KARAF-2888] New FeaturesService based on the real OSGi resolver

Posted by gn...@apache.org.
http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceBuilder.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceBuilder.java b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceBuilder.java
new file mode 100644
index 0000000..cb2c36a
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceBuilder.java
@@ -0,0 +1,1129 @@
+/*
+ * 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.resolver;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.felix.utils.version.VersionRange;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.Constants;
+import org.osgi.framework.Version;
+import org.osgi.framework.namespace.IdentityNamespace;
+import org.osgi.framework.wiring.BundleCapability;
+import org.osgi.framework.wiring.BundleRevision;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Requirement;
+import org.osgi.resource.Resource;
+
+public class ResourceBuilder {
+
+    public static final String RESOLUTION_DYNAMIC = "dynamic";
+
+    public static Resource build(String uri, Map<String, String> headerMap)
+            throws BundleException {
+
+        // Verify that only manifest version 2 is specified.
+        String manifestVersion = getManifestVersion(headerMap);
+        if (manifestVersion == null || !manifestVersion.equals("2")) {
+            throw new BundleException("Unsupported 'Bundle-ManifestVersion' value: " + manifestVersion);
+        }
+
+        //
+        // Parse bundle version.
+        //
+
+        Version bundleVersion = Version.emptyVersion;
+        if (headerMap.get(Constants.BUNDLE_VERSION) != null) {
+            bundleVersion = Version.parseVersion(headerMap.get(Constants.BUNDLE_VERSION));
+        }
+
+        //
+        // Parse bundle symbolic name.
+        //
+
+        String bundleSymbolicName = null;
+        ParsedHeaderClause bundleCap = parseBundleSymbolicName(headerMap);
+        if (bundleCap == null) {
+            throw new BundleException("Bundle manifest must include bundle symbolic name");
+        }
+        bundleSymbolicName = (String) bundleCap.attrs.get(BundleRevision.BUNDLE_NAMESPACE);
+
+        // Now that we have symbolic name and version, create the resource
+        String type = headerMap.get(Constants.FRAGMENT_HOST) == null ? IdentityNamespace.TYPE_BUNDLE : IdentityNamespace.TYPE_FRAGMENT;
+        ResourceImpl resource = new ResourceImpl(bundleSymbolicName, type, bundleVersion);
+        if (uri != null) {
+            Map<String, Object> attrs = new HashMap<String, Object>();
+            attrs.put(UriNamespace.URI_NAMESPACE, uri);
+            resource.addCapability(new CapabilityImpl(resource, UriNamespace.URI_NAMESPACE, Collections.<String, String>emptyMap(), attrs));
+        }
+
+        // Add a bundle and host capability to all
+        // non-fragment bundles. A host capability is the same
+        // as a require capability, but with a different capability
+        // namespace. Bundle capabilities resolve required-bundle
+        // dependencies, while host capabilities resolve fragment-host
+        // dependencies.
+        if (headerMap.get(Constants.FRAGMENT_HOST) == null) {
+            // All non-fragment bundles have bundle capability.
+            resource.addCapability(new CapabilityImpl(resource, BundleRevision.BUNDLE_NAMESPACE, bundleCap.dirs, bundleCap.attrs));
+            // A non-fragment bundle can choose to not have a host capability.
+            String attachment = bundleCap.dirs.get(Constants.FRAGMENT_ATTACHMENT_DIRECTIVE);
+            attachment = (attachment == null) ? Constants.FRAGMENT_ATTACHMENT_RESOLVETIME : attachment;
+            if (!attachment.equalsIgnoreCase(Constants.FRAGMENT_ATTACHMENT_NEVER)) {
+                Map<String, Object> hostAttrs = new HashMap<String, Object>(bundleCap.attrs);
+                Object value = hostAttrs.remove(BundleRevision.BUNDLE_NAMESPACE);
+                hostAttrs.put(BundleRevision.HOST_NAMESPACE, value);
+                resource.addCapability(new CapabilityImpl(
+                        resource, BundleRevision.HOST_NAMESPACE,
+                        bundleCap.dirs,
+                        hostAttrs));
+            }
+        }
+
+        //
+        // Parse Fragment-Host.
+        //
+
+        List<RequirementImpl> hostReqs = parseFragmentHost(resource, headerMap);
+
+        //
+        // Parse Require-Bundle
+        //
+
+        List<ParsedHeaderClause> rbClauses = parseStandardHeader(headerMap.get(Constants.REQUIRE_BUNDLE));
+        rbClauses = normalizeRequireClauses(rbClauses);
+        List<Requirement> rbReqs = convertRequires(rbClauses, resource);
+
+        //
+        // Parse Import-Package.
+        //
+
+        List<ParsedHeaderClause> importClauses = parseStandardHeader(headerMap.get(Constants.IMPORT_PACKAGE));
+        importClauses = normalizeImportClauses(importClauses);
+        List<Requirement> importReqs = convertImports(importClauses, resource);
+
+        //
+        // Parse DynamicImport-Package.
+        //
+
+        List<ParsedHeaderClause> dynamicClauses = parseStandardHeader(headerMap.get(Constants.DYNAMICIMPORT_PACKAGE));
+        dynamicClauses = normalizeDynamicImportClauses(dynamicClauses);
+        List<Requirement> dynamicReqs = convertImports(dynamicClauses, resource);
+
+        //
+        // Parse Require-Capability.
+        //
+
+        List<ParsedHeaderClause> requireClauses = parseStandardHeader(headerMap.get(Constants.REQUIRE_CAPABILITY));
+        requireClauses = normalizeRequireCapabilityClauses(requireClauses);
+        List<Requirement> requireReqs = convertRequireCapabilities(requireClauses, resource);
+
+        //
+        // Parse Export-Package.
+        //
+
+        List<ParsedHeaderClause> exportClauses = parseStandardHeader(headerMap.get(Constants.EXPORT_PACKAGE));
+        exportClauses = normalizeExportClauses(exportClauses, bundleSymbolicName, bundleVersion);
+        List<Capability> exportCaps = convertExports(exportClauses, resource);
+
+        //
+        // Parse Provide-Capability.
+        //
+
+        List<ParsedHeaderClause> provideClauses = parseStandardHeader(headerMap.get(Constants.PROVIDE_CAPABILITY));
+        provideClauses = normalizeProvideCapabilityClauses(provideClauses);
+        List<Capability> provideCaps = convertProvideCapabilities(provideClauses, resource);
+
+        //
+        // Parse Import-Service and Export-Service
+        // if Require-Capability and Provide-Capability are not set for services
+        //
+
+        boolean hasServiceReferenceCapability = false;
+        for (Capability cap : exportCaps) {
+            hasServiceReferenceCapability |= ServiceNamespace.SERVICE_NAMESPACE.equals(cap.getNamespace());
+        }
+        if (!hasServiceReferenceCapability) {
+            List<ParsedHeaderClause> exportServices = parseStandardHeader(headerMap.get(Constants.EXPORT_SERVICE));
+            List<Capability> caps = convertExportService(exportServices, resource);
+            provideCaps.addAll(caps);
+        }
+
+        boolean hasServiceReferenceRequirement = false;
+        for (Requirement req : requireReqs) {
+            hasServiceReferenceRequirement |= ServiceNamespace.SERVICE_NAMESPACE.equals(req.getNamespace());
+        }
+        if (!hasServiceReferenceRequirement) {
+            List<ParsedHeaderClause> importServices = parseStandardHeader(headerMap.get(Constants.IMPORT_SERVICE));
+            List<Requirement> reqs = convertImportService(importServices, resource);
+            requireReqs.addAll(reqs);
+        }
+
+        // Combine all capabilities.
+        resource.addCapabilities(exportCaps);
+        resource.addCapabilities(provideCaps);
+
+        // Combine all requirements.
+        resource.addRequirements(hostReqs);
+        resource.addRequirements(importReqs);
+        resource.addRequirements(rbReqs);
+        resource.addRequirements(requireReqs);
+        resource.addRequirements(dynamicReqs);
+
+        return resource;
+    }
+
+    public static List<Requirement> parseImport(Resource resource, String imports) throws BundleException {
+        List<ParsedHeaderClause> importClauses = parseStandardHeader(imports);
+        importClauses = normalizeImportClauses(importClauses);
+        List<Requirement> importReqs = convertImports(importClauses, resource);
+        return importReqs;
+    }
+
+    public static List<Requirement> parseRequirement(Resource resource, String requirement) throws BundleException {
+        List<ParsedHeaderClause> requireClauses = parseStandardHeader(requirement);
+        requireClauses = normalizeRequireCapabilityClauses(requireClauses);
+        List<Requirement> requireReqs = convertRequireCapabilities(requireClauses, resource);
+        return requireReqs;
+    }
+
+    public static List<Capability> parseExport(Resource resource, String bundleSymbolicName, Version bundleVersion, String exports) throws BundleException {
+        List<ParsedHeaderClause> exportClauses = parseStandardHeader(exports);
+        exportClauses = normalizeExportClauses(exportClauses, bundleSymbolicName, bundleVersion);
+        List<Capability> exportCaps = convertExports(exportClauses, resource);
+        return exportCaps;
+    }
+
+    public static List<Capability> parseCapability(Resource resource, String capability) throws BundleException {
+        List<ParsedHeaderClause> provideClauses = parseStandardHeader(capability);
+        provideClauses = normalizeProvideCapabilityClauses(provideClauses);
+        List<Capability> provideCaps = convertProvideCapabilities(provideClauses, resource);
+        return provideCaps;
+    }
+
+    @SuppressWarnings( "deprecation" )
+    private static List<ParsedHeaderClause> normalizeImportClauses(
+            List<ParsedHeaderClause> clauses)
+            throws BundleException {
+        // Verify that the values are equals if the package specifies
+        // both version and specification-version attributes.
+        Set<String> dupeSet = new HashSet<String>();
+        for (ParsedHeaderClause clause : clauses) {
+            // Check for "version" and "specification-version" attributes
+            // and verify they are the same if both are specified.
+            Object v = clause.attrs.get(Constants.VERSION_ATTRIBUTE);
+            Object sv = clause.attrs.get(Constants.PACKAGE_SPECIFICATION_VERSION);
+            if ((v != null) && (sv != null)) {
+                // Verify they are equal.
+                if (!((String) v).trim().equals(((String) sv).trim())) {
+                    throw new IllegalArgumentException(
+                            "Both version and specification-version are specified, but they are not equal.");
+                }
+            }
+
+            // Ensure that only the "version" attribute is used and convert
+            // it to the VersionRange type.
+            if ((v != null) || (sv != null)) {
+                clause.attrs.remove(Constants.PACKAGE_SPECIFICATION_VERSION);
+                v = (v == null) ? sv : v;
+                clause.attrs.put(Constants.VERSION_ATTRIBUTE, VersionRange.parseVersionRange(v.toString()));
+            }
+
+            // If bundle version is specified, then convert its type to VersionRange.
+            v = clause.attrs.get(Constants.BUNDLE_VERSION_ATTRIBUTE);
+            if (v != null) {
+                clause.attrs.put(Constants.BUNDLE_VERSION_ATTRIBUTE, VersionRange.parseVersionRange(v.toString()));
+            }
+
+            // Verify java.* is not imported, nor any duplicate imports.
+            for (String pkgName : clause.paths) {
+                if (!dupeSet.contains(pkgName)) {
+                    // Verify that java.* packages are not imported.
+                    if (pkgName.startsWith("java.")) {
+                        throw new BundleException("Importing java.* packages not allowed: " + pkgName);
+                    }
+                    // The character "." has no meaning in the OSGi spec except
+                    // when placed on the bundle class path. Some people, however,
+                    // mistakenly think it means the default package when imported
+                    // or exported. This is not correct. It is invalid.
+                    else if (pkgName.equals(".")) {
+                        throw new BundleException("Importing '.' is invalid.");
+                    }
+                    // Make sure a package name was specified.
+                    else if (pkgName.length() == 0) {
+                        throw new BundleException(
+                                "Imported package names cannot be zero length.");
+                    }
+                    dupeSet.add(pkgName);
+                } else {
+                    throw new BundleException("Duplicate import: " + pkgName);
+                }
+            }
+        }
+
+        return clauses;
+    }
+
+    private static List<Capability> convertExportService(List<ParsedHeaderClause> clauses, Resource resource) {
+        List<Capability> capList = new ArrayList<Capability>();
+        for (ParsedHeaderClause clause : clauses) {
+            for (String path : clause.paths) {
+                Map<String, String> dirs = new LinkedHashMap<String, String>();
+                dirs.put(ServiceNamespace.CAPABILITY_EFFECTIVE_DIRECTIVE, ServiceNamespace.EFFECTIVE_ACTIVE);
+                Map<String, Object> attrs = new LinkedHashMap<String, Object>();
+                attrs.put(Constants.OBJECTCLASS, path);
+                attrs.putAll(clause.attrs);
+                capList.add(new CapabilityImpl(
+                                resource,
+                                ServiceNamespace.SERVICE_NAMESPACE,
+                                dirs,
+                                attrs));
+            }
+        }
+        return capList;
+    }
+
+    private static List<Requirement> convertImportService(List<ParsedHeaderClause> clauses, Resource resource) throws BundleException {
+        try {
+            List<Requirement> reqList = new ArrayList<Requirement>();
+            for (ParsedHeaderClause clause : clauses) {
+                for (String path : clause.paths) {
+                    String multiple = clause.dirs.get("multiple");
+                    String avail    = clause.dirs.get("availability");
+                    String filter   = (String) clause.attrs.get("filter");
+                    Map<String, String> dirs = new LinkedHashMap<String, String>();
+                    dirs.put(ServiceNamespace.REQUIREMENT_EFFECTIVE_DIRECTIVE, ServiceNamespace.EFFECTIVE_ACTIVE);
+                    if ("optional".equals(avail)) {
+                        dirs.put(ServiceNamespace.REQUIREMENT_RESOLUTION_DIRECTIVE, ServiceNamespace.RESOLUTION_OPTIONAL);
+                    }
+                    if ("true".equals(multiple)) {
+                        dirs.put(ServiceNamespace.REQUIREMENT_CARDINALITY_DIRECTIVE, ServiceNamespace.CARDINALITY_MULTIPLE);
+                    }
+                    if (filter == null) {
+                        filter = "(" + Constants.OBJECTCLASS + "=" + path + ")";
+                    } else if (!filter.startsWith("(") && !filter.endsWith(")")) {
+                        filter = "(&(" + Constants.OBJECTCLASS + "=" + path + ")(" + filter + "))";
+                    } else {
+                        filter = "(&(" + Constants.OBJECTCLASS + "=" + path + ")" + filter + ")";
+                    }
+                    dirs.put(ServiceNamespace.REQUIREMENT_FILTER_DIRECTIVE, filter);
+                    reqList.add(new RequirementImpl(
+                                    resource,
+                                    ServiceNamespace.SERVICE_NAMESPACE,
+                                    dirs,
+                                    Collections.<String, Object>emptyMap(),
+                                    SimpleFilter.parse(filter)));
+                }
+            }
+            return reqList;
+        } catch (Exception ex) {
+            throw new BundleException("Error creating requirement: " + ex, ex);
+        }
+    }
+
+    private static List<Requirement> convertImports(List<ParsedHeaderClause> clauses, Resource resource) {
+        // Now convert generic header clauses into requirements.
+        List<Requirement> reqList = new ArrayList<Requirement>();
+        for (ParsedHeaderClause clause : clauses) {
+            for (String path : clause.paths) {
+                // Prepend the package name to the array of attributes.
+                Map<String, Object> attrs = clause.attrs;
+                // Note that we use a linked hash map here to ensure the
+                // package attribute is first, which will make indexing
+                // more efficient.
+    // TODO: OSGi R4.3 - This is ordering is kind of hacky.
+                // Prepend the package name to the array of attributes.
+                Map<String, Object> newAttrs = new LinkedHashMap<String, Object>(attrs.size() + 1);
+                // We want this first from an indexing perspective.
+                newAttrs.put(BundleRevision.PACKAGE_NAMESPACE, path);
+                newAttrs.putAll(attrs);
+                // But we need to put it again to make sure it wasn't overwritten.
+                newAttrs.put(BundleRevision.PACKAGE_NAMESPACE, path);
+
+                // Create filter now so we can inject filter directive.
+                SimpleFilter sf = SimpleFilter.convert(newAttrs);
+
+                // Inject filter directive.
+    // TODO: OSGi R4.3 - Can we insert this on demand somehow?
+                Map<String, String> dirs = clause.dirs;
+                Map<String, String> newDirs = new HashMap<String, String>(dirs.size() + 1);
+                newDirs.putAll(dirs);
+                newDirs.put(Constants.FILTER_DIRECTIVE, sf.toString());
+
+                // Create package requirement and add to requirement list.
+                reqList.add(
+                        new RequirementImpl(
+                                resource,
+                                BundleRevision.PACKAGE_NAMESPACE,
+                                newDirs,
+                                Collections.<String, Object>emptyMap(),
+                                sf));
+            }
+        }
+
+        return reqList;
+    }
+
+    @SuppressWarnings( "deprecation" )
+    private static List<ParsedHeaderClause> normalizeDynamicImportClauses(
+            List<ParsedHeaderClause> clauses)
+            throws BundleException {
+        // Verify that the values are equals if the package specifies
+        // both version and specification-version attributes.
+        for (ParsedHeaderClause clause : clauses) {
+            // Add the resolution directive to indicate that these are
+            // dynamic imports.
+            clause.dirs.put(Constants.RESOLUTION_DIRECTIVE, RESOLUTION_DYNAMIC);
+
+            // Check for "version" and "specification-version" attributes
+            // and verify they are the same if both are specified.
+            Object v = clause.attrs.get(Constants.VERSION_ATTRIBUTE);
+            Object sv = clause.attrs.get(Constants.PACKAGE_SPECIFICATION_VERSION);
+            if ((v != null) && (sv != null)) {
+                // Verify they are equal.
+                if (!((String) v).trim().equals(((String) sv).trim())) {
+                    throw new IllegalArgumentException(
+                            "Both version and specification-version are specified, but they are not equal.");
+                }
+            }
+
+            // Ensure that only the "version" attribute is used and convert
+            // it to the VersionRange type.
+            if ((v != null) || (sv != null)) {
+                clause.attrs.remove(Constants.PACKAGE_SPECIFICATION_VERSION);
+                v = (v == null) ? sv : v;
+                clause.attrs.put(Constants.VERSION_ATTRIBUTE, VersionRange.parseVersionRange(v.toString()));
+            }
+
+            // If bundle version is specified, then convert its type to VersionRange.
+            v = clause.attrs.get(Constants.BUNDLE_VERSION_ATTRIBUTE);
+            if (v != null) {
+                clause.attrs.put(Constants.BUNDLE_VERSION_ATTRIBUTE, VersionRange.parseVersionRange(v.toString()));
+            }
+
+            // Dynamic imports can have duplicates, so verify that java.*
+            // packages are not imported.
+            for (String pkgName : clause.paths) {
+                if (pkgName.startsWith("java.")) {
+                    throw new BundleException("Dynamically importing java.* packages not allowed: " + pkgName);
+                } else if (!pkgName.equals("*") && pkgName.endsWith("*") && !pkgName.endsWith(".*")) {
+                    throw new BundleException("Partial package name wild carding is not allowed: " + pkgName);
+                }
+            }
+        }
+
+        return clauses;
+    }
+
+    private static List<ParsedHeaderClause> normalizeRequireCapabilityClauses(
+            List<ParsedHeaderClause> clauses)
+            throws BundleException {
+
+        return clauses;
+    }
+
+    private static List<ParsedHeaderClause> normalizeProvideCapabilityClauses(
+            List<ParsedHeaderClause> clauses)
+            throws BundleException
+    {
+
+        // Convert attributes into specified types.
+        for (ParsedHeaderClause clause : clauses)
+        {
+            for (Map.Entry<String, String> entry : clause.types.entrySet())
+            {
+                String type = entry.getValue();
+                if (!type.equals("String"))
+                {
+                    if (type.equals("Double"))
+                    {
+                        clause.attrs.put(
+                                entry.getKey(),
+                                new Double(clause.attrs.get(entry.getKey()).toString().trim()));
+                    }
+                    else if (type.equals("Version"))
+                    {
+                        clause.attrs.put(
+                                entry.getKey(),
+                                new Version(clause.attrs.get(entry.getKey()).toString().trim()));
+                    }
+                    else if (type.equals("Long"))
+                    {
+                        clause.attrs.put(
+                                entry.getKey(),
+                                new Long(clause.attrs.get(entry.getKey()).toString().trim()));
+                    }
+                    else if (type.startsWith("List"))
+                    {
+                        int startIdx = type.indexOf('<');
+                        int endIdx = type.indexOf('>');
+                        if (((startIdx > 0) && (endIdx <= startIdx))
+                                || ((startIdx < 0) && (endIdx > 0)))
+                        {
+                            throw new BundleException(
+                                    "Invalid Provide-Capability attribute list type for '"
+                                            + entry.getKey()
+                                            + "' : "
+                                            + type);
+                        }
+
+                        String listType = "String";
+                        if (endIdx > startIdx)
+                        {
+                            listType = type.substring(startIdx + 1, endIdx).trim();
+                        }
+
+                        List<String> tokens = parseDelimitedString(
+                                clause.attrs.get(entry.getKey()).toString(), ",", false);
+                        List<Object> values = new ArrayList<Object>(tokens.size());
+                        for (String token : tokens)
+                        {
+                            if (listType.equals("String"))
+                            {
+                                values.add(token);
+                            }
+                            else if (listType.equals("Double"))
+                            {
+                                values.add(new Double(token.trim()));
+                            }
+                            else if (listType.equals("Version"))
+                            {
+                                values.add(new Version(token.trim()));
+                            }
+                            else if (listType.equals("Long"))
+                            {
+                                values.add(new Long(token.trim()));
+                            }
+                            else
+                            {
+                                throw new BundleException(
+                                        "Unknown Provide-Capability attribute list type for '"
+                                                + entry.getKey()
+                                                + "' : "
+                                                + type);
+                            }
+                        }
+                        clause.attrs.put(
+                                entry.getKey(),
+                                values);
+                    }
+                    else
+                    {
+                        throw new BundleException(
+                                "Unknown Provide-Capability attribute type for '"
+                                        + entry.getKey()
+                                        + "' : "
+                                        + type);
+                    }
+                }
+            }
+        }
+
+        return clauses;
+    }
+
+    private static List<Requirement> convertRequireCapabilities(
+            List<ParsedHeaderClause> clauses, Resource resource)
+            throws BundleException {
+        // Now convert generic header clauses into requirements.
+        List<Requirement> reqList = new ArrayList<Requirement>();
+        for (ParsedHeaderClause clause : clauses) {
+            try {
+                String filterStr = clause.dirs.get(Constants.FILTER_DIRECTIVE);
+                SimpleFilter sf = (filterStr != null)
+                        ? SimpleFilter.parse(filterStr)
+                        : new SimpleFilter(null, null, SimpleFilter.MATCH_ALL);
+                for (String path : clause.paths) {
+                    // Create requirement and add to requirement list.
+                    reqList.add(new RequirementImpl(
+                                    resource, path, clause.dirs, clause.attrs, sf));
+                }
+            } catch (Exception ex) {
+                throw new BundleException("Error creating requirement: " + ex, ex);
+            }
+        }
+
+        return reqList;
+    }
+
+    private static List<Capability> convertProvideCapabilities(
+            List<ParsedHeaderClause> clauses, Resource resource)
+            throws BundleException {
+        List<Capability> capList = new ArrayList<Capability>();
+        for (ParsedHeaderClause clause : clauses) {
+            for (String path : clause.paths) {
+                if (path.startsWith("osgi.wiring.")) {
+//                    throw new BundleException("Manifest cannot use Provide-Capability for '" + path + "' namespace.");
+                }
+
+                // Create package capability and add to capability list.
+                capList.add(new CapabilityImpl(resource, path, clause.dirs, clause.attrs));
+            }
+        }
+
+        return capList;
+    }
+
+    @SuppressWarnings( "deprecation" )
+    private static List<ParsedHeaderClause> normalizeExportClauses(
+            List<ParsedHeaderClause> clauses,
+            String bsn, Version bv)
+            throws BundleException {
+        // Verify that "java.*" packages are not exported.
+        for (ParsedHeaderClause clause : clauses) {
+            // Verify that the named package has not already been declared.
+            for (String pkgName : clause.paths) {
+                // Verify that java.* packages are not exported.
+                if (pkgName.startsWith("java.")) {
+                    throw new BundleException("Exporting java.* packages not allowed: " + pkgName);
+                }
+                // The character "." has no meaning in the OSGi spec except
+                // when placed on the bundle class path. Some people, however,
+                // mistakenly think it means the default package when imported
+                // or exported. This is not correct. It is invalid.
+                else if (pkgName.equals(".")) {
+                    throw new BundleException("Exporing '.' is invalid.");
+                }
+                // Make sure a package name was specified.
+                else if (pkgName.length() == 0) {
+                    throw new BundleException("Exported package names cannot be zero length.");
+                }
+            }
+
+            // Check for "version" and "specification-version" attributes
+            // and verify they are the same if both are specified.
+            Object v = clause.attrs.get(Constants.VERSION_ATTRIBUTE);
+            Object sv = clause.attrs.get(Constants.PACKAGE_SPECIFICATION_VERSION);
+            if ((v != null) && (sv != null)) {
+                // Verify they are equal.
+                if (!((String) v).trim().equals(((String) sv).trim())) {
+                    throw new IllegalArgumentException("Both version and specification-version are specified, but they are not equal.");
+                }
+            }
+
+            // Always add the default version if not specified.
+            if ((v == null) && (sv == null)) {
+                v = Version.emptyVersion;
+            }
+
+            // Ensure that only the "version" attribute is used and convert
+            // it to the appropriate type.
+            if ((v != null) || (sv != null)) {
+                // Convert version attribute to type Version.
+                clause.attrs.remove(Constants.PACKAGE_SPECIFICATION_VERSION);
+                v = (v == null) ? sv : v;
+                clause.attrs.put(Constants.VERSION_ATTRIBUTE, Version.parseVersion(v.toString()));
+            }
+
+            // Find symbolic name and version attribute, if present.
+            if (clause.attrs.containsKey(Constants.BUNDLE_VERSION_ATTRIBUTE)
+                    || clause.attrs.containsKey(Constants.BUNDLE_SYMBOLICNAME_ATTRIBUTE)) {
+                throw new BundleException("Exports must not specify bundle symbolic name or bundle version.");
+            }
+
+            // Now that we know that there are no bundle symbolic name and version
+            // attributes, add them since the spec says they are there implicitly.
+            clause.attrs.put(Constants.BUNDLE_SYMBOLICNAME_ATTRIBUTE, bsn);
+            clause.attrs.put(Constants.BUNDLE_VERSION_ATTRIBUTE, bv);
+        }
+
+        return clauses;
+    }
+
+    private static List<Capability> convertExports(
+            List<ParsedHeaderClause> clauses, Resource resource) {
+        List<Capability> capList = new ArrayList<Capability>();
+        for (ParsedHeaderClause clause : clauses) {
+            for (String pkgName : clause.paths) {
+                // Prepend the package name to the array of attributes.
+                Map<String, Object> attrs = clause.attrs;
+                Map<String, Object> newAttrs = new HashMap<String, Object>(attrs.size() + 1);
+                newAttrs.putAll(attrs);
+                newAttrs.put(BundleRevision.PACKAGE_NAMESPACE, pkgName);
+
+                // Create package capability and add to capability list.
+                capList.add(new CapabilityImpl(resource, BundleRevision.PACKAGE_NAMESPACE, clause.dirs, newAttrs));
+            }
+        }
+
+        return capList;
+    }
+
+    private static String getManifestVersion(Map<String, String> headerMap) {
+        String manifestVersion = headerMap.get(Constants.BUNDLE_MANIFESTVERSION);
+        return (manifestVersion == null) ? "1" : manifestVersion.trim();
+    }
+
+    private static List<ParsedHeaderClause> calculateImplicitImports(
+            List<BundleCapability> exports, List<ParsedHeaderClause> imports)
+            throws BundleException {
+        List<ParsedHeaderClause> clauseList = new ArrayList<ParsedHeaderClause>();
+
+        // Since all R3 exports imply an import, add a corresponding
+        // requirement for each existing export capability. Do not
+        // duplicate imports.
+        Map<String, String> map = new HashMap<String, String>();
+        // Add existing imports.
+        for (ParsedHeaderClause anImport : imports) {
+            for (int pathIdx = 0; pathIdx < anImport.paths.size(); pathIdx++) {
+                map.put(anImport.paths.get(pathIdx), anImport.paths.get(pathIdx));
+            }
+        }
+        // Add import requirement for each export capability.
+        for (BundleCapability export : exports) {
+            if (map.get(export.getAttributes().get(BundleRevision.PACKAGE_NAMESPACE).toString()) == null) {
+                // Convert Version to VersionRange.
+                Object version = export.getAttributes().get(Constants.VERSION_ATTRIBUTE);
+                ParsedHeaderClause clause = new ParsedHeaderClause();
+                if (version != null) {
+                    clause.attrs.put(Constants.VERSION_ATTRIBUTE, VersionRange.parseVersionRange(version.toString()));
+                }
+                clause.paths.add((String) export.getAttributes().get(BundleRevision.PACKAGE_NAMESPACE));
+                clauseList.add(clause);
+            }
+        }
+
+        return clauseList;
+    }
+
+    private static List<Capability> calculateImplicitUses(
+            List<Capability> exports, List<ParsedHeaderClause> imports)
+            throws BundleException {
+        // Add a "uses" directive onto each export of R3 bundles
+        // that references every other import (which will include
+        // exports, since export implies import); this is
+        // necessary since R3 bundles assumed a single class space,
+        // but R4 allows for multiple class spaces.
+        String usesValue = "";
+        for (ParsedHeaderClause anImport : imports) {
+            for (int pathIdx = 0; pathIdx < anImport.paths.size(); pathIdx++) {
+                usesValue = usesValue
+                        + ((usesValue.length() > 0) ? "," : "")
+                        + anImport.paths.get(pathIdx);
+            }
+        }
+        for (int i = 0; i < exports.size(); i++) {
+            Map<String, String> dirs = new HashMap<String, String>(1);
+            dirs.put(Constants.USES_DIRECTIVE, usesValue);
+            exports.set(i, new CapabilityImpl(
+                    exports.get(i).getResource(),
+                    BundleRevision.PACKAGE_NAMESPACE,
+                    dirs,
+                    exports.get(i).getAttributes()));
+        }
+
+        return exports;
+    }
+
+    private static ParsedHeaderClause parseBundleSymbolicName(Map<String, String> headerMap)
+            throws BundleException {
+        List<ParsedHeaderClause> clauses = parseStandardHeader(headerMap.get(Constants.BUNDLE_SYMBOLICNAME));
+        if (clauses.size() > 0) {
+            if (clauses.size() > 1 || clauses.get(0).paths.size() > 1) {
+                throw new BundleException("Cannot have multiple symbolic names: " + headerMap.get(Constants.BUNDLE_SYMBOLICNAME));
+            }
+
+            // Get bundle version.
+            Version bundleVersion = Version.emptyVersion;
+            if (headerMap.get(Constants.BUNDLE_VERSION) != null) {
+                bundleVersion = Version.parseVersion(headerMap.get(Constants.BUNDLE_VERSION));
+            }
+
+            // Create a require capability and return it.
+            ParsedHeaderClause clause = clauses.get(0);
+            String symName = clause.paths.get(0);
+            clause.attrs.put(BundleRevision.BUNDLE_NAMESPACE, symName);
+            clause.attrs.put(Constants.BUNDLE_VERSION_ATTRIBUTE, bundleVersion);
+            return clause;
+        }
+
+        return null;
+    }
+
+    private static List<RequirementImpl> parseFragmentHost(
+            Resource resource, Map<String, String> headerMap)
+            throws BundleException {
+        List<RequirementImpl> reqs = new ArrayList<RequirementImpl>();
+
+        List<ParsedHeaderClause> clauses = parseStandardHeader(headerMap.get(Constants.FRAGMENT_HOST));
+        if (clauses.size() > 0) {
+            // Make sure that only one fragment host symbolic name is specified.
+            if (clauses.size() > 1 || clauses.get(0).paths.size() > 1) {
+                throw new BundleException("Fragments cannot have multiple hosts: " + headerMap.get(Constants.FRAGMENT_HOST));
+            }
+
+            // If the bundle-version attribute is specified, then convert
+            // it to the proper type.
+            Object value = clauses.get(0).attrs.get(Constants.BUNDLE_VERSION_ATTRIBUTE);
+            value = (value == null) ? "0.0.0" : value;
+            clauses.get(0).attrs.put(Constants.BUNDLE_VERSION_ATTRIBUTE, VersionRange.parseVersionRange(value.toString()));
+
+            // Note that we use a linked hash map here to ensure the
+            // host symbolic name is first, which will make indexing
+            // more efficient.
+    // TODO: OSGi R4.3 - This is ordering is kind of hacky.
+            // Prepend the host symbolic name to the map of attributes.
+            Map<String, Object> attrs = clauses.get(0).attrs;
+            Map<String, Object> newAttrs = new LinkedHashMap<String, Object>(attrs.size() + 1);
+            // We want this first from an indexing perspective.
+            newAttrs.put(BundleRevision.HOST_NAMESPACE, clauses.get(0).paths.get(0));
+            newAttrs.putAll(attrs);
+            // But we need to put it again to make sure it wasn't overwritten.
+            newAttrs.put(BundleRevision.HOST_NAMESPACE, clauses.get(0).paths.get(0));
+
+            // Create filter now so we can inject filter directive.
+            SimpleFilter sf = SimpleFilter.convert(newAttrs);
+
+            // Inject filter directive.
+    // TODO: OSGi R4.3 - Can we insert this on demand somehow?
+            Map<String, String> dirs = clauses.get(0).dirs;
+            Map<String, String> newDirs = new HashMap<String, String>(dirs.size() + 1);
+            newDirs.putAll(dirs);
+            newDirs.put(Constants.FILTER_DIRECTIVE, sf.toString());
+
+            reqs.add(new RequirementImpl(
+                    resource, BundleRevision.HOST_NAMESPACE,
+                    newDirs,
+                    newAttrs));
+        }
+
+        return reqs;
+    }
+
+    private static List<ParsedHeaderClause> normalizeRequireClauses(List<ParsedHeaderClause> clauses) {
+        // Convert bundle version attribute to VersionRange type.
+        for (ParsedHeaderClause clause : clauses) {
+            Object value = clause.attrs.get(Constants.BUNDLE_VERSION_ATTRIBUTE);
+            if (value != null) {
+                clause.attrs.put(Constants.BUNDLE_VERSION_ATTRIBUTE, VersionRange.parseVersionRange(value.toString()));
+            }
+        }
+
+        return clauses;
+    }
+
+    private static List<Requirement> convertRequires(List<ParsedHeaderClause> clauses, Resource resource) {
+        List<Requirement> reqList = new ArrayList<Requirement>();
+        for (ParsedHeaderClause clause : clauses) {
+            for (String path : clause.paths) {
+                // Prepend the bundle symbolic name to the array of attributes.
+                Map<String, Object> attrs = clause.attrs;
+                // Note that we use a linked hash map here to ensure the
+                // symbolic name attribute is first, which will make indexing
+                // more efficient.
+    // TODO: OSGi R4.3 - This is ordering is kind of hacky.
+                // Prepend the symbolic name to the array of attributes.
+                Map<String, Object> newAttrs = new LinkedHashMap<String, Object>(attrs.size() + 1);
+                // We want this first from an indexing perspective.
+                newAttrs.put(BundleRevision.BUNDLE_NAMESPACE, path);
+                newAttrs.putAll(attrs);
+                // But we need to put it again to make sure it wasn't overwritten.
+                newAttrs.put(BundleRevision.BUNDLE_NAMESPACE, path);
+
+                // Create filter now so we can inject filter directive.
+                SimpleFilter sf = SimpleFilter.convert(newAttrs);
+
+                // Inject filter directive.
+    // TODO: OSGi R4.3 - Can we insert this on demand somehow?
+                Map<String, String> dirs = clause.dirs;
+                Map<String, String> newDirs = new HashMap<String, String>(dirs.size() + 1);
+                newDirs.putAll(dirs);
+                newDirs.put(Constants.FILTER_DIRECTIVE, sf.toString());
+
+                // Create package requirement and add to requirement list.
+                reqList.add(new RequirementImpl(resource, BundleRevision.BUNDLE_NAMESPACE, newDirs, newAttrs));
+            }
+        }
+
+        return reqList;
+    }
+
+    private static final char EOF = (char) -1;
+
+    private static char charAt(int pos, String headers, int length)
+    {
+        if (pos >= length)
+        {
+            return EOF;
+        }
+        return headers.charAt(pos);
+    }
+
+    private static final int CLAUSE_START = 0;
+    private static final int PARAMETER_START = 1;
+    private static final int KEY = 2;
+    private static final int DIRECTIVE_OR_TYPEDATTRIBUTE = 4;
+    private static final int ARGUMENT = 8;
+    private static final int VALUE = 16;
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    private static List<ParsedHeaderClause> parseStandardHeader(String header)
+    {
+        List<ParsedHeaderClause> clauses = new ArrayList<ParsedHeaderClause>();
+        if (header == null)
+        {
+            return clauses;
+        }
+        ParsedHeaderClause clause = null;
+        String key = null;
+        Map targetMap = null;
+        int state = CLAUSE_START;
+        int currentPosition = 0;
+        int startPosition = 0;
+        int length = header.length();
+        boolean quoted = false;
+        boolean escaped = false;
+
+        char currentChar = EOF;
+        do
+        {
+            currentChar = charAt(currentPosition, header, length);
+            switch (state)
+            {
+                case CLAUSE_START:
+                    clause = new ParsedHeaderClause();
+                    clauses.add(clause);
+                    state = PARAMETER_START;
+                case PARAMETER_START:
+                    startPosition = currentPosition;
+                    state = KEY;
+                case KEY:
+                    switch (currentChar)
+                    {
+                        case ':':
+                        case '=':
+                            key = header.substring(startPosition, currentPosition).trim();
+                            startPosition = currentPosition + 1;
+                            targetMap = clause.attrs;
+                            state = currentChar == ':' ? DIRECTIVE_OR_TYPEDATTRIBUTE : ARGUMENT;
+                            break;
+                        case EOF:
+                        case ',':
+                        case ';':
+                            clause.paths.add(header.substring(startPosition, currentPosition).trim());
+                            state = currentChar == ',' ? CLAUSE_START : PARAMETER_START;
+                            break;
+                        default:
+                            break;
+                    }
+                    currentPosition++;
+                    break;
+                case DIRECTIVE_OR_TYPEDATTRIBUTE:
+                    switch(currentChar)
+                    {
+                        case '=':
+                            if (startPosition != currentPosition)
+                            {
+                                clause.types.put(key, header.substring(startPosition, currentPosition).trim());
+                            }
+                            else
+                            {
+                                targetMap = clause.dirs;
+                            }
+                            state = ARGUMENT;
+                            startPosition = currentPosition + 1;
+                            break;
+                        default:
+                            break;
+                    }
+                    currentPosition++;
+                    break;
+                case ARGUMENT:
+                    if (currentChar == '\"')
+                    {
+                        quoted = true;
+                        currentPosition++;
+                    }
+                    else
+                    {
+                        quoted = false;
+                    }
+                    if (!Character.isWhitespace(currentChar)) {
+                        state = VALUE;
+                    }
+                    else {
+                        currentPosition++;
+                    }
+                    break;
+                case VALUE:
+                    if (escaped)
+                    {
+                        escaped = false;
+                    }
+                    else
+                    {
+                        if (currentChar == '\\' )
+                        {
+                            escaped = true;
+                        }
+                        else if (quoted && currentChar == '\"')
+                        {
+                            quoted = false;
+                        }
+                        else if (!quoted)
+                        {
+                            String value = null;
+                            switch(currentChar)
+                            {
+                                case EOF:
+                                case ';':
+                                case ',':
+                                    value = header.substring(startPosition, currentPosition).trim();
+                                    if (value.startsWith("\"") && value.endsWith("\""))
+                                    {
+                                        value = value.substring(1, value.length() - 1);
+                                    }
+                                    if (targetMap.put(key, value) != null)
+                                    {
+                                        throw new IllegalArgumentException(
+                                                "Duplicate '" + key + "' in: " + header);
+                                    }
+                                    state = currentChar == ';' ? PARAMETER_START : CLAUSE_START;
+                                    break;
+                                default:
+                                    break;
+                            }
+                        }
+                    }
+                    currentPosition++;
+                    break;
+                default:
+                    break;
+            }
+        } while ( currentChar != EOF);
+
+        if (state > PARAMETER_START)
+        {
+            throw new IllegalArgumentException("Unable to parse header: " + header);
+        }
+        return clauses;
+    }
+
+    public static List<String> parseDelimitedString(String value, String delim)
+    {
+        return parseDelimitedString(value, delim, true);
+    }
+
+    /**
+     * Parses delimited string and returns an array containing the tokens. This
+     * parser obeys quotes, so the delimiter character will be ignored if it is
+     * inside of a quote. This method assumes that the quote character is not
+     * included in the set of delimiter characters.
+     * @param value the delimited string to parse.
+     * @param delim the characters delimiting the tokens.
+     * @return a list of string or an empty list if there are none.
+     **/
+    public static List<String> parseDelimitedString(String value, String delim, boolean trim)
+    {
+        if (value == null)
+        {
+            value = "";
+        }
+
+        List<String> list = new ArrayList();
+
+        int CHAR = 1;
+        int DELIMITER = 2;
+        int STARTQUOTE = 4;
+        int ENDQUOTE = 8;
+
+        StringBuffer sb = new StringBuffer();
+
+        int expecting = (CHAR | DELIMITER | STARTQUOTE);
+
+        boolean isEscaped = false;
+        for (int i = 0; i < value.length(); i++)
+        {
+            char c = value.charAt(i);
+
+            boolean isDelimiter = (delim.indexOf(c) >= 0);
+
+            if (!isEscaped && (c == '\\'))
+            {
+                isEscaped = true;
+                continue;
+            }
+
+            if (isEscaped)
+            {
+                sb.append(c);
+            }
+            else if (isDelimiter && ((expecting & DELIMITER) > 0))
+            {
+                if (trim)
+                {
+                    list.add(sb.toString().trim());
+                }
+                else
+                {
+                    list.add(sb.toString());
+                }
+                sb.delete(0, sb.length());
+                expecting = (CHAR | DELIMITER | STARTQUOTE);
+            }
+            else if ((c == '"') && ((expecting & STARTQUOTE) > 0))
+            {
+                sb.append(c);
+                expecting = CHAR | ENDQUOTE;
+            }
+            else if ((c == '"') && ((expecting & ENDQUOTE) > 0))
+            {
+                sb.append(c);
+                expecting = (CHAR | STARTQUOTE | DELIMITER);
+            }
+            else if ((expecting & CHAR) > 0)
+            {
+                sb.append(c);
+            }
+            else
+            {
+                throw new IllegalArgumentException("Invalid delimited string: " + value);
+            }
+
+            isEscaped = false;
+        }
+
+        if (sb.length() > 0)
+        {
+            if (trim)
+            {
+                list.add(sb.toString().trim());
+            }
+            else
+            {
+                list.add(sb.toString());
+            }
+        }
+
+        return list;
+    }
+
+
+    static class ParsedHeaderClause {
+        public final List<String> paths = new ArrayList<String>();
+        public final Map<String, String> dirs = new LinkedHashMap<String, String>();
+        public final Map<String, Object> attrs = new LinkedHashMap<String, Object>();
+        public final Map<String, String> types = new LinkedHashMap<String, String>();
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceImpl.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceImpl.java b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceImpl.java
new file mode 100644
index 0000000..18e0dc3
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceImpl.java
@@ -0,0 +1,110 @@
+/*
+ * 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.resolver;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.osgi.framework.Version;
+import org.osgi.framework.namespace.IdentityNamespace;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Requirement;
+import org.osgi.resource.Resource;
+
+/**
+ */
+public class ResourceImpl implements Resource {
+
+    private final List<Capability> m_caps;
+    private final List<Requirement> m_reqs;
+
+    public ResourceImpl(String name, Version version) {
+        this(name, IdentityNamespace.TYPE_BUNDLE, version);
+    }
+
+    public ResourceImpl(String name, String type, Version version)
+    {
+        m_caps = new ArrayList<Capability>();
+        m_caps.add(0, new IdentityCapability(this, name, type, version));
+        m_reqs = new ArrayList<Requirement>();
+    }
+
+    public void addCapability(Capability capability) {
+        assert capability.getResource() == this;
+        m_caps.add(capability);
+    }
+
+    public void addCapabilities(Iterable<? extends Capability> capabilities) {
+        for (Capability cap : capabilities) {
+            addCapability(cap);
+        }
+    }
+
+    public void addRequirement(Requirement requirement) {
+        assert requirement.getResource() == this;
+        m_reqs.add(requirement);
+    }
+
+    public void addRequirements(Iterable<? extends Requirement> requirements) {
+        for (Requirement req : requirements) {
+            addRequirement(req);
+        }
+    }
+
+    public List<Capability> getCapabilities(String namespace)
+    {
+        List<Capability> result = m_caps;
+        if (namespace != null)
+        {
+            result = new ArrayList<Capability>();
+            for (Capability cap : m_caps)
+            {
+                if (cap.getNamespace().equals(namespace))
+                {
+                    result.add(cap);
+                }
+            }
+        }
+        return result;
+    }
+
+    public List<Requirement> getRequirements(String namespace)
+    {
+        List<Requirement> result = m_reqs;
+        if (namespace != null)
+        {
+            result = new ArrayList<Requirement>();
+            for (Requirement req : m_reqs)
+            {
+                if (req.getNamespace().equals(namespace))
+                {
+                    result.add(req);
+                }
+            }
+        }
+        return result;
+    }
+
+    @Override
+    public String toString()
+    {
+        Capability cap = getCapabilities(IdentityNamespace.IDENTITY_NAMESPACE).get(0);
+        return cap.getAttributes().get(IdentityNamespace.IDENTITY_NAMESPACE) + "/"
+                + cap.getAttributes().get(IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ServiceNamespace.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ServiceNamespace.java b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ServiceNamespace.java
new file mode 100644
index 0000000..4fe3bf8
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ServiceNamespace.java
@@ -0,0 +1,30 @@
+/*
+ * 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.resolver;
+
+import org.osgi.resource.Namespace;
+
+/**
+ */
+public final class ServiceNamespace extends Namespace {
+
+    public static final String SERVICE_NAMESPACE = "service-reference";
+
+    private ServiceNamespace() {
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/internal/resolver/SimpleFilter.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/resolver/SimpleFilter.java b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/SimpleFilter.java
new file mode 100644
index 0000000..ae10441
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/SimpleFilter.java
@@ -0,0 +1,649 @@
+/*
+ * 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.resolver;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.felix.utils.version.VersionRange;
+
+public class SimpleFilter
+{
+    public static final int MATCH_ALL = 0;
+    public static final int AND = 1;
+    public static final int OR = 2;
+    public static final int NOT = 3;
+    public static final int EQ = 4;
+    public static final int LTE = 5;
+    public static final int GTE = 6;
+    public static final int SUBSTRING = 7;
+    public static final int PRESENT = 8;
+    public static final int APPROX = 9;
+
+    private final String m_name;
+    private final Object m_value;
+    private final int m_op;
+
+    public SimpleFilter(String attr, Object value, int op)
+    {
+        m_name = attr;
+        m_value = value;
+        m_op = op;
+    }
+
+    public String getName()
+    {
+        return m_name;
+    }
+
+    public Object getValue()
+    {
+        return m_value;
+    }
+
+    public int getOperation()
+    {
+        return m_op;
+    }
+
+    public String toString()
+    {
+        String s = null;
+        switch (m_op)
+        {
+            case AND:
+                s = "(&" + toString((List) m_value) + ")";
+                break;
+            case OR:
+                s = "(|" + toString((List) m_value) + ")";
+                break;
+            case NOT:
+                s = "(!" + toString((List) m_value) + ")";
+                break;
+            case EQ:
+                s = "(" + m_name + "=" + toEncodedString(m_value) + ")";
+                break;
+            case LTE:
+                s = "(" + m_name + "<=" + toEncodedString(m_value) + ")";
+                break;
+            case GTE:
+                s = "(" + m_name + ">=" + toEncodedString(m_value) + ")";
+                break;
+            case SUBSTRING:
+                s = "(" + m_name + "=" + unparseSubstring((List<String>) m_value) + ")";
+                break;
+            case PRESENT:
+                s = "(" + m_name + "=*)";
+                break;
+            case APPROX:
+                s = "(" + m_name + "~=" + toEncodedString(m_value) + ")";
+                break;
+            case MATCH_ALL:
+                s = "(*)";
+                break;
+        }
+        return s;
+    }
+
+    private static String toString(List list)
+    {
+        StringBuffer sb = new StringBuffer();
+        for (int i = 0; i < list.size(); i++)
+        {
+            sb.append(list.get(i).toString());
+        }
+        return sb.toString();
+    }
+
+    private static String toDecodedString(String s, int startIdx, int endIdx)
+    {
+        StringBuffer sb = new StringBuffer(endIdx - startIdx);
+        boolean escaped = false;
+        for (int i = 0; i < (endIdx - startIdx); i++)
+        {
+            char c = s.charAt(startIdx + i);
+            if (!escaped && (c == '\\'))
+            {
+                escaped = true;
+            }
+            else
+            {
+                escaped = false;
+                sb.append(c);
+            }
+        }
+
+        return sb.toString();
+    }
+
+    private static String toEncodedString(Object o)
+    {
+        if (o instanceof String)
+        {
+            String s = (String) o;
+            StringBuffer sb = new StringBuffer();
+            for (int i = 0; i < s.length(); i++)
+            {
+                char c = s.charAt(i);
+                if ((c == '\\') || (c == '(') || (c == ')') || (c == '*'))
+                {
+                    sb.append('\\');
+                }
+                sb.append(c);
+            }
+
+            o = sb.toString();
+        }
+
+        return o.toString();
+    }
+
+    public static SimpleFilter parse(String filter)
+    {
+        int idx = skipWhitespace(filter, 0);
+
+        if ((filter == null) || (filter.length() == 0) || (idx >= filter.length()))
+        {
+            throw new IllegalArgumentException("Null or empty filter.");
+        }
+        else if (filter.charAt(idx) != '(')
+        {
+            throw new IllegalArgumentException("Missing opening parenthesis: " + filter);
+        }
+
+        SimpleFilter sf = null;
+        List stack = new ArrayList();
+        boolean isEscaped = false;
+        while (idx < filter.length())
+        {
+            if (sf != null)
+            {
+                throw new IllegalArgumentException(
+                        "Only one top-level operation allowed: " + filter);
+            }
+
+            if (!isEscaped && (filter.charAt(idx) == '('))
+            {
+                // Skip paren and following whitespace.
+                idx = skipWhitespace(filter, idx + 1);
+
+                if (filter.charAt(idx) == '&')
+                {
+                    int peek = skipWhitespace(filter, idx + 1);
+                    if (filter.charAt(peek) == '(')
+                    {
+                        idx = peek - 1;
+                        stack.add(0, new SimpleFilter(null, new ArrayList(), SimpleFilter.AND));
+                    }
+                    else
+                    {
+                        stack.add(0, new Integer(idx));
+                    }
+                }
+                else if (filter.charAt(idx) == '|')
+                {
+                    int peek = skipWhitespace(filter, idx + 1);
+                    if (filter.charAt(peek) == '(')
+                    {
+                        idx = peek - 1;
+                        stack.add(0, new SimpleFilter(null, new ArrayList(), SimpleFilter.OR));
+                    }
+                    else
+                    {
+                        stack.add(0, new Integer(idx));
+                    }
+                }
+                else if (filter.charAt(idx) == '!')
+                {
+                    int peek = skipWhitespace(filter, idx + 1);
+                    if (filter.charAt(peek) == '(')
+                    {
+                        idx = peek - 1;
+                        stack.add(0, new SimpleFilter(null, new ArrayList(), SimpleFilter.NOT));
+                    }
+                    else
+                    {
+                        stack.add(0, new Integer(idx));
+                    }
+                }
+                else
+                {
+                    stack.add(0, new Integer(idx));
+                }
+            }
+            else if (!isEscaped && (filter.charAt(idx) == ')'))
+            {
+                Object top = stack.remove(0);
+                if (top instanceof SimpleFilter)
+                {
+                    if (!stack.isEmpty() && (stack.get(0) instanceof SimpleFilter))
+                    {
+                        ((List) ((SimpleFilter) stack.get(0)).m_value).add(top);
+                    }
+                    else
+                    {
+                        sf = (SimpleFilter) top;
+                    }
+                }
+                else if (!stack.isEmpty() && (stack.get(0) instanceof SimpleFilter))
+                {
+                    ((List) ((SimpleFilter) stack.get(0)).m_value).add(
+                            SimpleFilter.subfilter(filter, ((Integer) top).intValue(), idx));
+                }
+                else
+                {
+                    sf = SimpleFilter.subfilter(filter, ((Integer) top).intValue(), idx);
+                }
+            }
+            else if (!isEscaped && (filter.charAt(idx) == '\\'))
+            {
+                isEscaped = true;
+            }
+            else
+            {
+                isEscaped = false;
+            }
+
+            idx = skipWhitespace(filter, idx + 1);
+        }
+
+        if (sf == null)
+        {
+            throw new IllegalArgumentException("Missing closing parenthesis: " + filter);
+        }
+
+        return sf;
+    }
+
+    private static SimpleFilter subfilter(String filter, int startIdx, int endIdx)
+    {
+        final String opChars = "=<>~";
+
+        // Determine the ending index of the attribute name.
+        int attrEndIdx = startIdx;
+        for (int i = 0; i < (endIdx - startIdx); i++)
+        {
+            char c = filter.charAt(startIdx + i);
+            if (opChars.indexOf(c) >= 0)
+            {
+                break;
+            }
+            else if (!Character.isWhitespace(c))
+            {
+                attrEndIdx = startIdx + i + 1;
+            }
+        }
+        if (attrEndIdx == startIdx)
+        {
+            throw new IllegalArgumentException(
+                    "Missing attribute name: " + filter.substring(startIdx, endIdx));
+        }
+        String attr = filter.substring(startIdx, attrEndIdx);
+
+        // Skip the attribute name and any following whitespace.
+        startIdx = skipWhitespace(filter, attrEndIdx);
+
+        // Determine the operator type.
+        int op = -1;
+        switch (filter.charAt(startIdx))
+        {
+            case '=':
+                op = EQ;
+                startIdx++;
+                break;
+            case '<':
+                if (filter.charAt(startIdx + 1) != '=')
+                {
+                    throw new IllegalArgumentException(
+                            "Unknown operator: " + filter.substring(startIdx, endIdx));
+                }
+                op = LTE;
+                startIdx += 2;
+                break;
+            case '>':
+                if (filter.charAt(startIdx + 1) != '=')
+                {
+                    throw new IllegalArgumentException(
+                            "Unknown operator: " + filter.substring(startIdx, endIdx));
+                }
+                op = GTE;
+                startIdx += 2;
+                break;
+            case '~':
+                if (filter.charAt(startIdx + 1) != '=')
+                {
+                    throw new IllegalArgumentException(
+                            "Unknown operator: " + filter.substring(startIdx, endIdx));
+                }
+                op = APPROX;
+                startIdx += 2;
+                break;
+            default:
+                throw new IllegalArgumentException(
+                        "Unknown operator: " + filter.substring(startIdx, endIdx));
+        }
+
+        // Parse value.
+        Object value = toDecodedString(filter, startIdx, endIdx);
+
+        // Check if the equality comparison is actually a substring
+        // or present operation.
+        if (op == EQ)
+        {
+            String valueStr = filter.substring(startIdx, endIdx);
+            List<String> values = parseSubstring(valueStr);
+            if ((values.size() == 2)
+                    && (values.get(0).length() == 0)
+                    && (values.get(1).length() == 0))
+            {
+                op = PRESENT;
+            }
+            else if (values.size() > 1)
+            {
+                op = SUBSTRING;
+                value = values;
+            }
+        }
+
+        return new SimpleFilter(attr, value, op);
+    }
+
+    public static List<String> parseSubstring(String value)
+    {
+        List<String> pieces = new ArrayList();
+        StringBuffer ss = new StringBuffer();
+        // int kind = SIMPLE; // assume until proven otherwise
+        boolean wasStar = false; // indicates last piece was a star
+        boolean leftstar = false; // track if the initial piece is a star
+        boolean rightstar = false; // track if the final piece is a star
+
+        int idx = 0;
+
+        // We assume (sub)strings can contain leading and trailing blanks
+        boolean escaped = false;
+        loop:   for (;;)
+        {
+            if (idx >= value.length())
+            {
+                if (wasStar)
+                {
+                    // insert last piece as "" to handle trailing star
+                    rightstar = true;
+                }
+                else
+                {
+                    pieces.add(ss.toString());
+                    // accumulate the last piece
+                    // note that in the case of
+                    // (cn=); this might be
+                    // the string "" (!=null)
+                }
+                ss.setLength(0);
+                break loop;
+            }
+
+            // Read the next character and account for escapes.
+            char c = value.charAt(idx++);
+            if (!escaped && (c == '*'))
+            {
+                // If we have successive '*' characters, then we can
+                // effectively collapse them by ignoring succeeding ones.
+                if (!wasStar)
+                {
+                    if (ss.length() > 0)
+                    {
+                        pieces.add(ss.toString()); // accumulate the pieces
+                        // between '*' occurrences
+                    }
+                    ss.setLength(0);
+                    // if this is a leading star, then track it
+                    if (pieces.isEmpty())
+                    {
+                        leftstar = true;
+                    }
+                    wasStar = true;
+                }
+            }
+            else if (!escaped && (c == '\\'))
+            {
+                escaped = true;
+            }
+            else
+            {
+                escaped = false;
+                wasStar = false;
+                ss.append(c);
+            }
+        }
+        if (leftstar || rightstar || pieces.size() > 1)
+        {
+            // insert leading and/or trailing "" to anchor ends
+            if (rightstar)
+            {
+                pieces.add("");
+            }
+            if (leftstar)
+            {
+                pieces.add(0, "");
+            }
+        }
+        return pieces;
+    }
+
+    public static String unparseSubstring(List<String> pieces)
+    {
+        StringBuffer sb = new StringBuffer();
+        for (int i = 0; i < pieces.size(); i++)
+        {
+            if (i > 0)
+            {
+                sb.append("*");
+            }
+            sb.append(toEncodedString(pieces.get(i)));
+        }
+        return sb.toString();
+    }
+
+    public static boolean compareSubstring(List<String> pieces, String s)
+    {
+        // Walk the pieces to match the string
+        // There are implicit stars between each piece,
+        // and the first and last pieces might be "" to anchor the match.
+        // assert (pieces.length > 1)
+        // minimal case is <string>*<string>
+
+        boolean result = true;
+        int len = pieces.size();
+
+        // Special case, if there is only one piece, then
+        // we must perform an equality test.
+        if (len == 1)
+        {
+            return s.equals(pieces.get(0));
+        }
+
+        // Otherwise, check whether the pieces match
+        // the specified string.
+
+        int index = 0;
+
+        loop:   for (int i = 0; i < len; i++)
+        {
+            String piece = pieces.get(i);
+
+            // If this is the first piece, then make sure the
+            // string starts with it.
+            if (i == 0)
+            {
+                if (!s.startsWith(piece))
+                {
+                    result = false;
+                    break loop;
+                }
+            }
+
+            // If this is the last piece, then make sure the
+            // string ends with it.
+            if (i == (len - 1))
+            {
+                if (s.endsWith(piece) && (s.length() >= (index + piece.length())))
+                {
+                    result = true;
+                }
+                else
+                {
+                    result = false;
+                }
+                break loop;
+            }
+
+            // If this is neither the first or last piece, then
+            // make sure the string contains it.
+            if ((i > 0) && (i < (len - 1)))
+            {
+                index = s.indexOf(piece, index);
+                if (index < 0)
+                {
+                    result = false;
+                    break loop;
+                }
+            }
+
+            // Move string index beyond the matching piece.
+            index += piece.length();
+        }
+
+        return result;
+    }
+
+    private static int skipWhitespace(String s, int startIdx)
+    {
+        int len = s.length();
+        while ((startIdx < len) && Character.isWhitespace(s.charAt(startIdx)))
+        {
+            startIdx++;
+        }
+        return startIdx;
+    }
+
+    /**
+     * Converts a attribute map to a filter. The filter is created by iterating
+     * over the map's entry set. If ordering of attributes is important (e.g.,
+     * for hitting attribute indices), then the map's entry set should iterate
+     * in the desired order. Equality testing is assumed for all attribute types
+     * other than version ranges, which are handled appropriated. If the attribute
+     * map is empty, then a filter that matches anything is returned.
+     * @param attrs Map of attributes to convert to a filter.
+     * @return A filter corresponding to the attributes.
+     */
+    public static SimpleFilter convert(Map<String, Object> attrs)
+    {
+        // Rather than building a filter string to be parsed into a SimpleFilter,
+        // we will just create the parsed SimpleFilter directly.
+
+        List<SimpleFilter> filters = new ArrayList<SimpleFilter>();
+
+        for (Entry<String, Object> entry : attrs.entrySet())
+        {
+            if (entry.getValue() instanceof VersionRange)
+            {
+                VersionRange vr = (VersionRange) entry.getValue();
+                if (!vr.isOpenFloor())
+                {
+                    filters.add(
+                            new SimpleFilter(
+                                    entry.getKey(),
+                                    vr.getFloor().toString(),
+                                    SimpleFilter.GTE));
+                }
+                else
+                {
+                    SimpleFilter not =
+                            new SimpleFilter(null, new ArrayList(), SimpleFilter.NOT);
+                    ((List) not.getValue()).add(
+                            new SimpleFilter(
+                                    entry.getKey(),
+                                    vr.getFloor().toString(),
+                                    SimpleFilter.LTE));
+                    filters.add(not);
+                }
+
+                if (vr.getCeiling() != null)
+                {
+                    if (!vr.isOpenCeiling())
+                    {
+                        filters.add(
+                                new SimpleFilter(
+                                        entry.getKey(),
+                                        vr.getCeiling().toString(),
+                                        SimpleFilter.LTE));
+                    }
+                    else
+                    {
+                        SimpleFilter not =
+                                new SimpleFilter(null, new ArrayList(), SimpleFilter.NOT);
+                        ((List) not.getValue()).add(
+                                new SimpleFilter(
+                                        entry.getKey(),
+                                        vr.getCeiling().toString(),
+                                        SimpleFilter.GTE));
+                        filters.add(not);
+                    }
+                }
+            }
+            else
+            {
+                List<String> values = SimpleFilter.parseSubstring(entry.getValue().toString());
+                if (values.size() > 1)
+                {
+                    filters.add(
+                            new SimpleFilter(
+                                    entry.getKey(),
+                                    values,
+                                    SimpleFilter.SUBSTRING));
+                }
+                else
+                {
+                    filters.add(
+                            new SimpleFilter(
+                                    entry.getKey(),
+                                    values.get(0),
+                                    SimpleFilter.EQ));
+                }
+            }
+        }
+
+        SimpleFilter sf = null;
+
+        if (filters.size() == 1)
+        {
+            sf = filters.get(0);
+        }
+        else if (attrs.size() > 1)
+        {
+            sf = new SimpleFilter(null, filters, SimpleFilter.AND);
+        }
+        else if (filters.isEmpty())
+        {
+            sf = new SimpleFilter(null, null, SimpleFilter.MATCH_ALL);
+        }
+
+        return sf;
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/internal/resolver/Slf4jResolverLog.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/resolver/Slf4jResolverLog.java b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/Slf4jResolverLog.java
new file mode 100644
index 0000000..2f4a1f3
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/Slf4jResolverLog.java
@@ -0,0 +1,49 @@
+/*
+ * 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.resolver;
+
+import org.slf4j.Logger;
+
+/**
+ */
+public class Slf4jResolverLog extends org.apache.felix.resolver.Logger {
+
+    private final Logger logger;
+
+    public Slf4jResolverLog(Logger logger) {
+        super(LOG_DEBUG);
+        this.logger = logger;
+    }
+
+    @Override
+    protected void doLog(int level, String msg, Throwable throwable) {
+        switch (level) {
+            case LOG_ERROR:
+                logger.error(msg, throwable);
+                break;
+            case LOG_WARNING:
+                logger.warn(msg, throwable);
+                break;
+            case LOG_INFO:
+                logger.info(msg, throwable);
+                break;
+            case LOG_DEBUG:
+                logger.debug(msg, throwable);
+                break;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/internal/resolver/UriNamespace.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/resolver/UriNamespace.java b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/UriNamespace.java
new file mode 100644
index 0000000..b5158bf
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/UriNamespace.java
@@ -0,0 +1,47 @@
+/*
+ * 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.resolver;
+
+import java.util.List;
+
+import org.osgi.resource.Capability;
+import org.osgi.resource.Namespace;
+import org.osgi.resource.Resource;
+
+/**
+ */
+public final class UriNamespace extends Namespace {
+
+    public static final String URI_NAMESPACE = "karaf.uri";
+
+    public static String getUri(Resource resource)
+    {
+        List<Capability> caps = resource.getCapabilities(null);
+        for (Capability cap : caps)
+        {
+            if (cap.getNamespace().equals(UriNamespace.URI_NAMESPACE))
+            {
+                return cap.getAttributes().get(UriNamespace.URI_NAMESPACE).toString();
+            }
+        }
+        return null;
+    }
+
+
+    private UriNamespace() {
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/internal/service/Artifact.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/Artifact.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/Artifact.java
new file mode 100644
index 0000000..44e9a7c
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/Artifact.java
@@ -0,0 +1,56 @@
+/*
+ * 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.service;
+
+import java.net.URI;
+
+/**
+ * Simple abstraction of a maven artifact to avoid external deps
+ */
+public class Artifact {
+    String groupId;
+    String artifactId;
+    String version;
+    String extension;
+    String classifier;
+    
+    public Artifact(String coords) {
+        String[] coordsAr = coords.split(":");
+        if (coordsAr.length != 5) {
+            throw new IllegalArgumentException("Maven URL " + coords + " is malformed or not complete");
+        }
+        this.groupId = coordsAr[0];
+        this.artifactId = coordsAr[1];
+        this.version = coordsAr[4];
+        this.extension = coordsAr[2];
+        this.classifier = coordsAr[3];
+    }
+    
+    public Artifact(String coords, String version) {
+        this(coords);
+        this.version = version;
+    }
+    
+    public URI getMavenUrl(String version) {
+        String uriSt = "mvn:" + this.groupId + "/" + this.artifactId + "/" + version + "/" + this.extension + "/" + this.classifier;
+        try {
+            return new URI(uriSt);
+        } catch (Exception e) {
+            return null;
+        }
+    }
+}


[10/13] [KARAF-2888] New FeaturesService based on the real OSGi resolver

Posted by gn...@apache.org.
http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/internal/resolver/CapabilitySet.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/resolver/CapabilitySet.java b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/CapabilitySet.java
new file mode 100644
index 0000000..4c5656d
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/CapabilitySet.java
@@ -0,0 +1,612 @@
+/*
+ * 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.resolver;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Constructor;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.TreeMap;
+
+import org.osgi.framework.Constants;
+import org.osgi.resource.Capability;
+
+public class CapabilitySet
+{
+    private final Map<String, Map<Object, Set<Capability>>> m_indices;
+    private final Set<Capability> m_capSet = new HashSet<Capability>();
+
+public void dump()
+{
+    for (Entry<String, Map<Object, Set<Capability>>> entry : m_indices.entrySet())
+    {
+        boolean header1 = false;
+        for (Entry<Object, Set<Capability>> entry2 : entry.getValue().entrySet())
+        {
+            boolean header2 = false;
+            for (Capability cap : entry2.getValue())
+            {
+                if (!header1)
+                {
+                    System.out.println(entry.getKey() + ":");
+                    header1 = true;
+                }
+                if (!header2)
+                {
+                    System.out.println("   " + entry2.getKey());
+                    header2 = true;
+                }
+                System.out.println("      " + cap);
+            }
+        }
+    }
+}
+
+    public CapabilitySet(List<String> indexProps)
+    {
+        m_indices = new TreeMap<String, Map<Object, Set<Capability>>>();
+        for (int i = 0; (indexProps != null) && (i < indexProps.size()); i++)
+        {
+            m_indices.put(
+                indexProps.get(i), new HashMap<Object, Set<Capability>>());
+        }
+    }
+
+    public void addCapability(Capability cap)
+    {
+        m_capSet.add(cap);
+
+        // Index capability.
+        for (Entry<String, Map<Object, Set<Capability>>> entry : m_indices.entrySet())
+        {
+            Object value = cap.getAttributes().get(entry.getKey());
+            if (value != null)
+            {
+                if (value.getClass().isArray())
+                {
+                    value = convertArrayToList(value);
+                }
+
+                Map<Object, Set<Capability>> index = entry.getValue();
+
+                if (value instanceof Collection)
+                {
+                    Collection c = (Collection) value;
+                    for (Object o : c)
+                    {
+                        indexCapability(index, cap, o);
+                    }
+                }
+                else
+                {
+                    indexCapability(index, cap, value);
+                }
+            }
+        }
+    }
+
+    private void indexCapability(
+        Map<Object, Set<Capability>> index, Capability cap, Object capValue)
+    {
+        Set<Capability> caps = index.get(capValue);
+        if (caps == null)
+        {
+            caps = new HashSet<Capability>();
+            index.put(capValue, caps);
+        }
+        caps.add(cap);
+    }
+
+    public void removeCapability(Capability cap)
+    {
+        if (m_capSet.remove(cap))
+        {
+            for (Entry<String, Map<Object, Set<Capability>>> entry : m_indices.entrySet())
+            {
+                Object value = cap.getAttributes().get(entry.getKey());
+                if (value != null)
+                {
+                    if (value.getClass().isArray())
+                    {
+                        value = convertArrayToList(value);
+                    }
+
+                    Map<Object, Set<Capability>> index = entry.getValue();
+
+                    if (value instanceof Collection)
+                    {
+                        Collection c = (Collection) value;
+                        for (Object o : c)
+                        {
+                            deindexCapability(index, cap, o);
+                        }
+                    }
+                    else
+                    {
+                        deindexCapability(index, cap, value);
+                    }
+                }
+            }
+        }
+    }
+
+    private void deindexCapability(
+        Map<Object, Set<Capability>> index, Capability cap, Object value)
+    {
+        Set<Capability> caps = index.get(value);
+        if (caps != null)
+        {
+            caps.remove(cap);
+            if (caps.isEmpty())
+            {
+                index.remove(value);
+            }
+        }
+    }
+
+    public Set<Capability> match(SimpleFilter sf, boolean obeyMandatory)
+    {
+        Set<Capability> matches = match(m_capSet, sf);
+        return (obeyMandatory)
+            ? matchMandatory(matches, sf)
+            : matches;
+    }
+
+    private Set<Capability> match(Set<Capability> caps, SimpleFilter sf)
+    {
+        Set<Capability> matches = new HashSet<Capability>();
+
+        if (sf.getOperation() == SimpleFilter.MATCH_ALL)
+        {
+            matches.addAll(caps);
+        }
+        else if (sf.getOperation() == SimpleFilter.AND)
+        {
+            // Evaluate each subfilter against the remaining capabilities.
+            // For AND we calculate the intersection of each subfilter.
+            // We can short-circuit the AND operation if there are no
+            // remaining capabilities.
+            List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue();
+            for (int i = 0; (caps.size() > 0) && (i < sfs.size()); i++)
+            {
+                matches = match(caps, sfs.get(i));
+                caps = matches;
+            }
+        }
+        else if (sf.getOperation() == SimpleFilter.OR)
+        {
+            // Evaluate each subfilter against the remaining capabilities.
+            // For OR we calculate the union of each subfilter.
+            List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue();
+            for (int i = 0; i < sfs.size(); i++)
+            {
+                matches.addAll(match(caps, sfs.get(i)));
+            }
+        }
+        else if (sf.getOperation() == SimpleFilter.NOT)
+        {
+            // Evaluate each subfilter against the remaining capabilities.
+            // For OR we calculate the union of each subfilter.
+            matches.addAll(caps);
+            List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue();
+            for (int i = 0; i < sfs.size(); i++)
+            {
+                matches.removeAll(match(caps, sfs.get(i)));
+            }
+        }
+        else
+        {
+            Map<Object, Set<Capability>> index = m_indices.get(sf.getName());
+            if ((sf.getOperation() == SimpleFilter.EQ) && (index != null))
+            {
+                Set<Capability> existingCaps = index.get(sf.getValue());
+                if (existingCaps != null)
+                {
+                    matches.addAll(existingCaps);
+                    matches.retainAll(caps);
+                }
+            }
+            else
+            {
+                for (Iterator<Capability> it = caps.iterator(); it.hasNext(); )
+                {
+                    Capability cap = it.next();
+                    Object lhs = cap.getAttributes().get(sf.getName());
+                    if (lhs != null)
+                    {
+                        if (compare(lhs, sf.getValue(), sf.getOperation()))
+                        {
+                            matches.add(cap);
+                        }
+                    }
+                }
+            }
+        }
+
+        return matches;
+    }
+
+    public static boolean matches(Capability cap, SimpleFilter sf)
+    {
+        return matchesInternal(cap, sf) && matchMandatory(cap, sf);
+    }
+
+    private static boolean matchesInternal(Capability cap, SimpleFilter sf)
+    {
+        boolean matched = true;
+
+        if (sf.getOperation() == SimpleFilter.MATCH_ALL)
+        {
+            matched = true;
+        }
+        else if (sf.getOperation() == SimpleFilter.AND)
+        {
+            // Evaluate each subfilter against the remaining capabilities.
+            // For AND we calculate the intersection of each subfilter.
+            // We can short-circuit the AND operation if there are no
+            // remaining capabilities.
+            List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue();
+            for (int i = 0; matched && (i < sfs.size()); i++)
+            {
+                matched = matchesInternal(cap, sfs.get(i));
+            }
+        }
+        else if (sf.getOperation() == SimpleFilter.OR)
+        {
+            // Evaluate each subfilter against the remaining capabilities.
+            // For OR we calculate the union of each subfilter.
+            matched = false;
+            List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue();
+            for (int i = 0; !matched && (i < sfs.size()); i++)
+            {
+                matched = matchesInternal(cap, sfs.get(i));
+            }
+        }
+        else if (sf.getOperation() == SimpleFilter.NOT)
+        {
+            // Evaluate each subfilter against the remaining capabilities.
+            // For OR we calculate the union of each subfilter.
+            List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue();
+            for (int i = 0; i < sfs.size(); i++)
+            {
+                matched = !(matchesInternal(cap, sfs.get(i)));
+            }
+        }
+        else
+        {
+            matched = false;
+            Object lhs = cap.getAttributes().get(sf.getName());
+            if (lhs != null)
+            {
+                matched = compare(lhs, sf.getValue(), sf.getOperation());
+            }
+        }
+
+        return matched;
+    }
+
+    private static Set<Capability> matchMandatory(
+        Set<Capability> caps, SimpleFilter sf)
+    {
+        for (Iterator<Capability> it = caps.iterator(); it.hasNext(); )
+        {
+            Capability cap = it.next();
+            if (!matchMandatory(cap, sf))
+            {
+                it.remove();
+            }
+        }
+        return caps;
+    }
+
+    private static boolean matchMandatory(Capability cap, SimpleFilter sf)
+    {
+        if (cap instanceof CapabilityImpl) {
+            for (Entry<String, Object> entry : cap.getAttributes().entrySet())
+            {
+                if (((CapabilityImpl) cap).isAttributeMandatory(entry.getKey())
+                    && !matchMandatoryAttribute(entry.getKey(), sf))
+                {
+                    return false;
+                }
+            }
+        } else {
+            String value = cap.getDirectives().get(Constants.MANDATORY_DIRECTIVE);
+            if (value != null) {
+                List<String> names = ResourceBuilder.parseDelimitedString(value, ",");
+                for (Entry<String, Object> entry : cap.getAttributes().entrySet())
+                {
+                    if (names.contains(entry.getKey())
+                            && !matchMandatoryAttribute(entry.getKey(), sf))
+                    {
+                        return false;
+                    }
+                }
+            }
+
+        }
+        return true;
+    }
+
+    private static boolean matchMandatoryAttribute(String attrName, SimpleFilter sf)
+    {
+        if ((sf.getName() != null) && sf.getName().equals(attrName))
+        {
+            return true;
+        }
+        else if (sf.getOperation() == SimpleFilter.AND)
+        {
+            List list = (List) sf.getValue();
+            for (int i = 0; i < list.size(); i++)
+            {
+                SimpleFilter sf2 = (SimpleFilter) list.get(i);
+                if ((sf2.getName() != null)
+                    && sf2.getName().equals(attrName))
+                {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private static final Class<?>[] STRING_CLASS = new Class[] { String.class };
+
+    private static boolean compare(Object lhs, Object rhsUnknown, int op)
+    {
+        if (lhs == null)
+        {
+            return false;
+        }
+
+        // If this is a PRESENT operation, then just return true immediately
+        // since we wouldn't be here if the attribute wasn't present.
+        if (op == SimpleFilter.PRESENT)
+        {
+            return true;
+        }
+
+        // If the type is comparable, then we can just return the
+        // result immediately.
+        if (lhs instanceof Comparable)
+        {
+            // Spec says SUBSTRING is false for all types other than string.
+            if ((op == SimpleFilter.SUBSTRING) && !(lhs instanceof String))
+            {
+                return false;
+            }
+
+            Object rhs;
+            if (op == SimpleFilter.SUBSTRING)
+            {
+                rhs = rhsUnknown;
+            }
+            else
+            {
+                try
+                {
+                    rhs = coerceType(lhs, (String) rhsUnknown);
+                }
+                catch (Exception ex)
+                {
+                    return false;
+                }
+            }
+
+            switch (op)
+            {
+                case SimpleFilter.EQ :
+                    try
+                    {
+                        return (((Comparable) lhs).compareTo(rhs) == 0);
+                    }
+                    catch (Exception ex)
+                    {
+                        return false;
+                    }
+                case SimpleFilter.GTE :
+                    try
+                    {
+                        return (((Comparable) lhs).compareTo(rhs) >= 0);
+                    }
+                    catch (Exception ex)
+                    {
+                        return false;
+                    }
+                case SimpleFilter.LTE :
+                    try
+                    {
+                        return (((Comparable) lhs).compareTo(rhs) <= 0);
+                    }
+                    catch (Exception ex)
+                    {
+                        return false;
+                    }
+                case SimpleFilter.APPROX :
+                    return compareApproximate(((Comparable) lhs), rhs);
+                case SimpleFilter.SUBSTRING :
+                    return SimpleFilter.compareSubstring((List<String>) rhs, (String) lhs);
+                default:
+                    throw new RuntimeException(
+                        "Unknown comparison operator: " + op);
+            }
+        }
+        // Booleans do not implement comparable, so special case them.
+        else if (lhs instanceof Boolean)
+        {
+            Object rhs;
+            try
+            {
+                rhs = coerceType(lhs, (String) rhsUnknown);
+            }
+            catch (Exception ex)
+            {
+                return false;
+            }
+
+            switch (op)
+            {
+                case SimpleFilter.EQ :
+                case SimpleFilter.GTE :
+                case SimpleFilter.LTE :
+                case SimpleFilter.APPROX :
+                    return (lhs.equals(rhs));
+                default:
+                    throw new RuntimeException(
+                        "Unknown comparison operator: " + op);
+            }
+        }
+
+        // If the LHS is not a comparable or boolean, check if it is an
+        // array. If so, convert it to a list so we can treat it as a
+        // collection.
+        if (lhs.getClass().isArray())
+        {
+            lhs = convertArrayToList(lhs);
+        }
+
+        // If LHS is a collection, then call compare() on each element
+        // of the collection until a match is found.
+        if (lhs instanceof Collection)
+        {
+            for (Iterator iter = ((Collection) lhs).iterator(); iter.hasNext(); )
+            {
+                if (compare(iter.next(), rhsUnknown, op))
+                {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
+        // Spec says SUBSTRING is false for all types other than string.
+        if ((op == SimpleFilter.SUBSTRING) && !(lhs instanceof String))
+        {
+            return false;
+        }
+
+        // Since we cannot identify the LHS type, then we can only perform
+        // equality comparison.
+        try
+        {
+            return lhs.equals(coerceType(lhs, (String) rhsUnknown));
+        }
+        catch (Exception ex)
+        {
+            return false;
+        }
+    }
+
+    private static boolean compareApproximate(Object lhs, Object rhs)
+    {
+        if (rhs instanceof String)
+        {
+            return removeWhitespace((String) lhs)
+                .equalsIgnoreCase(removeWhitespace((String) rhs));
+        }
+        else if (rhs instanceof Character)
+        {
+            return Character.toLowerCase(((Character) lhs))
+                == Character.toLowerCase(((Character) rhs));
+        }
+        return lhs.equals(rhs);
+    }
+
+    private static String removeWhitespace(String s)
+    {
+        StringBuffer sb = new StringBuffer(s.length());
+        for (int i = 0; i < s.length(); i++)
+        {
+            if (!Character.isWhitespace(s.charAt(i)))
+            {
+                sb.append(s.charAt(i));
+            }
+        }
+        return sb.toString();
+    }
+
+    private static Object coerceType(Object lhs, String rhsString) throws Exception
+    {
+        // If the LHS expects a string, then we can just return
+        // the RHS since it is a string.
+        if (lhs.getClass() == rhsString.getClass())
+        {
+            return rhsString;
+        }
+
+        // Try to convert the RHS type to the LHS type by using
+        // the string constructor of the LHS class, if it has one.
+        Object rhs = null;
+        try
+        {
+            // The Character class is a special case, since its constructor
+            // does not take a string, so handle it separately.
+            if (lhs instanceof Character)
+            {
+                rhs = new Character(rhsString.charAt(0));
+            }
+            else
+            {
+                // Spec says we should trim number types.
+                if ((lhs instanceof Number) || (lhs instanceof Boolean))
+                {
+                    rhsString = rhsString.trim();
+                }
+                Constructor ctor = lhs.getClass().getConstructor(STRING_CLASS);
+                ctor.setAccessible(true);
+                rhs = ctor.newInstance(new Object[] { rhsString });
+            }
+        }
+        catch (Exception ex)
+        {
+            throw new Exception(
+                "Could not instantiate class "
+                    + lhs.getClass().getName()
+                    + " from string constructor with argument '"
+                    + rhsString + "' because " + ex);
+        }
+
+        return rhs;
+    }
+
+    /**
+     * This is an ugly utility method to convert an array of primitives
+     * to an array of primitive wrapper objects. This method simplifies
+     * processing LDAP filters since the special case of primitive arrays
+     * can be ignored.
+     * @param array An array of primitive types.
+     * @return An corresponding array using pritive wrapper objects.
+    **/
+    private static List convertArrayToList(Object array)
+    {
+        int len = Array.getLength(array);
+        List list = new ArrayList(len);
+        for (int i = 0; i < len; i++)
+        {
+            list.add(Array.get(array, i));
+        }
+        return list;
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/internal/resolver/FeatureNamespace.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/resolver/FeatureNamespace.java b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/FeatureNamespace.java
new file mode 100644
index 0000000..e211618
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/FeatureNamespace.java
@@ -0,0 +1,72 @@
+/*
+ * 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.resolver;
+
+import java.util.List;
+
+import org.osgi.framework.Version;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Namespace;
+import org.osgi.resource.Resource;
+
+/**
+ */
+public final class FeatureNamespace extends Namespace {
+
+    public static final String FEATURE_NAMESPACE = "karaf.feature";
+
+    public static final String	CAPABILITY_VERSION_ATTRIBUTE	= "version";
+
+    /**
+     * The attribute value identifying the resource
+     * {@link org.osgi.framework.namespace.IdentityNamespace#CAPABILITY_TYPE_ATTRIBUTE type} as an OSGi bundle.
+     *
+     * @see org.osgi.framework.namespace.IdentityNamespace#CAPABILITY_TYPE_ATTRIBUTE
+     */
+    public static final String	TYPE_FEATURE = "karaf.feature";
+
+    public static String getName(Resource resource)
+    {
+        List<Capability> caps = resource.getCapabilities(null);
+        for (Capability cap : caps)
+        {
+            if (cap.getNamespace().equals(FEATURE_NAMESPACE))
+            {
+                return cap.getAttributes().get(FEATURE_NAMESPACE).toString();
+            }
+        }
+        return null;
+    }
+
+    public static Version getVersion(Resource resource)
+    {
+        List<Capability> caps = resource.getCapabilities(null);
+        for (Capability cap : caps)
+        {
+            if (cap.getNamespace().equals(FEATURE_NAMESPACE))
+            {
+                return (Version)
+                        cap.getAttributes().get(CAPABILITY_VERSION_ATTRIBUTE);
+            }
+        }
+        return null;
+    }
+
+
+    private FeatureNamespace() {
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/internal/resolver/FeatureResource.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/resolver/FeatureResource.java b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/FeatureResource.java
new file mode 100644
index 0000000..7d1671c
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/FeatureResource.java
@@ -0,0 +1,101 @@
+/*
+ * 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.resolver;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.felix.utils.version.VersionRange;
+import org.apache.felix.utils.version.VersionTable;
+import org.apache.karaf.features.BundleInfo;
+import org.apache.karaf.features.Dependency;
+import org.apache.karaf.features.Feature;
+import org.apache.karaf.features.internal.util.Macro;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.Constants;
+import org.osgi.framework.Version;
+import org.osgi.framework.namespace.IdentityNamespace;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Requirement;
+import org.osgi.resource.Resource;
+
+/**
+*/
+public class FeatureResource extends ResourceImpl {
+
+    private final Feature feature;
+
+    public static Resource build(Feature feature, String featureRange, Map<String, Resource> locToRes) throws BundleException {
+        FeatureResource resource = new FeatureResource(feature);
+        Map<String, String> dirs = new HashMap<String, String>();
+        Map<String, Object> attrs = new HashMap<String, Object>();
+        attrs.put(FeatureNamespace.FEATURE_NAMESPACE, feature.getName());
+        attrs.put(FeatureNamespace.CAPABILITY_VERSION_ATTRIBUTE, VersionTable.getVersion(feature.getVersion()));
+        resource.addCapability(new CapabilityImpl(resource, FeatureNamespace.FEATURE_NAMESPACE, dirs, attrs));
+        for (BundleInfo info : feature.getBundles()) {
+            if (!info.isDependency()) {
+                Resource res = locToRes.get(info.getLocation());
+                if (res == null) {
+                    throw new IllegalStateException("Resource not found for url " + info.getLocation());
+                }
+                List<Capability> caps = res.getCapabilities(IdentityNamespace.IDENTITY_NAMESPACE);
+                if (caps.size() != 1) {
+                    throw new IllegalStateException("Resource does not have a single " + IdentityNamespace.IDENTITY_NAMESPACE + " capability");
+                }
+                dirs = new HashMap<String, String>();
+                attrs = new HashMap<String, Object>();
+                attrs.put(IdentityNamespace.IDENTITY_NAMESPACE, caps.get(0).getAttributes().get(IdentityNamespace.IDENTITY_NAMESPACE));
+                attrs.put(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE, caps.get(0).getAttributes().get(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE));
+                attrs.put(IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE, new VersionRange((Version) caps.get(0).getAttributes().get(IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE), true));
+                resource.addRequirement(new RequirementImpl(resource, IdentityNamespace.IDENTITY_NAMESPACE, dirs, attrs));
+            }
+        }
+        for (Dependency dep : feature.getDependencies()) {
+            String name = dep.getName();
+            String version = dep.getVersion();
+            if (version.equals("0.0.0")) {
+                version = null;
+            } else if (!version.startsWith("[") && !version.startsWith("(")) {
+                version = Macro.transform(featureRange, version);
+            }
+            dirs = new HashMap<String, String>();
+            attrs = new HashMap<String, Object>();
+            attrs.put(FeatureNamespace.FEATURE_NAMESPACE, name);
+            if (version != null) {
+                attrs.put(FeatureNamespace.CAPABILITY_VERSION_ATTRIBUTE, new VersionRange(version));
+            }
+            resource.addRequirement(new RequirementImpl(resource, FeatureNamespace.FEATURE_NAMESPACE, dirs, attrs));
+        }
+        for (org.apache.karaf.features.Capability cap : feature.getCapabilities()) {
+            resource.addCapabilities(ResourceBuilder.parseCapability(resource, cap.getValue()));
+        }
+        for (org.apache.karaf.features.Requirement req : feature.getRequirements()) {
+            resource.addRequirements(ResourceBuilder.parseRequirement(resource, req.getValue()));
+        }
+        return resource;
+    }
+
+    public FeatureResource(Feature feature) {
+        super(feature.getName(), FeatureNamespace.TYPE_FEATURE, VersionTable.getVersion(feature.getVersion()));
+        this.feature = feature;
+    }
+
+    public Feature getFeature() {
+        return feature;
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/internal/resolver/IdentityCapability.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/resolver/IdentityCapability.java b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/IdentityCapability.java
new file mode 100644
index 0000000..cdc00d1
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/IdentityCapability.java
@@ -0,0 +1,63 @@
+/*
+ * 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.resolver;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.osgi.framework.Version;
+import org.osgi.framework.namespace.IdentityNamespace;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Resource;
+
+class IdentityCapability extends BaseClause implements Capability
+{
+    private final Resource m_resource;
+    private final Map<String, String> m_dirs;
+    private final Map<String, Object> m_attrs;
+
+    public IdentityCapability(Resource resource, String name, String type, Version version)
+    {
+        m_resource = resource;
+        m_dirs = new HashMap<String, String>();
+        m_attrs = new HashMap<String, Object>();
+        m_attrs.put(IdentityNamespace.IDENTITY_NAMESPACE, name);
+        m_attrs.put(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE, type);
+        m_attrs.put(IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE, version);
+    }
+
+    public String getNamespace()
+    {
+        return IdentityNamespace.IDENTITY_NAMESPACE;
+    }
+
+    public Map<String, String> getDirectives()
+    {
+        return m_dirs;
+    }
+
+    public Map<String, Object> getAttributes()
+    {
+        return m_attrs;
+    }
+
+    public Resource getResource()
+    {
+        return m_resource;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/internal/resolver/RequirementImpl.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/resolver/RequirementImpl.java b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/RequirementImpl.java
new file mode 100644
index 0000000..a4ef775
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/RequirementImpl.java
@@ -0,0 +1,80 @@
+/*
+ * 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.resolver;
+
+import java.util.Map;
+
+import org.osgi.framework.Constants;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Requirement;
+import org.osgi.resource.Resource;
+
+public class RequirementImpl extends BaseClause implements Requirement {
+    private final Resource m_resource;
+    private final String m_namespace;
+    private final SimpleFilter m_filter;
+    private final boolean m_optional;
+    private final Map<String, String> m_dirs;
+    private final Map<String, Object> m_attrs;
+
+    public RequirementImpl(
+            Resource resource, String namespace,
+            Map<String, String> dirs, Map<String, Object> attrs, SimpleFilter filter) {
+        m_resource = resource;
+        m_namespace = namespace;
+        m_dirs = dirs;
+        m_attrs = attrs;
+        m_filter = filter;
+        // Find resolution import directives.
+        m_optional = Constants.RESOLUTION_OPTIONAL.equals(m_dirs.get(Constants.RESOLUTION_DIRECTIVE));
+    }
+
+    public RequirementImpl(
+            Resource resource, String namespace,
+            Map<String, String> dirs, Map<String, Object> attrs) {
+        this(resource, namespace, dirs, attrs, SimpleFilter.convert(attrs));
+    }
+
+    public String getNamespace() {
+        return m_namespace;
+    }
+
+    public Map<String, String> getDirectives() {
+        return m_dirs;
+    }
+
+    public Map<String, Object> getAttributes() {
+        return m_attrs;
+    }
+
+    public Resource getResource() {
+        return m_resource;
+    }
+
+    public boolean matches(Capability cap) {
+        return CapabilitySet.matches(cap, getFilter());
+    }
+
+    public boolean isOptional() {
+        return m_optional;
+    }
+
+    public SimpleFilter getFilter() {
+        return m_filter;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResolveContextImpl.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResolveContextImpl.java b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResolveContextImpl.java
new file mode 100644
index 0000000..e2ff793
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResolveContextImpl.java
@@ -0,0 +1,102 @@
+/*
+ * 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.resolver;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.osgi.framework.Constants;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Requirement;
+import org.osgi.resource.Resource;
+import org.osgi.resource.Wiring;
+import org.osgi.service.repository.Repository;
+import org.osgi.service.resolver.HostedCapability;
+import org.osgi.service.resolver.ResolveContext;
+
+/**
+*/
+public class ResolveContextImpl extends ResolveContext {
+
+    private final Set<Resource> mandatory;
+    private final Set<Resource> optional;
+    private final Repository repository;
+    private final Map<Resource, Wiring> wirings;
+    private final boolean resolveOptional;
+
+    private final CandidateComparator candidateComparator = new CandidateComparator();
+
+    public ResolveContextImpl(Set<Resource> mandatory,
+                              Set<Resource> optional,
+                              Repository repository,
+                              boolean resolveOptional) {
+        this.mandatory = mandatory;
+        this.optional = optional;
+        this.repository = repository;
+        this.wirings = new HashMap<Resource, Wiring>();
+        this.resolveOptional = resolveOptional;
+    }
+
+    @Override
+    public Collection<Resource> getMandatoryResources() {
+        return mandatory;
+    }
+
+    @Override
+    public Collection<Resource> getOptionalResources() {
+        return optional;
+    }
+
+    @Override
+    public List<Capability> findProviders(Requirement requirement) {
+        List<Capability> caps = new ArrayList<Capability>();
+        Map<Requirement, Collection<Capability>> resMap =
+                repository.findProviders(Collections.singleton(requirement));
+        Collection<Capability> res = resMap != null ? resMap.get(requirement) : null;
+        if (res != null) {
+            caps.addAll(res);
+        }
+        Collections.sort(caps, candidateComparator);
+        return caps;
+    }
+    @Override
+    public int insertHostedCapability(List capabilities, HostedCapability hostedCapability) {
+        for (int i=0; i < capabilities.size(); i++) {
+            Capability cap = (Capability) capabilities.get(i);
+            if (candidateComparator.compare(hostedCapability, cap) <= 0) {
+                capabilities.add(i, hostedCapability);
+                return i;
+            }
+        }
+        capabilities.add(hostedCapability);
+        return capabilities.size() - 1;
+    }
+    @Override
+    public boolean isEffective(Requirement requirement) {
+        return resolveOptional ||
+                !Constants.RESOLUTION_OPTIONAL.equals(requirement.getDirectives().get(Constants.RESOLUTION_DIRECTIVE));
+    }
+    @Override
+    public Map<Resource, Wiring> getWirings() {
+        return wirings;
+    }
+}


[05/13] [KARAF-2888] New FeaturesService based on the real OSGi resolver

Posted by gn...@apache.org.
http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/test/resources/org/apache/karaf/features/internal/f01.xml
----------------------------------------------------------------------
diff --git a/features/core/src/test/resources/org/apache/karaf/features/internal/f01.xml b/features/core/src/test/resources/org/apache/karaf/features/internal/f01.xml
deleted file mode 100644
index 814c722..0000000
--- a/features/core/src/test/resources/org/apache/karaf/features/internal/f01.xml
+++ /dev/null
@@ -1,92 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-      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.
--->
-<features name="karaf-2.0.0">
-    <feature name="spring" version="3.0.3.RELEASE">
-        <bundle>mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.aopalliance/1.0_6</bundle>
-        <bundle>mvn:org.springframework/spring-core/3.0.3.RELEASE</bundle>
-        <bundle>mvn:org.springframework/spring-asm/3.0.3.RELEASE</bundle>
-        <bundle>mvn:org.springframework/spring-expression/3.0.3.RELEASE</bundle>
-        <bundle>mvn:org.springframework/spring-beans/3.0.3.RELEASE</bundle>
-        <bundle>mvn:org.springframework/spring-aop/3.0.3.RELEASE</bundle>
-        <bundle>mvn:org.springframework/spring-context/3.0.3.RELEASE</bundle>
-        <bundle>mvn:org.springframework/spring-context-support/3.0.3.RELEASE</bundle>
-    </feature>
-    <feature name="spring-dm" version="1.2.0">
-        <feature version="3.0.3.RELEASE">spring</feature>
-        <bundle>mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.cglib/2.2.2_1</bundle>
-        <bundle>mvn:org.springframework.osgi/spring-osgi-io/1.2.0</bundle>
-        <bundle>mvn:org.springframework.osgi/spring-osgi-core/1.2.0</bundle>
-        <bundle>mvn:org.springframework.osgi/spring-osgi-extender/1.2.0</bundle>
-        <bundle>mvn:org.springframework.osgi/spring-osgi-annotation/1.2.0</bundle>
-        <bundle>mvn:org.apache.karaf.deployer/org.apache.karaf.deployer.spring/2.0.0</bundle>
-    </feature>
-    <feature name="wrapper" version="2.0.0">
-        <bundle>mvn:org.apache.karaf.shell/org.apache.karaf.shell.wrapper/2.0.0</bundle>
-    </feature>
-    <feature name="obr" version="2.0.0">
-        <bundle>mvn:org.apache.felix/org.apache.felix.bundlerepository/1.6.4</bundle>
-        <bundle>mvn:org.apache.karaf.shell/org.apache.karaf.shell.obr/2.0.0</bundle>
-        <bundle>mvn:org.apache.karaf.features/org.apache.karaf.features.obr/2.0.0</bundle>
-    </feature>
-    <feature name="http" version="2.0.0">
-        <config name="org.ops4j.pax.web">
-            org.osgi.service.http.port=8181
-        </config>
-        <bundle>mvn:org.apache.geronimo.specs/geronimo-servlet_2.5_spec/1.1.2</bundle>
-        <bundle>mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.jetty-bundle/6.1.22_1</bundle>
-        <bundle>mvn:org.ops4j.pax.web/pax-web-api/0.7.2</bundle>
-        <bundle>mvn:org.ops4j.pax.web/pax-web-spi/0.7.2</bundle>
-        <bundle>mvn:org.ops4j.pax.web/pax-web-runtime/0.7.2</bundle>
-        <bundle>mvn:org.ops4j.pax.web/pax-web-jetty/0.7.2</bundle>
-    </feature>
-    <feature name="war" version="2.0.0">
-        <feature version="2.0.0">http</feature>
-        <bundle>mvn:org.ops4j.pax.web/pax-web-jsp/0.7.2</bundle>
-        <bundle>mvn:org.ops4j.pax.web/pax-web-extender-war/0.7.2</bundle>
-        <bundle>mvn:org.ops4j.pax.web/pax-web-extender-whiteboard/0.7.2</bundle>
-        <bundle>mvn:org.ops4j.pax.url/pax-url-war/1.1.3</bundle>
-        <bundle>mvn:org.apache.karaf.deployer/org.apache.karaf.deployer.war/2.0.0</bundle>
-    </feature>
-    <feature name="webconsole" version="2.0.0">
-        <feature version="2.0.0">http</feature>
-        <config name="org.apache.karaf.webconsole">
-            realm=karaf
-        </config>
-        <bundle>mvn:org.apache.felix/org.apache.felix.metatype/1.0.2</bundle>
-        <bundle>mvn:org.apache.karaf.webconsole/org.apache.karaf.webconsole.branding/2.0.0</bundle>
-        <bundle>mvn:org.apache.felix/org.apache.felix.webconsole/3.1.0</bundle>
-        <bundle>mvn:org.apache.karaf.webconsole/org.apache.karaf.webconsole.admin/2.0.0</bundle>
-        <bundle>mvn:org.apache.karaf.webconsole/org.apache.karaf.webconsole.features/2.0.0</bundle>
-        <bundle>mvn:org.apache.karaf.webconsole/org.apache.karaf.webconsole.gogo/2.0.0</bundle>
-    </feature>
-    <feature name="ssh" version="2.0.0">
-        <config name="org.apache.karaf.shell.ssh">
-            sshPort=8101
-            sshHost=0.0.0.0
-            sshRealm=karaf
-        </config>
-        <bundle>mvn:org.apache.mina/mina-core/2.0.0-RC1</bundle>
-        <bundle>mvn:org.apache.sshd/sshd-core/0.4.0</bundle>
-        <bundle>mvn:org.apache.karaf.shell/org.apache.karaf.shell.ssh/2.0.0</bundle>
-    </feature>
-    <feature name="management" version="2.0.0">
-        <bundle>mvn:org.apache.karaf/org.apache.karaf.management/2.0.0</bundle>
-        <bundle>mvn:org.apache.aries.jmx/org.apache.aries.jmx/0.1-r964701</bundle>
-        <bundle>mvn:org.apache.aries.jmx/org.apache.aries.jmx.blueprint/0.1-r964701</bundle>
-    </feature>
-</features>

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/test/resources/org/apache/karaf/features/internal/f02.xml
----------------------------------------------------------------------
diff --git a/features/core/src/test/resources/org/apache/karaf/features/internal/f02.xml b/features/core/src/test/resources/org/apache/karaf/features/internal/f02.xml
deleted file mode 100644
index 1578faa..0000000
--- a/features/core/src/test/resources/org/apache/karaf/features/internal/f02.xml
+++ /dev/null
@@ -1,164 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-      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.
--->
-<features name="karaf-2.2.0" xmlns="http://karaf.apache.org/xmlns/features/v1.0.0">
-    <feature name="spring" version="2.5.6.SEC02" resolver="(obr)">
-        <bundle dependency='true'>mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.aopalliance/1.0_6</bundle>
-        <bundle>mvn:org.springframework/spring-core/2.5.6.SEC02</bundle>
-        <bundle>mvn:org.springframework/spring-beans/2.5.6.SEC02</bundle>
-        <bundle>mvn:org.springframework/spring-aop/2.5.6.SEC02</bundle>
-        <bundle>mvn:org.springframework/spring-context/2.5.6.SEC02</bundle>
-        <bundle>mvn:org.springframework/spring-context-support/2.5.6.SEC02</bundle>
-    </feature>
-    <feature name="spring-web" version="2.5.6.SEC02" resolver="(obr)">
-    	<feature version="2.5.6.SEC02">spring</feature>
-    	<feature version="2.2.0">http</feature>
-    	<bundle>mvn:org.springframework/spring-web/2.5.6.SEC02</bundle>
-		<bundle>mvn:org.springframework/spring-webmvc/2.5.6.SEC02</bundle>
-    </feature>
-    <feature name="spring" version="3.0.5.RELEASE" resolver="(obr)">
-        <bundle dependency='true'>mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.aopalliance/1.0_6</bundle>
-        <bundle>mvn:org.springframework/spring-core/3.0.5.RELEASE</bundle>
-        <bundle>mvn:org.springframework/spring-asm/3.0.5.RELEASE</bundle>
-        <bundle>mvn:org.springframework/spring-expression/3.0.5.RELEASE</bundle>
-        <bundle>mvn:org.springframework/spring-beans/3.0.5.RELEASE</bundle>
-        <bundle>mvn:org.springframework/spring-aop/3.0.5.RELEASE</bundle>
-        <bundle>mvn:org.springframework/spring-context/3.0.5.RELEASE</bundle>
-        <bundle>mvn:org.springframework/spring-context-support/3.0.5.RELEASE</bundle>
-    </feature>
-    <feature name="spring-web" version="3.0.5.RELEASE" resolver="(obr)">
-    	<feature version="3.0.5.RELEASE">spring</feature>
-    	<feature version="2.2.0">http</feature>
-    	<bundle>mvn:org.springframework/spring-web/3.0.5.RELEASE</bundle>
-		<bundle>mvn:org.springframework/spring-webmvc/3.0.5.RELEASE</bundle>
-    </feature>
-    <feature name="spring-dm" version="1.2.1" resolver="(obr)">
-        <feature version="[2.5.6,4)">spring</feature>
-        <bundle dependency='true'>mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.cglib/2.2.2_1</bundle>
-        <bundle>mvn:org.springframework.osgi/spring-osgi-io/1.2.1</bundle>
-        <bundle>mvn:org.springframework.osgi/spring-osgi-core/1.2.1</bundle>
-        <bundle>mvn:org.springframework.osgi/spring-osgi-extender/1.2.1</bundle>
-        <bundle>mvn:org.springframework.osgi/spring-osgi-annotation/1.2.1</bundle>
-        <bundle>mvn:org.apache.karaf.deployer/org.apache.karaf.deployer.spring/2.2.0</bundle>
-    </feature>
-    <feature name="spring-dm-web" version="1.2.1" resolver="(obr)">
-    	<feature version="1.2.1">spring-dm</feature>
-    	<feature version="[2.5.6,4)">spring-web</feature>
-    	<feature version="2.2.0">http</feature>
-		<bundle>mvn:org.springframework.osgi/spring-osgi-web/1.2.1</bundle>
-    </feature>
-    <feature name="wrapper" version="2.2.0">
-        <bundle>mvn:org.apache.karaf.shell/org.apache.karaf.shell.wrapper/2.2.0</bundle>
-    </feature>
-    <feature name="obr" version="2.2.0">
-        <bundle>mvn:org.apache.felix/org.apache.felix.bundlerepository/1.6.4</bundle>
-        <bundle>mvn:org.apache.karaf.shell/org.apache.karaf.shell.obr/2.2.0</bundle>
-        <bundle>mvn:org.apache.karaf.features/org.apache.karaf.features.obr/2.2.0</bundle>
-    </feature>
-    <feature name="config" version="2.2.0">
-        <bundle start-level='30'>mvn:org.apache.karaf.shell/org.apache.karaf.shell.config/2.2.0</bundle>
-    </feature>
-	<feature name="jetty" version="7.2.2.v20101205" resolver="(obr)">
-		<bundle dependency='true'>mvn:org.apache.geronimo.specs/geronimo-servlet_2.5_spec/1.1.2</bundle>
-        <bundle dependency='true'>mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.asm/3.3_1</bundle>
-	    <bundle>mvn:org.eclipse.jetty/jetty-util/7.2.2.v20101205</bundle>
-	    <bundle>mvn:org.eclipse.jetty/jetty-io/7.2.2.v20101205</bundle>
-	    <bundle>mvn:org.eclipse.jetty/jetty-http/7.2.2.v20101205</bundle>
-	    <bundle>mvn:org.eclipse.jetty/jetty-continuation/7.2.2.v20101205</bundle>
-	    <bundle>mvn:org.eclipse.jetty/jetty-server/7.2.2.v20101205</bundle>
-	    <bundle>mvn:org.eclipse.jetty/jetty-security/7.2.2.v20101205</bundle>
-	    <bundle>mvn:org.eclipse.jetty/jetty-servlet/7.2.2.v20101205</bundle>
-    	<bundle>mvn:org.eclipse.jetty/jetty-xml/7.2.2.v20101205</bundle>
-	</feature>
-	<feature name="jetty-jaas" version="7.2.2.v20101205" resolver="(obr)">
-		<feature version="[7.0,8.0)">jetty</feature>
-		<bundle dependency='true'>mvn:javax.mail/mail/1.4.3</bundle>
-		<bundle dependency='true'>mvn:org.apache.geronimo.specs/geronimo-jta_1.1_spec/1.1.1</bundle>
-		<bundle>mvn:org.eclipse.jetty/jetty-webapp/7.2.2.v20101205</bundle>
-		<bundle>mvn:org.eclipse.jetty/jetty-jndi/7.2.2.v20101205</bundle>
-		<bundle>mvn:org.eclipse.jetty/jetty-plus/7.2.2.v20101205</bundle>
-	</feature>
-    <feature name="http" version="2.2.0" resolver="(obr)">
-    	<configfile finalname="/etc/jetty.xml">mvn:org.apache.karaf/apache-karaf/2.2.0/xml/jettyconfig</configfile>
-		<config name="org.ops4j.pax.web">
-            org.osgi.service.http.port=8181
-            javax.servlet.context.tempdir=${karaf.data}/pax-web-jsp
-            org.ops4j.pax.web.config.file=${karaf.etc}/jetty.xml
-        </config>
-        <feature version="[7.0,8.0)">jetty</feature>
-    	<bundle>mvn:org.ops4j.pax.web/pax-web-api/1.0.1</bundle>
-        <bundle>mvn:org.ops4j.pax.web/pax-web-spi/1.0.1</bundle>
-        <bundle>mvn:org.ops4j.pax.web/pax-web-runtime/1.0.1</bundle>
-        <bundle>mvn:org.ops4j.pax.web/pax-web-jetty/1.0.1</bundle>
-    </feature>
-    <feature name="war" version="2.2.0" resolver="(obr)">
-        <config name="org.ops4j.pax.url.war">
-            org.ops4j.pax.url.war.importPaxLoggingPackages=true
-        </config>
-        <feature>http</feature>
-        <bundle start-level='30'>mvn:org.apache.karaf.shell/org.apache.karaf.shell.web/2.2.0</bundle>
-        <bundle>mvn:org.ops4j.pax.web/pax-web-jsp/1.0.1</bundle>
-        <bundle>mvn:org.ops4j.pax.web/pax-web-extender-war/1.0.1</bundle>
-        <bundle>mvn:org.ops4j.pax.web/pax-web-extender-whiteboard/1.0.1</bundle>
-        <bundle>mvn:org.ops4j.pax.web/pax-web-deployer/1.0.1</bundle>
-        <bundle>mvn:org.ops4j.pax.url/pax-url-war/1.2.5</bundle>
-    </feature>
-    <feature name="kar" version="2.2.0">
-        <bundle>mvn:org.apache.karaf.deployer/org.apache.karaf.deployer.kar/2.2.0</bundle>
-    </feature>
-    <feature name="webconsole-base" version="2.2.0">
-        <config name="org.apache.karaf.webconsole">
-            realm=karaf
-        </config>
-        <feature>http</feature>
-        <bundle>mvn:org.apache.felix/org.apache.felix.metatype/1.0.4</bundle>
-        <bundle>mvn:org.apache.karaf.webconsole/org.apache.karaf.webconsole.branding/2.2.0</bundle>
-        <bundle>mvn:org.apache.karaf.webconsole/org.apache.karaf.webconsole.console/2.2.0</bundle>
-    </feature>
-    <feature name="webconsole" version="2.2.0">
-        <feature version="2.2.0">webconsole-base</feature>
-        <bundle>mvn:org.apache.karaf.webconsole/org.apache.karaf.webconsole.admin/2.2.0</bundle>
-        <bundle>mvn:org.apache.karaf.webconsole/org.apache.karaf.webconsole.features/2.2.0</bundle>
-        <bundle>mvn:org.apache.karaf.webconsole/org.apache.karaf.webconsole.gogo/2.2.0</bundle>
-		<bundle>mvn:org.apache.felix/org.apache.felix.webconsole.plugins.event/1.0.2</bundle>
-    </feature>
-    <feature name="ssh" version="2.2.0">
-        <config name="org.apache.karaf.shell">
-            sshPort=8101
-            sshHost=0.0.0.0
-            sshRealm=karaf
-            hostKey=${karaf.etc}/host.key
-        </config>
-        <bundle dependency='true'>mvn:org.apache.mina/mina-core/2.0.1</bundle>
-        <bundle dependency='true'>mvn:org.apache.sshd/sshd-core/0.5.0</bundle>
-        <bundle>mvn:org.apache.karaf.shell/org.apache.karaf.shell.ssh/2.2.0</bundle>
-    </feature>
-    <feature name="management" version="2.2.0">
-        <bundle>mvn:org.apache.karaf/org.apache.karaf.management/2.2.0</bundle>
-        <bundle>mvn:org.apache.aries.jmx/org.apache.aries.jmx/0.3</bundle>
-        <bundle>mvn:org.apache.aries.jmx/org.apache.aries.jmx.blueprint/0.3</bundle>
-    </feature>
-    <feature name="eventadmin" version="2.2.0">
-		<bundle start-level='30'>mvn:org.apache.felix/org.apache.felix.eventadmin/1.2.8</bundle>
-    </feature>
-    <feature name="jasypt-encryption" version="2.2.0" resolver="(obr)">
-        <bundle dependency='true'>mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.commons-codec/1.3_3</bundle>
-        <bundle dependency='true'>mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.commons-lang/2.4_4</bundle>
-        <bundle dependency='true'>mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.jasypt/1.7_1</bundle>
-        <bundle>mvn:org.apache.karaf.jaas/org.apache.karaf.jaas.jasypt/2.2.0</bundle>
-    </feature>
-</features>

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/test/resources/org/apache/karaf/features/internal/f03.xml
----------------------------------------------------------------------
diff --git a/features/core/src/test/resources/org/apache/karaf/features/internal/f03.xml b/features/core/src/test/resources/org/apache/karaf/features/internal/f03.xml
deleted file mode 100644
index c058095..0000000
--- a/features/core/src/test/resources/org/apache/karaf/features/internal/f03.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-    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.
--->
-<features xmlns="http://karaf.apache.org/xmlns/features/v1.0.0">
-    <feature name="spring" version="2.5.6.SEC02" resolver="(obr)">
-        <bundle dependency='true'>mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.aopalliance/1.0_6</bundle>
-        <bundle>mvn:org.springframework/spring-core/2.5.6.SEC02</bundle>
-        <bundle>mvn:org.springframework/spring-beans/2.5.6.SEC02</bundle>
-        <bundle>mvn:org.springframework/spring-aop/2.5.6.SEC02</bundle>
-        <bundle>mvn:org.springframework/spring-context/2.5.6.SEC02</bundle>
-        <bundle>mvn:org.springframework/spring-context-support/2.5.6.SEC02</bundle>
-    </feature>
-</features>

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/test/resources/org/apache/karaf/features/internal/f04.xml
----------------------------------------------------------------------
diff --git a/features/core/src/test/resources/org/apache/karaf/features/internal/f04.xml b/features/core/src/test/resources/org/apache/karaf/features/internal/f04.xml
deleted file mode 100644
index 85f28ad..0000000
--- a/features/core/src/test/resources/org/apache/karaf/features/internal/f04.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-    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.
--->
-<features name="karaf" xmlns="http://karaf.apache.org/xmlns/features/v1.1.0">
-    <feature name="spring" version="2.5.6.SEC02" resolver="(obr)">
-        <bundle dependency='true'>mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.aopalliance/1.0_6</bundle>
-        <bundle>mvn:org.springframework/spring-core/2.5.6.SEC02</bundle>
-        <bundle>mvn:org.springframework/spring-beans/2.5.6.SEC02</bundle>
-        <bundle>mvn:org.springframework/spring-aop/2.5.6.SEC02</bundle>
-        <bundle>mvn:org.springframework/spring-context/2.5.6.SEC02</bundle>
-        <bundle>mvn:org.springframework/spring-context-support/2.5.6.SEC02</bundle>
-    </feature>
-</features>
-

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/test/resources/org/apache/karaf/features/internal/f05.xml
----------------------------------------------------------------------
diff --git a/features/core/src/test/resources/org/apache/karaf/features/internal/f05.xml b/features/core/src/test/resources/org/apache/karaf/features/internal/f05.xml
deleted file mode 100644
index 15d84e2..0000000
--- a/features/core/src/test/resources/org/apache/karaf/features/internal/f05.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-    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.
--->
-<features xmlns="http://karaf.apache.org/xmlns/features/v1.1.0">
-    <feature name="spring" version="2.5.6.SEC02" resolver="(obr)">
-        <bundle dependency='true'>mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.aopalliance/1.0_6</bundle>
-        <bundle>mvn:org.springframework/spring-core/2.5.6.SEC02</bundle>
-        <bundle>mvn:org.springframework/spring-beans/2.5.6.SEC02</bundle>
-        <bundle>mvn:org.springframework/spring-aop/2.5.6.SEC02</bundle>
-        <bundle>mvn:org.springframework/spring-context/2.5.6.SEC02</bundle>
-        <bundle>mvn:org.springframework/spring-context-support/2.5.6.SEC02</bundle>
-    </feature>
-</features>
-

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/test/resources/org/apache/karaf/features/internal/f06.xml
----------------------------------------------------------------------
diff --git a/features/core/src/test/resources/org/apache/karaf/features/internal/f06.xml b/features/core/src/test/resources/org/apache/karaf/features/internal/f06.xml
deleted file mode 100644
index 496cbdb..0000000
--- a/features/core/src/test/resources/org/apache/karaf/features/internal/f06.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-    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.
--->
-<features name="karaf" xmlns="http://karaf.apache.org/xmlns/features/v1.2.0">
-    <feature name="spring" version="2.5.6.SEC02" resolver="(obr)">
-        <bundle dependency='true'>mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.aopalliance/1.0_4</bundle>
-        <bundle>mvn:org.springframework/spring-core/2.5.6.SEC02</bundle>
-        <bundle>mvn:org.springframework/spring-beans/2.5.6.SEC02</bundle>
-        <bundle>mvn:org.springframework/spring-aop/2.5.6.SEC02</bundle>
-        <bundle>mvn:org.springframework/spring-context/2.5.6.SEC02</bundle>
-        <bundle>mvn:org.springframework/spring-context-support/2.5.6.SEC02</bundle>
-        <conditional>
-            <condition>http</condition>
-            <bundle>mvn:org.springframework/spring-web/2.5.6.SEC02</bundle>
-        </conditional>
-    </feature>
-</features>
-

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/test/resources/org/apache/karaf/features/internal/overrides.properties
----------------------------------------------------------------------
diff --git a/features/core/src/test/resources/org/apache/karaf/features/internal/overrides.properties b/features/core/src/test/resources/org/apache/karaf/features/internal/overrides.properties
deleted file mode 100644
index d34fa7e..0000000
--- a/features/core/src/test/resources/org/apache/karaf/features/internal/overrides.properties
+++ /dev/null
@@ -1,23 +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.
-#
-################################################################################
-
-# Sample etc/overrides.properties file for testing purposes
-mvn:org.apache.karaf.admin/org.apache.karaf.admin.command/2.3.0.redhat-61033X
-mvn:org.apache.karaf.admin/org.apache.karaf.admin.core/2.3.0.redhat-61033X;range=[2.3.0,2.5)

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/test/resources/org/apache/karaf/features/internal/repo2.xml
----------------------------------------------------------------------
diff --git a/features/core/src/test/resources/org/apache/karaf/features/internal/repo2.xml b/features/core/src/test/resources/org/apache/karaf/features/internal/repo2.xml
deleted file mode 100644
index 5fd51d0..0000000
--- a/features/core/src/test/resources/org/apache/karaf/features/internal/repo2.xml
+++ /dev/null
@@ -1,41 +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.
--->
-<features name="repo2">
-    <feature name="common">
-        <bundle>b1</bundle>
-    </feature>
-    <feature name="f1">
-        <feature>common</feature>
-        <bundle>b2</bundle>
-    </feature>
-    <feature name="f2">
-        <feature>common</feature>
-        <feature>f1</feature>
-        <bundle>b3</bundle>
-    </feature>
-    <feature name="f3">
-        <feature>f1</feature>
-        <feature>f2</feature>
-    	<bundle>b4</bundle>
-    </feature>
-    <feature name="all">
-        <feature>f1</feature>
-        <feature>f2</feature>
-        <feature>f3</feature>
-    </feature>
-</features>

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/test/resources/org/apache/karaf/features/internal/service/f01.xml
----------------------------------------------------------------------
diff --git a/features/core/src/test/resources/org/apache/karaf/features/internal/service/f01.xml b/features/core/src/test/resources/org/apache/karaf/features/internal/service/f01.xml
new file mode 100644
index 0000000..814c722
--- /dev/null
+++ b/features/core/src/test/resources/org/apache/karaf/features/internal/service/f01.xml
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+      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.
+-->
+<features name="karaf-2.0.0">
+    <feature name="spring" version="3.0.3.RELEASE">
+        <bundle>mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.aopalliance/1.0_6</bundle>
+        <bundle>mvn:org.springframework/spring-core/3.0.3.RELEASE</bundle>
+        <bundle>mvn:org.springframework/spring-asm/3.0.3.RELEASE</bundle>
+        <bundle>mvn:org.springframework/spring-expression/3.0.3.RELEASE</bundle>
+        <bundle>mvn:org.springframework/spring-beans/3.0.3.RELEASE</bundle>
+        <bundle>mvn:org.springframework/spring-aop/3.0.3.RELEASE</bundle>
+        <bundle>mvn:org.springframework/spring-context/3.0.3.RELEASE</bundle>
+        <bundle>mvn:org.springframework/spring-context-support/3.0.3.RELEASE</bundle>
+    </feature>
+    <feature name="spring-dm" version="1.2.0">
+        <feature version="3.0.3.RELEASE">spring</feature>
+        <bundle>mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.cglib/2.2.2_1</bundle>
+        <bundle>mvn:org.springframework.osgi/spring-osgi-io/1.2.0</bundle>
+        <bundle>mvn:org.springframework.osgi/spring-osgi-core/1.2.0</bundle>
+        <bundle>mvn:org.springframework.osgi/spring-osgi-extender/1.2.0</bundle>
+        <bundle>mvn:org.springframework.osgi/spring-osgi-annotation/1.2.0</bundle>
+        <bundle>mvn:org.apache.karaf.deployer/org.apache.karaf.deployer.spring/2.0.0</bundle>
+    </feature>
+    <feature name="wrapper" version="2.0.0">
+        <bundle>mvn:org.apache.karaf.shell/org.apache.karaf.shell.wrapper/2.0.0</bundle>
+    </feature>
+    <feature name="obr" version="2.0.0">
+        <bundle>mvn:org.apache.felix/org.apache.felix.bundlerepository/1.6.4</bundle>
+        <bundle>mvn:org.apache.karaf.shell/org.apache.karaf.shell.obr/2.0.0</bundle>
+        <bundle>mvn:org.apache.karaf.features/org.apache.karaf.features.obr/2.0.0</bundle>
+    </feature>
+    <feature name="http" version="2.0.0">
+        <config name="org.ops4j.pax.web">
+            org.osgi.service.http.port=8181
+        </config>
+        <bundle>mvn:org.apache.geronimo.specs/geronimo-servlet_2.5_spec/1.1.2</bundle>
+        <bundle>mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.jetty-bundle/6.1.22_1</bundle>
+        <bundle>mvn:org.ops4j.pax.web/pax-web-api/0.7.2</bundle>
+        <bundle>mvn:org.ops4j.pax.web/pax-web-spi/0.7.2</bundle>
+        <bundle>mvn:org.ops4j.pax.web/pax-web-runtime/0.7.2</bundle>
+        <bundle>mvn:org.ops4j.pax.web/pax-web-jetty/0.7.2</bundle>
+    </feature>
+    <feature name="war" version="2.0.0">
+        <feature version="2.0.0">http</feature>
+        <bundle>mvn:org.ops4j.pax.web/pax-web-jsp/0.7.2</bundle>
+        <bundle>mvn:org.ops4j.pax.web/pax-web-extender-war/0.7.2</bundle>
+        <bundle>mvn:org.ops4j.pax.web/pax-web-extender-whiteboard/0.7.2</bundle>
+        <bundle>mvn:org.ops4j.pax.url/pax-url-war/1.1.3</bundle>
+        <bundle>mvn:org.apache.karaf.deployer/org.apache.karaf.deployer.war/2.0.0</bundle>
+    </feature>
+    <feature name="webconsole" version="2.0.0">
+        <feature version="2.0.0">http</feature>
+        <config name="org.apache.karaf.webconsole">
+            realm=karaf
+        </config>
+        <bundle>mvn:org.apache.felix/org.apache.felix.metatype/1.0.2</bundle>
+        <bundle>mvn:org.apache.karaf.webconsole/org.apache.karaf.webconsole.branding/2.0.0</bundle>
+        <bundle>mvn:org.apache.felix/org.apache.felix.webconsole/3.1.0</bundle>
+        <bundle>mvn:org.apache.karaf.webconsole/org.apache.karaf.webconsole.admin/2.0.0</bundle>
+        <bundle>mvn:org.apache.karaf.webconsole/org.apache.karaf.webconsole.features/2.0.0</bundle>
+        <bundle>mvn:org.apache.karaf.webconsole/org.apache.karaf.webconsole.gogo/2.0.0</bundle>
+    </feature>
+    <feature name="ssh" version="2.0.0">
+        <config name="org.apache.karaf.shell.ssh">
+            sshPort=8101
+            sshHost=0.0.0.0
+            sshRealm=karaf
+        </config>
+        <bundle>mvn:org.apache.mina/mina-core/2.0.0-RC1</bundle>
+        <bundle>mvn:org.apache.sshd/sshd-core/0.4.0</bundle>
+        <bundle>mvn:org.apache.karaf.shell/org.apache.karaf.shell.ssh/2.0.0</bundle>
+    </feature>
+    <feature name="management" version="2.0.0">
+        <bundle>mvn:org.apache.karaf/org.apache.karaf.management/2.0.0</bundle>
+        <bundle>mvn:org.apache.aries.jmx/org.apache.aries.jmx/0.1-r964701</bundle>
+        <bundle>mvn:org.apache.aries.jmx/org.apache.aries.jmx.blueprint/0.1-r964701</bundle>
+    </feature>
+</features>

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/test/resources/org/apache/karaf/features/internal/service/f02.xml
----------------------------------------------------------------------
diff --git a/features/core/src/test/resources/org/apache/karaf/features/internal/service/f02.xml b/features/core/src/test/resources/org/apache/karaf/features/internal/service/f02.xml
new file mode 100644
index 0000000..1578faa
--- /dev/null
+++ b/features/core/src/test/resources/org/apache/karaf/features/internal/service/f02.xml
@@ -0,0 +1,164 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+      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.
+-->
+<features name="karaf-2.2.0" xmlns="http://karaf.apache.org/xmlns/features/v1.0.0">
+    <feature name="spring" version="2.5.6.SEC02" resolver="(obr)">
+        <bundle dependency='true'>mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.aopalliance/1.0_6</bundle>
+        <bundle>mvn:org.springframework/spring-core/2.5.6.SEC02</bundle>
+        <bundle>mvn:org.springframework/spring-beans/2.5.6.SEC02</bundle>
+        <bundle>mvn:org.springframework/spring-aop/2.5.6.SEC02</bundle>
+        <bundle>mvn:org.springframework/spring-context/2.5.6.SEC02</bundle>
+        <bundle>mvn:org.springframework/spring-context-support/2.5.6.SEC02</bundle>
+    </feature>
+    <feature name="spring-web" version="2.5.6.SEC02" resolver="(obr)">
+    	<feature version="2.5.6.SEC02">spring</feature>
+    	<feature version="2.2.0">http</feature>
+    	<bundle>mvn:org.springframework/spring-web/2.5.6.SEC02</bundle>
+		<bundle>mvn:org.springframework/spring-webmvc/2.5.6.SEC02</bundle>
+    </feature>
+    <feature name="spring" version="3.0.5.RELEASE" resolver="(obr)">
+        <bundle dependency='true'>mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.aopalliance/1.0_6</bundle>
+        <bundle>mvn:org.springframework/spring-core/3.0.5.RELEASE</bundle>
+        <bundle>mvn:org.springframework/spring-asm/3.0.5.RELEASE</bundle>
+        <bundle>mvn:org.springframework/spring-expression/3.0.5.RELEASE</bundle>
+        <bundle>mvn:org.springframework/spring-beans/3.0.5.RELEASE</bundle>
+        <bundle>mvn:org.springframework/spring-aop/3.0.5.RELEASE</bundle>
+        <bundle>mvn:org.springframework/spring-context/3.0.5.RELEASE</bundle>
+        <bundle>mvn:org.springframework/spring-context-support/3.0.5.RELEASE</bundle>
+    </feature>
+    <feature name="spring-web" version="3.0.5.RELEASE" resolver="(obr)">
+    	<feature version="3.0.5.RELEASE">spring</feature>
+    	<feature version="2.2.0">http</feature>
+    	<bundle>mvn:org.springframework/spring-web/3.0.5.RELEASE</bundle>
+		<bundle>mvn:org.springframework/spring-webmvc/3.0.5.RELEASE</bundle>
+    </feature>
+    <feature name="spring-dm" version="1.2.1" resolver="(obr)">
+        <feature version="[2.5.6,4)">spring</feature>
+        <bundle dependency='true'>mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.cglib/2.2.2_1</bundle>
+        <bundle>mvn:org.springframework.osgi/spring-osgi-io/1.2.1</bundle>
+        <bundle>mvn:org.springframework.osgi/spring-osgi-core/1.2.1</bundle>
+        <bundle>mvn:org.springframework.osgi/spring-osgi-extender/1.2.1</bundle>
+        <bundle>mvn:org.springframework.osgi/spring-osgi-annotation/1.2.1</bundle>
+        <bundle>mvn:org.apache.karaf.deployer/org.apache.karaf.deployer.spring/2.2.0</bundle>
+    </feature>
+    <feature name="spring-dm-web" version="1.2.1" resolver="(obr)">
+    	<feature version="1.2.1">spring-dm</feature>
+    	<feature version="[2.5.6,4)">spring-web</feature>
+    	<feature version="2.2.0">http</feature>
+		<bundle>mvn:org.springframework.osgi/spring-osgi-web/1.2.1</bundle>
+    </feature>
+    <feature name="wrapper" version="2.2.0">
+        <bundle>mvn:org.apache.karaf.shell/org.apache.karaf.shell.wrapper/2.2.0</bundle>
+    </feature>
+    <feature name="obr" version="2.2.0">
+        <bundle>mvn:org.apache.felix/org.apache.felix.bundlerepository/1.6.4</bundle>
+        <bundle>mvn:org.apache.karaf.shell/org.apache.karaf.shell.obr/2.2.0</bundle>
+        <bundle>mvn:org.apache.karaf.features/org.apache.karaf.features.obr/2.2.0</bundle>
+    </feature>
+    <feature name="config" version="2.2.0">
+        <bundle start-level='30'>mvn:org.apache.karaf.shell/org.apache.karaf.shell.config/2.2.0</bundle>
+    </feature>
+	<feature name="jetty" version="7.2.2.v20101205" resolver="(obr)">
+		<bundle dependency='true'>mvn:org.apache.geronimo.specs/geronimo-servlet_2.5_spec/1.1.2</bundle>
+        <bundle dependency='true'>mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.asm/3.3_1</bundle>
+	    <bundle>mvn:org.eclipse.jetty/jetty-util/7.2.2.v20101205</bundle>
+	    <bundle>mvn:org.eclipse.jetty/jetty-io/7.2.2.v20101205</bundle>
+	    <bundle>mvn:org.eclipse.jetty/jetty-http/7.2.2.v20101205</bundle>
+	    <bundle>mvn:org.eclipse.jetty/jetty-continuation/7.2.2.v20101205</bundle>
+	    <bundle>mvn:org.eclipse.jetty/jetty-server/7.2.2.v20101205</bundle>
+	    <bundle>mvn:org.eclipse.jetty/jetty-security/7.2.2.v20101205</bundle>
+	    <bundle>mvn:org.eclipse.jetty/jetty-servlet/7.2.2.v20101205</bundle>
+    	<bundle>mvn:org.eclipse.jetty/jetty-xml/7.2.2.v20101205</bundle>
+	</feature>
+	<feature name="jetty-jaas" version="7.2.2.v20101205" resolver="(obr)">
+		<feature version="[7.0,8.0)">jetty</feature>
+		<bundle dependency='true'>mvn:javax.mail/mail/1.4.3</bundle>
+		<bundle dependency='true'>mvn:org.apache.geronimo.specs/geronimo-jta_1.1_spec/1.1.1</bundle>
+		<bundle>mvn:org.eclipse.jetty/jetty-webapp/7.2.2.v20101205</bundle>
+		<bundle>mvn:org.eclipse.jetty/jetty-jndi/7.2.2.v20101205</bundle>
+		<bundle>mvn:org.eclipse.jetty/jetty-plus/7.2.2.v20101205</bundle>
+	</feature>
+    <feature name="http" version="2.2.0" resolver="(obr)">
+    	<configfile finalname="/etc/jetty.xml">mvn:org.apache.karaf/apache-karaf/2.2.0/xml/jettyconfig</configfile>
+		<config name="org.ops4j.pax.web">
+            org.osgi.service.http.port=8181
+            javax.servlet.context.tempdir=${karaf.data}/pax-web-jsp
+            org.ops4j.pax.web.config.file=${karaf.etc}/jetty.xml
+        </config>
+        <feature version="[7.0,8.0)">jetty</feature>
+    	<bundle>mvn:org.ops4j.pax.web/pax-web-api/1.0.1</bundle>
+        <bundle>mvn:org.ops4j.pax.web/pax-web-spi/1.0.1</bundle>
+        <bundle>mvn:org.ops4j.pax.web/pax-web-runtime/1.0.1</bundle>
+        <bundle>mvn:org.ops4j.pax.web/pax-web-jetty/1.0.1</bundle>
+    </feature>
+    <feature name="war" version="2.2.0" resolver="(obr)">
+        <config name="org.ops4j.pax.url.war">
+            org.ops4j.pax.url.war.importPaxLoggingPackages=true
+        </config>
+        <feature>http</feature>
+        <bundle start-level='30'>mvn:org.apache.karaf.shell/org.apache.karaf.shell.web/2.2.0</bundle>
+        <bundle>mvn:org.ops4j.pax.web/pax-web-jsp/1.0.1</bundle>
+        <bundle>mvn:org.ops4j.pax.web/pax-web-extender-war/1.0.1</bundle>
+        <bundle>mvn:org.ops4j.pax.web/pax-web-extender-whiteboard/1.0.1</bundle>
+        <bundle>mvn:org.ops4j.pax.web/pax-web-deployer/1.0.1</bundle>
+        <bundle>mvn:org.ops4j.pax.url/pax-url-war/1.2.5</bundle>
+    </feature>
+    <feature name="kar" version="2.2.0">
+        <bundle>mvn:org.apache.karaf.deployer/org.apache.karaf.deployer.kar/2.2.0</bundle>
+    </feature>
+    <feature name="webconsole-base" version="2.2.0">
+        <config name="org.apache.karaf.webconsole">
+            realm=karaf
+        </config>
+        <feature>http</feature>
+        <bundle>mvn:org.apache.felix/org.apache.felix.metatype/1.0.4</bundle>
+        <bundle>mvn:org.apache.karaf.webconsole/org.apache.karaf.webconsole.branding/2.2.0</bundle>
+        <bundle>mvn:org.apache.karaf.webconsole/org.apache.karaf.webconsole.console/2.2.0</bundle>
+    </feature>
+    <feature name="webconsole" version="2.2.0">
+        <feature version="2.2.0">webconsole-base</feature>
+        <bundle>mvn:org.apache.karaf.webconsole/org.apache.karaf.webconsole.admin/2.2.0</bundle>
+        <bundle>mvn:org.apache.karaf.webconsole/org.apache.karaf.webconsole.features/2.2.0</bundle>
+        <bundle>mvn:org.apache.karaf.webconsole/org.apache.karaf.webconsole.gogo/2.2.0</bundle>
+		<bundle>mvn:org.apache.felix/org.apache.felix.webconsole.plugins.event/1.0.2</bundle>
+    </feature>
+    <feature name="ssh" version="2.2.0">
+        <config name="org.apache.karaf.shell">
+            sshPort=8101
+            sshHost=0.0.0.0
+            sshRealm=karaf
+            hostKey=${karaf.etc}/host.key
+        </config>
+        <bundle dependency='true'>mvn:org.apache.mina/mina-core/2.0.1</bundle>
+        <bundle dependency='true'>mvn:org.apache.sshd/sshd-core/0.5.0</bundle>
+        <bundle>mvn:org.apache.karaf.shell/org.apache.karaf.shell.ssh/2.2.0</bundle>
+    </feature>
+    <feature name="management" version="2.2.0">
+        <bundle>mvn:org.apache.karaf/org.apache.karaf.management/2.2.0</bundle>
+        <bundle>mvn:org.apache.aries.jmx/org.apache.aries.jmx/0.3</bundle>
+        <bundle>mvn:org.apache.aries.jmx/org.apache.aries.jmx.blueprint/0.3</bundle>
+    </feature>
+    <feature name="eventadmin" version="2.2.0">
+		<bundle start-level='30'>mvn:org.apache.felix/org.apache.felix.eventadmin/1.2.8</bundle>
+    </feature>
+    <feature name="jasypt-encryption" version="2.2.0" resolver="(obr)">
+        <bundle dependency='true'>mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.commons-codec/1.3_3</bundle>
+        <bundle dependency='true'>mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.commons-lang/2.4_4</bundle>
+        <bundle dependency='true'>mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.jasypt/1.7_1</bundle>
+        <bundle>mvn:org.apache.karaf.jaas/org.apache.karaf.jaas.jasypt/2.2.0</bundle>
+    </feature>
+</features>

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/test/resources/org/apache/karaf/features/internal/service/f03.xml
----------------------------------------------------------------------
diff --git a/features/core/src/test/resources/org/apache/karaf/features/internal/service/f03.xml b/features/core/src/test/resources/org/apache/karaf/features/internal/service/f03.xml
new file mode 100644
index 0000000..c058095
--- /dev/null
+++ b/features/core/src/test/resources/org/apache/karaf/features/internal/service/f03.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+<features xmlns="http://karaf.apache.org/xmlns/features/v1.0.0">
+    <feature name="spring" version="2.5.6.SEC02" resolver="(obr)">
+        <bundle dependency='true'>mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.aopalliance/1.0_6</bundle>
+        <bundle>mvn:org.springframework/spring-core/2.5.6.SEC02</bundle>
+        <bundle>mvn:org.springframework/spring-beans/2.5.6.SEC02</bundle>
+        <bundle>mvn:org.springframework/spring-aop/2.5.6.SEC02</bundle>
+        <bundle>mvn:org.springframework/spring-context/2.5.6.SEC02</bundle>
+        <bundle>mvn:org.springframework/spring-context-support/2.5.6.SEC02</bundle>
+    </feature>
+</features>

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/test/resources/org/apache/karaf/features/internal/service/f04.xml
----------------------------------------------------------------------
diff --git a/features/core/src/test/resources/org/apache/karaf/features/internal/service/f04.xml b/features/core/src/test/resources/org/apache/karaf/features/internal/service/f04.xml
new file mode 100644
index 0000000..85f28ad
--- /dev/null
+++ b/features/core/src/test/resources/org/apache/karaf/features/internal/service/f04.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+<features name="karaf" xmlns="http://karaf.apache.org/xmlns/features/v1.1.0">
+    <feature name="spring" version="2.5.6.SEC02" resolver="(obr)">
+        <bundle dependency='true'>mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.aopalliance/1.0_6</bundle>
+        <bundle>mvn:org.springframework/spring-core/2.5.6.SEC02</bundle>
+        <bundle>mvn:org.springframework/spring-beans/2.5.6.SEC02</bundle>
+        <bundle>mvn:org.springframework/spring-aop/2.5.6.SEC02</bundle>
+        <bundle>mvn:org.springframework/spring-context/2.5.6.SEC02</bundle>
+        <bundle>mvn:org.springframework/spring-context-support/2.5.6.SEC02</bundle>
+    </feature>
+</features>
+

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/test/resources/org/apache/karaf/features/internal/service/f05.xml
----------------------------------------------------------------------
diff --git a/features/core/src/test/resources/org/apache/karaf/features/internal/service/f05.xml b/features/core/src/test/resources/org/apache/karaf/features/internal/service/f05.xml
new file mode 100644
index 0000000..15d84e2
--- /dev/null
+++ b/features/core/src/test/resources/org/apache/karaf/features/internal/service/f05.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+<features xmlns="http://karaf.apache.org/xmlns/features/v1.1.0">
+    <feature name="spring" version="2.5.6.SEC02" resolver="(obr)">
+        <bundle dependency='true'>mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.aopalliance/1.0_6</bundle>
+        <bundle>mvn:org.springframework/spring-core/2.5.6.SEC02</bundle>
+        <bundle>mvn:org.springframework/spring-beans/2.5.6.SEC02</bundle>
+        <bundle>mvn:org.springframework/spring-aop/2.5.6.SEC02</bundle>
+        <bundle>mvn:org.springframework/spring-context/2.5.6.SEC02</bundle>
+        <bundle>mvn:org.springframework/spring-context-support/2.5.6.SEC02</bundle>
+    </feature>
+</features>
+

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/test/resources/org/apache/karaf/features/internal/service/f06.xml
----------------------------------------------------------------------
diff --git a/features/core/src/test/resources/org/apache/karaf/features/internal/service/f06.xml b/features/core/src/test/resources/org/apache/karaf/features/internal/service/f06.xml
new file mode 100644
index 0000000..496cbdb
--- /dev/null
+++ b/features/core/src/test/resources/org/apache/karaf/features/internal/service/f06.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+<features name="karaf" xmlns="http://karaf.apache.org/xmlns/features/v1.2.0">
+    <feature name="spring" version="2.5.6.SEC02" resolver="(obr)">
+        <bundle dependency='true'>mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.aopalliance/1.0_4</bundle>
+        <bundle>mvn:org.springframework/spring-core/2.5.6.SEC02</bundle>
+        <bundle>mvn:org.springframework/spring-beans/2.5.6.SEC02</bundle>
+        <bundle>mvn:org.springframework/spring-aop/2.5.6.SEC02</bundle>
+        <bundle>mvn:org.springframework/spring-context/2.5.6.SEC02</bundle>
+        <bundle>mvn:org.springframework/spring-context-support/2.5.6.SEC02</bundle>
+        <conditional>
+            <condition>http</condition>
+            <bundle>mvn:org.springframework/spring-web/2.5.6.SEC02</bundle>
+        </conditional>
+    </feature>
+</features>
+

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/test/resources/org/apache/karaf/features/internal/service/f07.xml
----------------------------------------------------------------------
diff --git a/features/core/src/test/resources/org/apache/karaf/features/internal/service/f07.xml b/features/core/src/test/resources/org/apache/karaf/features/internal/service/f07.xml
new file mode 100644
index 0000000..5b7dd90
--- /dev/null
+++ b/features/core/src/test/resources/org/apache/karaf/features/internal/service/f07.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+<features name="karaf" xmlns="http://karaf.apache.org/xmlns/features/v1.3.0">
+    <feature name="spring" version="2.5.6.SEC02" resolver="(obr)">
+        <bundle dependency='true'>mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.aopalliance/1.0_4</bundle>
+        <bundle>mvn:org.springframework/spring-core/2.5.6.SEC02</bundle>
+        <bundle>mvn:org.springframework/spring-beans/2.5.6.SEC02</bundle>
+        <bundle>mvn:org.springframework/spring-aop/2.5.6.SEC02</bundle>
+        <bundle>mvn:org.springframework/spring-context/2.5.6.SEC02</bundle>
+        <bundle>mvn:org.springframework/spring-context-support/2.5.6.SEC02</bundle>
+        <conditional>
+            <condition>http</condition>
+            <bundle>mvn:org.springframework/spring-web/2.5.6.SEC02</bundle>
+        </conditional>
+        <capability>
+            service-reference;effective:=active;objectClass=org.apache.aries.proxy.ProxyManager
+        </capability>
+    </feature>
+</features>
+

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/test/resources/org/apache/karaf/features/internal/service/overrides.properties
----------------------------------------------------------------------
diff --git a/features/core/src/test/resources/org/apache/karaf/features/internal/service/overrides.properties b/features/core/src/test/resources/org/apache/karaf/features/internal/service/overrides.properties
new file mode 100644
index 0000000..d34fa7e
--- /dev/null
+++ b/features/core/src/test/resources/org/apache/karaf/features/internal/service/overrides.properties
@@ -0,0 +1,23 @@
+
+################################################################################
+#
+#    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.
+#
+################################################################################
+
+# Sample etc/overrides.properties file for testing purposes
+mvn:org.apache.karaf.admin/org.apache.karaf.admin.command/2.3.0.redhat-61033X
+mvn:org.apache.karaf.admin/org.apache.karaf.admin.core/2.3.0.redhat-61033X;range=[2.3.0,2.5)

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/test/resources/org/apache/karaf/features/internal/service/repo2.xml
----------------------------------------------------------------------
diff --git a/features/core/src/test/resources/org/apache/karaf/features/internal/service/repo2.xml b/features/core/src/test/resources/org/apache/karaf/features/internal/service/repo2.xml
new file mode 100644
index 0000000..5fd51d0
--- /dev/null
+++ b/features/core/src/test/resources/org/apache/karaf/features/internal/service/repo2.xml
@@ -0,0 +1,41 @@
+<!--
+
+    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.
+-->
+<features name="repo2">
+    <feature name="common">
+        <bundle>b1</bundle>
+    </feature>
+    <feature name="f1">
+        <feature>common</feature>
+        <bundle>b2</bundle>
+    </feature>
+    <feature name="f2">
+        <feature>common</feature>
+        <feature>f1</feature>
+        <bundle>b3</bundle>
+    </feature>
+    <feature name="f3">
+        <feature>f1</feature>
+        <feature>f2</feature>
+    	<bundle>b4</bundle>
+    </feature>
+    <feature name="all">
+        <feature>f1</feature>
+        <feature>f2</feature>
+        <feature>f3</feature>
+    </feature>
+</features>

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/test/resources/org/apache/karaf/features/repo3.xml
----------------------------------------------------------------------
diff --git a/features/core/src/test/resources/org/apache/karaf/features/repo3.xml b/features/core/src/test/resources/org/apache/karaf/features/repo3.xml
new file mode 100644
index 0000000..ffe08ed
--- /dev/null
+++ b/features/core/src/test/resources/org/apache/karaf/features/repo3.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+<features name="test" xmlns="http://karaf.apache.org/xmlns/features/v1.3.0">
+    <feature name="f1">
+        <capability>
+            cap
+        </capability>
+        <requirement>
+            req
+        </requirement>
+    </feature>
+</features>

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/obr/NOTICE
----------------------------------------------------------------------
diff --git a/features/obr/NOTICE b/features/obr/NOTICE
deleted file mode 100644
index b70f1f9..0000000
--- a/features/obr/NOTICE
+++ /dev/null
@@ -1,71 +0,0 @@
-Apache Karaf
-Copyright 2010-2014 The Apache Software Foundation
-
-
-I. Included Software
-
-This product includes software developed at
-The Apache Software Foundation (http://www.apache.org/).
-Licensed under the Apache License 2.0.
-
-This product uses software developed at
-The OSGi Alliance (http://www.osgi.org/).
-Copyright (c) OSGi Alliance (2000, 2010).
-Licensed under the Apache License 2.0.
-
-This product includes software developed at
-OW2 (http://www.ow2.org/).
-Licensed under the BSD License.
-
-This product includes software developed at
-OPS4J (http://www.ops4j.org/).
-Licensed under the Apache License 2.0.
-
-This product includes software developed at
-Eclipse Foundation (http://www.eclipse.org/).
-Licensed under the EPL.
-
-This product includes software written by
-Antony Lesuisse.
-Licensed under Public Domain.
-
-
-II. Used Software
-
-This product uses software developed at
-FUSE Source (http://www.fusesource.org/).
-Licensed under the Apache License 2.0.
-
-This product uses software developed at
-AOP Alliance (http://aopalliance.sourceforge.net/).
-Licensed under the Public Domain.
-
-This product uses software developed at
-Tanuki Software (http://www.tanukisoftware.com/).
-Licensed under the Apache License 2.0.
-
-This product uses software developed at
-Jasypt (http://jasypt.sourceforge.net/).
-Licensed under the Apache License 2.0.
-
-This product uses software developed at
-JLine (http://jline.sourceforge.net).
-Licensed under the BSD License.
-
-This product uses software developed at
-SLF4J (http://www.slf4j.org/).
-Licensed under the MIT License.
-
-This product uses software developed at
-SpringSource (http://www.springsource.org/).
-Licensed under the Apache License 2.0.
-
-This product includes software from http://www.json.org.
-Copyright (c) 2002 JSON.org
-
-
-III. License Summary
-- Apache License 2.0
-- BSD License
-- EPL License
-- MIT License

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/obr/pom.xml
----------------------------------------------------------------------
diff --git a/features/obr/pom.xml b/features/obr/pom.xml
deleted file mode 100644
index 3aff278..0000000
--- a/features/obr/pom.xml
+++ /dev/null
@@ -1,100 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-
-    <!--
-
-        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.
-    -->
-
-    <modelVersion>4.0.0</modelVersion>
-
-    <parent>
-        <groupId>org.apache.karaf.features</groupId>
-        <artifactId>features</artifactId>
-        <version>4.0.0-SNAPSHOT</version>
-        <relativePath>../pom.xml</relativePath>
-    </parent>
-
-    <artifactId>org.apache.karaf.features.obr</artifactId>
-    <packaging>bundle</packaging>
-    <name>Apache Karaf :: Features :: OBR Resolver</name>
-    <description>This bundle provide OBR (OSGi Bundle Repository) support for Karaf features.</description>
-
-    <properties>
-        <appendedResourcesDirectory>${basedir}/../../etc/appended-resources</appendedResourcesDirectory>
-    </properties>
-
-    <dependencies>
-        <dependency>
-            <groupId>org.osgi</groupId>
-            <artifactId>org.osgi.core</artifactId>
-            <scope>provided</scope>
-        </dependency>
-
-        <dependency>
-            <groupId>org.apache.karaf.features</groupId>
-            <artifactId>org.apache.karaf.features.core</artifactId>
-        </dependency>
-
-        <dependency>
-            <groupId>org.apache.felix</groupId>
-            <artifactId>org.apache.felix.utils</artifactId>
-            <scope>provided</scope>
-        </dependency>
-
-        <dependency>
-            <groupId>org.apache.felix</groupId>
-            <artifactId>org.apache.felix.bundlerepository</artifactId>
-        </dependency>
-
-    </dependencies>
-
-    <build>
-        <resources>
-            <resource>
-                <directory>${project.basedir}/src/main/resources</directory>
-                <includes>
-                    <include>**/*</include>
-                </includes>
-            </resource>
-            <resource>
-                <directory>${project.basedir}/src/main/resources</directory>
-                <filtering>true</filtering>
-                <includes>
-                    <include>**/*.info</include>
-                </includes>
-            </resource>
-        </resources>
-        <plugins>
-            <plugin>
-                <groupId>org.apache.felix</groupId>
-                <artifactId>maven-bundle-plugin</artifactId>
-                <configuration>
-                    <instructions>
-                        <Import-Package>
-                            org.apache.aries.blueprint,
-                            org.osgi.service.blueprint.container,
-                            org.osgi.service.blueprint.reflect,
-                            *
-                        </Import-Package>
-                        <Private-Package>org.apache.karaf.features.obr.internal</Private-Package>
-                    </instructions>
-                </configuration>
-            </plugin>
-        </plugins>
-    </build>
-
-</project>

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/obr/src/main/java/org/apache/karaf/features/obr/internal/BundleInfoImpl.java
----------------------------------------------------------------------
diff --git a/features/obr/src/main/java/org/apache/karaf/features/obr/internal/BundleInfoImpl.java b/features/obr/src/main/java/org/apache/karaf/features/obr/internal/BundleInfoImpl.java
deleted file mode 100644
index ac48da8..0000000
--- a/features/obr/src/main/java/org/apache/karaf/features/obr/internal/BundleInfoImpl.java
+++ /dev/null
@@ -1,95 +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.obr.internal;
-
-import org.apache.karaf.features.BundleInfo;
-
-/**
- * A holder of bundle info
- */
-public class BundleInfoImpl implements BundleInfo {
-
-    private int startLevel;
-    private String location;
-    private boolean start;
-    private boolean dependency;
-
-
-    public BundleInfoImpl() {
-    }
-
-    public BundleInfoImpl(String location) {
-    	this.location = location;
-    }
-
-
-    public BundleInfoImpl(String location, boolean start) {
-    	this.location = location;
-        this.start = start;
-    }
-
-    public BundleInfoImpl(String location, int startLevel) {
-    	this.location = location;
-        this.startLevel = startLevel;
-    }
-
-    public BundleInfoImpl(String location, int startLevel, boolean start) {
-    	this.location = location;
-        this.startLevel = startLevel;
-        this.start = start;
-    }
-
-    public BundleInfoImpl(String location, int startLevel, boolean start, boolean dependency) {
-        this.location = location;
-        this.startLevel = startLevel;
-        this.start = start;
-        this.dependency = dependency;
-    }
-
-    public void setStartLevel(Integer startLevel) {
-    	this.startLevel = startLevel;
-    }
-
-    public int getStartLevel() {
-		return this.startLevel;
-	}
-
-	public void setName(String location) {
-		this.location = location;
-	}
-
-	public String getLocation() {
-		return this.location;
-	}
-
-	public void setStart(boolean start) {
-		this.start = start;
-	}
-
-	public boolean isStart() {
-		return start;
-	}
-
-    public void setDependency(boolean dependency) {
-        this.dependency = dependency;
-    }
-
-    public boolean isDependency() {
-        return dependency;
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/obr/src/main/java/org/apache/karaf/features/obr/internal/ObrResolver.java
----------------------------------------------------------------------
diff --git a/features/obr/src/main/java/org/apache/karaf/features/obr/internal/ObrResolver.java b/features/obr/src/main/java/org/apache/karaf/features/obr/internal/ObrResolver.java
deleted file mode 100644
index 2823e88..0000000
--- a/features/obr/src/main/java/org/apache/karaf/features/obr/internal/ObrResolver.java
+++ /dev/null
@@ -1,199 +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.obr.internal;
-
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import org.apache.felix.bundlerepository.Reason;
-import org.apache.felix.bundlerepository.Repository;
-import org.apache.felix.bundlerepository.RepositoryAdmin;
-import org.apache.felix.bundlerepository.Requirement;
-import org.apache.felix.bundlerepository.Resource;
-import org.apache.karaf.features.BundleInfo;
-import org.apache.karaf.features.Feature;
-import org.apache.karaf.features.Resolver;
-import org.osgi.framework.InvalidSyntaxException;
-
-public class ObrResolver implements Resolver {
-
-    private RepositoryAdmin repositoryAdmin;
-    private boolean resolveOptionalImports;
-    private boolean startByDefault;
-    private int startLevel;
-
-    public RepositoryAdmin getRepositoryAdmin() {
-        return repositoryAdmin;
-    }
-
-    public void setRepositoryAdmin(RepositoryAdmin repositoryAdmin) {
-        this.repositoryAdmin = repositoryAdmin;
-    }
-
-    public boolean isResolveOptionalImports() {
-        return resolveOptionalImports;
-    }
-
-    /**
-     * When set to <code>true</code>, the OBR resolver will try to resolve optional imports as well.
-     * Defaults to <code>false</code>
-     *
-     * @param resolveOptionalImports
-     */
-    public void setResolveOptionalImports(boolean resolveOptionalImports) {
-        this.resolveOptionalImports = resolveOptionalImports;
-    }
-
-    public void setStartByDefault(boolean startByDefault) {
-        this.startByDefault = startByDefault;
-    }
-
-    public void setStartLevel(int startLevel) {
-        this.startLevel = startLevel;
-    }
-
-    public List<BundleInfo> resolve(Feature feature) throws Exception {
-        List<Requirement> reqs = new ArrayList<Requirement>();
-        List<Resource> ress = new ArrayList<Resource>();
-        List<Resource> featureDeploy = new ArrayList<Resource>();
-        Map<Object, BundleInfo> infos = new HashMap<Object, BundleInfo>();
-        for (BundleInfo bundleInfo : feature.getBundles()) {
-        	URL url = null;
-            try {
-                url = new URL(bundleInfo.getLocation());
-            } catch (MalformedURLException e) {
-                Requirement req = parseRequirement(bundleInfo.getLocation());
-                reqs.add(req);
-                infos.put(req, bundleInfo);
-            }
-            if (url != null) {
-            	Resource res = repositoryAdmin.getHelper().createResource(url);
-            	ress.add(res);
-            	infos.put(res, bundleInfo);
-            }
-        }
-
-        Repository repository = repositoryAdmin.getHelper().repository(ress.toArray(new Resource[ress.size()]));
-        List<Repository> repos = new ArrayList<Repository>();
-        repos.add(repositoryAdmin.getSystemRepository());
-        repos.add(repositoryAdmin.getLocalRepository());
-        repos.add(repository);
-        repos.addAll(Arrays.asList(repositoryAdmin.listRepositories()));
-        org.apache.felix.bundlerepository.Resolver resolver = repositoryAdmin.resolver(repos.toArray(new Repository[repos.size()]));
-
-        for (Resource res : ress) {
-            if (!infos.get(res).isDependency()) {
-                resolver.add(res);
-            }
-        }
-        for (Requirement req : reqs) {
-            resolver.add(req);
-        }
-
-        if (!doResolve(resolver)) {
-            StringWriter w = new StringWriter();
-            PrintWriter out = new PrintWriter(w);
-            Reason[] failedReqs = resolver.getUnsatisfiedRequirements();
-            if ((failedReqs != null) && (failedReqs.length > 0)) {
-                out.println("Unsatisfied requirement(s):");
-                printUnderline(out, 27);
-                for (Reason r : failedReqs) {
-                    out.println("   " + r.getRequirement().getName() + ":" + r.getRequirement().getFilter());
-                    out.println("      " + r.getResource().getPresentationName());
-                }
-            } else {
-                out.println("Could not resolve targets.");
-            }
-            out.flush();
-            throw new Exception("Can not resolve feature:\n" + w.toString());
-        }
-
-        List<BundleInfo> bundles = new ArrayList<BundleInfo>();
-        List<Resource> deploy = new ArrayList<Resource>();
-        Collections.addAll(deploy, resolver.getRequiredResources());
-        if (resolveOptionalImports) {
-            Collections.addAll(deploy, resolver.getOptionalResources());
-        }
-        Collections.addAll(deploy, resolver.getAddedResources());
-        deploy.addAll(featureDeploy);
-        for (Resource res : deploy) {
-            BundleInfo info = infos.get(res);
-            if (info == null) {
-                Reason[] reasons = resolver.getReason(res);
-                if (reasons != null) {
-                    for (Reason r : reasons) {
-                        info = infos.get(r);
-                        if (info != null) {
-                            break;
-                        }
-                    }
-                }
-            }
-            if (info == null) {
-                info = new BundleInfoImpl(res.getURI(), this.startLevel, this.startByDefault, false);
-            }
-            bundles.add(info);
-        }
-        return bundles;
-    }
-
-    private boolean doResolve(org.apache.felix.bundlerepository.Resolver resolver) {
-        if (resolveOptionalImports) {
-            return resolver.resolve();
-        } else {
-            return resolver.resolve(org.apache.felix.bundlerepository.Resolver.NO_OPTIONAL_RESOURCES);
-        }
-    }
-
-    protected void printUnderline(PrintWriter out, int length) {
-        for (int i = 0; i < length; i++) {
-            out.print('-');
-        }
-        out.println("");
-    }
-
-    protected Requirement parseRequirement(String req) throws InvalidSyntaxException {
-        int p = req.indexOf(':');
-        String name;
-        String filter;
-        if (p > 0) {
-            name = req.substring(0, p);
-            filter = req.substring(p + 1);
-        } else {
-            if (req.contains("package")) {
-                name = "package";
-            } else if (req.contains("service")) {
-                name = "service";
-            } else {
-                name = "bundle";
-            }
-            filter = req;
-        }
-        if (!filter.startsWith("(")) {
-            filter = "(" + filter + ")";
-        }
-        return repositoryAdmin.getHelper().requirement(name, filter);
-    }
-}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/obr/src/main/resources/OSGI-INF/blueprint/features-obr.xml
----------------------------------------------------------------------
diff --git a/features/obr/src/main/resources/OSGI-INF/blueprint/features-obr.xml b/features/obr/src/main/resources/OSGI-INF/blueprint/features-obr.xml
deleted file mode 100644
index 590258f..0000000
--- a/features/obr/src/main/resources/OSGI-INF/blueprint/features-obr.xml
+++ /dev/null
@@ -1,50 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-
-    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.
-
--->
-<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
-           xmlns:ext="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.0.0"
-           default-activation="lazy">
-
-    <ext:property-placeholder placeholder-prefix="$(" placeholder-suffix=")"/>
-
-    <ext:property-placeholder placeholder-prefix="$[" placeholder-suffix="]" ignore-missing-locations="true">
-        <ext:default-properties>
-            <ext:property name="resolveOptionalImports" value="false"/>
-            <ext:property name="startByDefault" value="true"/>
-            <ext:property name="startLevel" value="80"/>
-        </ext:default-properties>
-        <ext:location>file:$(karaf.etc)/org.apache.karaf.features.obr.cfg</ext:location>
-    </ext:property-placeholder>
-
-    <bean id="obrResolver" class="org.apache.karaf.features.obr.internal.ObrResolver">
-        <property name="repositoryAdmin" ref="repositoryAdmin" />
-        <property name="resolveOptionalImports" value="$[resolveOptionalImports]" />
-        <property name="startByDefault" value="$[startByDefault]" />
-        <property name="startLevel" value="$[startLevel]" />
-    </bean>
-
-    <reference id="repositoryAdmin" interface="org.apache.felix.bundlerepository.RepositoryAdmin" />
-
-    <service ref="obrResolver" interface="org.apache.karaf.features.Resolver">
-        <service-properties>
-            <entry key="name" value="obr" />
-        </service-properties>
-    </service>
-
-</blueprint>

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/obr/src/main/resources/OSGI-INF/bundle.info
----------------------------------------------------------------------
diff --git a/features/obr/src/main/resources/OSGI-INF/bundle.info b/features/obr/src/main/resources/OSGI-INF/bundle.info
deleted file mode 100644
index bf550bf..0000000
--- a/features/obr/src/main/resources/OSGI-INF/bundle.info
+++ /dev/null
@@ -1,18 +0,0 @@
-h1. Synopsis
-
-${project.name}
-
-${project.description}
-
-Maven URL:
-[mvn:${project.groupId}/${project.artifactId}/${project.version}]
-
-h1. Description
-
-This bundle provides the OBR (OSGi Bundle Repository) support for Karaf features.
-
-It allows you to use an OBR resolver for bundles.
-
-h1. See also
-
-Provisioning - section of the Karaf User Guide


[13/13] git commit: [KARAF-2888] New FeaturesService based on the real OSGi resolver

Posted by gn...@apache.org.
[KARAF-2888] New FeaturesService based on the real OSGi resolver


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

Branch: refs/heads/master
Commit: 38502e41540de3443b2d7f8215d43bb33db5e1c0
Parents: 9605df3
Author: Guillaume Nodet <gn...@gmail.com>
Authored: Mon Apr 7 09:25:51 2014 +0200
Committer: Guillaume Nodet <gn...@gmail.com>
Committed: Mon Apr 7 10:52:37 2014 +0200

----------------------------------------------------------------------
 .../enterprise/src/main/feature/feature.xml     |   17 +-
 .../standard/src/main/feature/feature.xml       |    9 +-
 features/core/pom.xml                           |   19 +-
 .../org/apache/karaf/features/Capability.java   |   23 +
 .../java/org/apache/karaf/features/Feature.java |    5 +
 .../karaf/features/FeaturesNamespaces.java      |    6 +-
 .../org/apache/karaf/features/Requirement.java  |   23 +
 .../karaf/features/internal/Artifact.java       |   56 -
 .../internal/BootFeaturesInstaller.java         |  171 ---
 .../karaf/features/internal/BundleManager.java  |  498 -------
 .../features/internal/EventAdminListener.java   |   92 --
 .../internal/FeatureConfigInstaller.java        |  166 ---
 .../karaf/features/internal/FeatureFinder.java  |   59 -
 .../internal/FeatureValidationUtil.java         |  110 --
 .../features/internal/FeaturesServiceImpl.java  | 1192 ---------------
 .../features/internal/InstallationState.java    |   35 -
 .../karaf/features/internal/Overrides.java      |  230 ---
 .../karaf/features/internal/RepositoryImpl.java |   99 --
 .../internal/deployment/DeploymentBuilder.java  |  330 +++++
 .../internal/deployment/Downloader.java         |   35 +
 .../internal/deployment/StreamProvider.java     |   26 +
 .../management/FeaturesServiceMBeanImpl.java    |  274 ++++
 .../management/StandardEmitterMBean.java        |   65 +
 .../features/internal/model/Capability.java     |   91 ++
 .../karaf/features/internal/model/Feature.java  |   36 +-
 .../features/internal/model/Requirement.java    |   87 ++
 .../karaf/features/internal/osgi/Activator.java |  114 +-
 .../repository/AggregateRepository.java         |   55 +
 .../internal/repository/BaseRepository.java     |   86 ++
 .../internal/repository/CacheRepository.java    |   59 +
 .../repository/HttpMetadataProvider.java        |   88 ++
 .../internal/repository/MetadataProvider.java   |   29 +
 .../internal/repository/MetadataRepository.java |   43 +
 .../internal/repository/StaticRepository.java   |   33 +
 .../features/internal/resolver/BaseClause.java  |  114 ++
 .../internal/resolver/CandidateComparator.java  |  129 ++
 .../internal/resolver/CapabilityImpl.java       |  165 +++
 .../internal/resolver/CapabilitySet.java        |  612 ++++++++
 .../internal/resolver/FeatureNamespace.java     |   72 +
 .../internal/resolver/FeatureResource.java      |  101 ++
 .../internal/resolver/IdentityCapability.java   |   63 +
 .../internal/resolver/RequirementImpl.java      |   80 +
 .../internal/resolver/ResolveContextImpl.java   |  102 ++
 .../internal/resolver/ResourceBuilder.java      | 1129 ++++++++++++++
 .../internal/resolver/ResourceImpl.java         |  110 ++
 .../internal/resolver/ServiceNamespace.java     |   30 +
 .../internal/resolver/SimpleFilter.java         |  649 +++++++++
 .../internal/resolver/Slf4jResolverLog.java     |   49 +
 .../internal/resolver/UriNamespace.java         |   47 +
 .../features/internal/service/Artifact.java     |   56 +
 .../internal/service/BootFeaturesInstaller.java |  193 +++
 .../internal/service/EventAdminListener.java    |   91 ++
 .../service/FeatureConfigInstaller.java         |  167 +++
 .../internal/service/FeatureFinder.java         |   68 +
 .../internal/service/FeatureValidationUtil.java |  113 ++
 .../internal/service/FeaturesServiceImpl.java   | 1378 ++++++++++++++++++
 .../features/internal/service/Overrides.java    |  132 ++
 .../internal/service/RepositoryImpl.java        |  103 ++
 .../internal/service/RequirementSort.java       |  107 ++
 .../internal/service/SimpleDownloader.java      |   51 +
 .../karaf/features/internal/service/State.java  |   34 +
 .../features/internal/service/StateStorage.java |  175 +++
 .../features/internal/util/ChecksumUtils.java   |   56 +
 .../features/internal/util/JsonReader.java      |  343 +++++
 .../features/internal/util/JsonWriter.java      |  120 ++
 .../karaf/features/internal/util/Macro.java     |  142 ++
 .../features/internal/util/MultiException.java  |   95 ++
 .../management/FeaturesServiceMBean.java        |    2 +-
 .../management/codec/JmxRepositoryEvent.java    |   13 +-
 .../internal/FeaturesServiceMBeanImpl.java      |  274 ----
 .../internal/StandardEmitterMBean.java          |   65 -
 .../karaf/features/karaf-features-1.3.0.xsd     |  280 ++++
 .../apache/karaf/features/ConditionalTest.java  |    5 +-
 .../karaf/features/FeaturesServiceTest.java     |  165 +--
 .../apache/karaf/features/RepositoryTest.java   |   24 +-
 .../org/apache/karaf/features/TestBase.java     |  105 ++
 .../internal/BootFeaturesInstallerTest.java     |  103 --
 .../features/internal/BundleManagerTest.java    |   74 -
 .../internal/FeaturesServiceImplTest.java       |  178 ---
 .../internal/FeaturesValidationTest.java        |   60 -
 .../karaf/features/internal/OverridesTest.java  |  243 ---
 .../karaf/features/internal/TestBase.java       |  108 --
 .../service/BootFeaturesInstallerTest.java      |  128 ++
 .../internal/service/BundleManagerTest.java     |   64 +
 .../service/FeaturesServiceImplTest.java        |  167 +++
 .../service/FeaturesValidationTest.java         |   65 +
 .../internal/service/OverridesTest.java         |  208 +++
 .../org/apache/karaf/features/internal/f01.xml  |   92 --
 .../org/apache/karaf/features/internal/f02.xml  |  164 ---
 .../org/apache/karaf/features/internal/f03.xml  |   27 -
 .../org/apache/karaf/features/internal/f04.xml  |   28 -
 .../org/apache/karaf/features/internal/f05.xml  |   28 -
 .../org/apache/karaf/features/internal/f06.xml  |   32 -
 .../features/internal/overrides.properties      |   23 -
 .../apache/karaf/features/internal/repo2.xml    |   41 -
 .../karaf/features/internal/service/f01.xml     |   92 ++
 .../karaf/features/internal/service/f02.xml     |  164 +++
 .../karaf/features/internal/service/f03.xml     |   27 +
 .../karaf/features/internal/service/f04.xml     |   28 +
 .../karaf/features/internal/service/f05.xml     |   28 +
 .../karaf/features/internal/service/f06.xml     |   32 +
 .../karaf/features/internal/service/f07.xml     |   35 +
 .../internal/service/overrides.properties       |   23 +
 .../karaf/features/internal/service/repo2.xml   |   41 +
 .../org/apache/karaf/features/repo3.xml         |   27 +
 features/obr/NOTICE                             |   71 -
 features/obr/pom.xml                            |  100 --
 .../features/obr/internal/BundleInfoImpl.java   |   95 --
 .../features/obr/internal/ObrResolver.java      |  199 ---
 .../OSGI-INF/blueprint/features-obr.xml         |   50 -
 .../obr/src/main/resources/OSGI-INF/bundle.info |   18 -
 .../features/obr/internal/ObrResolverTest.java  |  174 ---
 features/pom.xml                                |    1 -
 instance/core/pom.xml                           |    3 +
 .../karaf/itests/ConditionalFeaturesTest.java   |    2 +-
 .../itests/FeatureSshCommandSecurityTest.java   |   29 +-
 .../org/apache/karaf/itests/FeatureTest.java    |   12 +-
 .../java/org/apache/karaf/itests/HttpTest.java  |    2 +
 .../apache/karaf/itests/KarafTestSupport.java   |   44 +-
 .../itests/features/Spring3FeaturesTest.java    |    4 +
 management/server/pom.xml                       |    3 +
 pom.xml                                         |    6 +
 shell/console/pom.xml                           |   42 +-
 shell/core/pom.xml                              |    3 +
 .../src/it/test-aggregate-features/control.xml  |    2 +-
 .../src/it/test-basic-generation/control.xml    |    2 +-
 .../test-check-dependencies-failure/control.xml |    2 +-
 .../src/it/test-check-dependencies/control.xml  |    2 +-
 .../src/it/test-input-file/control.xml          |    2 +-
 .../src/it/test-type-classifier/control.xml     |    2 +-
 .../features/ValidateDescriptorMojo.java        |    4 +-
 .../karaf/util/tracker/BaseActivator.java       |   12 +-
 webconsole/branding/pom.xml                     |    2 +-
 .../webconsole/features/ExtendedFeature.java    |   12 +
 .../webconsole/features/FeaturesPlugin.java     |    2 -
 135 files changed, 10070 insertions(+), 5232 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/assemblies/features/enterprise/src/main/feature/feature.xml
----------------------------------------------------------------------
diff --git a/assemblies/features/enterprise/src/main/feature/feature.xml b/assemblies/features/enterprise/src/main/feature/feature.xml
index 06905e4..69065ba 100644
--- a/assemblies/features/enterprise/src/main/feature/feature.xml
+++ b/assemblies/features/enterprise/src/main/feature/feature.xml
@@ -33,8 +33,11 @@
             aries.transaction.howl.bufferSizeKBytes = 4
         </config>
         <bundle dependency="true" start-level="30">mvn:org.apache.geronimo.specs/geronimo-jta_1.1_spec/${geronimo.jta-spec.version}</bundle>
-        <bundle start-level="30">mvn:org.apache.aries.transaction/org.apache.aries.transaction.blueprint/${aries.transaction.blueprint.version}</bundle>
         <bundle start-level="30">mvn:org.apache.aries.transaction/org.apache.aries.transaction.manager/${aries.transaction.manager.version}</bundle>
+        <conditional>
+            <condition>aries-blueprint</condition>
+            <bundle start-level="30">mvn:org.apache.aries.transaction/org.apache.aries.transaction.blueprint/${aries.transaction.blueprint.version}</bundle>
+        </conditional>
     </feature>
 
     <feature name="jpa" description="OSGi Persistence Container" version="${aries.jpa.version}" resolver="(obr)">
@@ -42,9 +45,12 @@
         <bundle dependency="true" start-level="30">mvn:org.apache.geronimo.specs/geronimo-jta_1.1_spec/${geronimo.jta-spec.version}</bundle>
         <bundle dependency="true" start-level="30">mvn:org.apache.geronimo.specs/geronimo-jpa_2.0_spec/${geronimo.jpa-spec.version}</bundle>
         <bundle start-level="30">mvn:org.apache.aries.jpa/org.apache.aries.jpa.api/${aries.jpa.api.version}</bundle>
-        <bundle start-level="30">mvn:org.apache.aries.jpa/org.apache.aries.jpa.blueprint.aries/${aries.jpa.blueprint.aries.version}</bundle>
         <bundle start-level="30">mvn:org.apache.aries.jpa/org.apache.aries.jpa.container/${aries.jpa.container.version}</bundle>
         <bundle start-level="30">mvn:org.apache.aries.jpa/org.apache.aries.jpa.container.context/${aries.jpa.container.context.version}</bundle>
+        <conditional>
+            <condition>aries-blueprint</condition>
+            <bundle start-level="30">mvn:org.apache.aries.jpa/org.apache.aries.jpa.blueprint.aries/${aries.jpa.blueprint.aries.version}</bundle>
+        </conditional>
     </feature>
 
     <feature name="openjpa" description="Apache OpenJPA 2.2.x persistence engine support" version="2.2.2" resolver="(obr)">
@@ -161,12 +167,14 @@
         <bundle dependency="true">mvn:com.fasterxml/classmate/1.0.0</bundle>
         <bundle dependency="true">mvn:javax.el/javax.el-api/2.2.4</bundle>
         <bundle dependency="true">mvn:org.glassfish.web/javax.el/2.2.4</bundle>
-        <bundle dependency="true">mvn:org.hibernate/hibernate-validator/${hibernate.validator.version}</bundle>
         <bundle dependency="true">mvn:org.jboss.logging/jboss-logging/3.1.4.GA</bundle>
+        <bundle>mvn:org.hibernate/hibernate-validator/${hibernate.validator.version}</bundle>
     </feature>
 
     <feature name="jndi" description="OSGi Service Registry JNDI access" version="${project.version}" resolver="(obr)">
         <details>JNDI support provided by Apache Aries JNDI ${aries.jndi.version}, including additional service, commands, and MBean.</details>
+        <feature>aries-proxy</feature>
+        <feature>aries-blueprint</feature>
         <bundle start-level="30">mvn:org.apache.xbean/xbean-naming/${xbean.version}</bundle>
         <bundle start-level="30">mvn:org.apache.karaf.jndi/org.apache.karaf.jndi.core/${project.version}</bundle>
         <bundle start-level="30">mvn:org.apache.aries.jndi/org.apache.aries.jndi.api/${aries.jndi.api.version}</bundle>
@@ -180,6 +188,7 @@
     <feature name="jdbc" description="JDBC service and commands" version="${project.version}" resolver="(obr)">
         <details>JDBC support providing service, commands, and MBean.</details>
         <feature>transaction</feature>
+        <feature>aries-blueprint</feature>
         <bundle>mvn:commons-pool/commons-pool/${commons-pool.version}</bundle>
         <bundle>mvn:commons-dbcp/commons-dbcp/${commons-dbcp.version}</bundle>
         <bundle>mvn:org.apache.karaf.jdbc/org.apache.karaf.jdbc.core/${project.version}</bundle>
@@ -189,6 +198,7 @@
     <feature name="jms" description="JMS service and commands" version="${project.version}" resolver="(obr)">
         <details>JMS support provinding service, commands, and MBean.</details>
         <feature>transaction</feature>
+        <feature>aries-blueprint</feature>
         <bundle>mvn:org.apache.geronimo.specs/geronimo-jms_1.1_spec/${geronimo.jms-spec.version}</bundle>
         <bundle>mvn:org.apache.karaf.jms/org.apache.karaf.jms.core/${project.version}</bundle>
         <bundle>mvn:org.apache.karaf.jms/org.apache.karaf.jms.command/${project.version}</bundle>
@@ -212,6 +222,7 @@
         <details>Support of the Aries EBA archives</details>
         <!-- pre-requisites-->
         <feature version="${project.version}">obr</feature>
+        <feature>aries-blueprint</feature>
         <!-- common -->
         <bundle start-level="30">mvn:org.apache.aries.application/org.apache.aries.application.resolver.obr/${aries.application.version}</bundle>
         <bundle start-level="30">mvn:org.apache.aries.application/org.apache.aries.application.install/${aries.application.version}</bundle>

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/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 3fae4aa..49028d2 100644
--- a/assemblies/features/standard/src/main/feature/feature.xml
+++ b/assemblies/features/standard/src/main/feature/feature.xml
@@ -16,7 +16,7 @@
       See the License for the specific language governing permissions and
       limitations under the License.
 -->
-<features name="standard-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://karaf.apache.org/xmlns/features/v1.2.0 http://karaf.apache.org/xmlns/features/v1.2.0">
+<features name="standard-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.3.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://karaf.apache.org/xmlns/features/v1.3.0 http://karaf.apache.org/xmlns/features/v1.3.0">
 
 	<repository>mvn:org.ops4j.pax.web/pax-web-features/${pax.web.version}/xml/features</repository>
 
@@ -35,6 +35,9 @@
         <bundle dependency="true" start-level="20">mvn:org.apache.aries/org.apache.aries.util/${aries.util.version}</bundle>
         <bundle start-level="20">mvn:org.apache.aries.proxy/org.apache.aries.proxy.api/${aries.proxy.api.version}</bundle>
         <bundle start-level="20">mvn:org.apache.aries.proxy/org.apache.aries.proxy.impl/${aries.proxy.version}</bundle>
+        <capability>
+            service-reference;effective:=active;objectClass=org.apache.aries.proxy.ProxyManager
+        </capability>
     </feature>
 
     <feature name="aries-blueprint" description="Aries Blueprint" version="${project.version}">
@@ -47,6 +50,9 @@
             <condition>bundle</condition>
             <bundle start-level="30">mvn:org.apache.karaf.bundle/org.apache.karaf.bundle.blueprintstate/${project.version}</bundle>
         </conditional>
+        <capability>
+            service-reference;effective:=active;objectClass=org.apache.aries.blueprint.services.ParserService
+        </capability>
     </feature>
 
     <feature name="aries-annotation" description="Aries Annotations" version="${project.version}">
@@ -202,6 +208,7 @@
             realm=karaf
         </config>
         <feature>http</feature>
+        <feature>aries-blueprint</feature>
         <bundle start-level="30">mvn:org.apache.felix/org.apache.felix.metatype/${felix.metatype.version}</bundle>
         <bundle start-level="30">mvn:org.apache.karaf.webconsole/org.apache.karaf.webconsole.branding/${project.version}</bundle>
         <bundle start-level="30">mvn:org.apache.karaf.webconsole/org.apache.karaf.webconsole.console/${project.version}</bundle>

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/pom.xml
----------------------------------------------------------------------
diff --git a/features/core/pom.xml b/features/core/pom.xml
index 8459b3d..fc9f9ce 100644
--- a/features/core/pom.xml
+++ b/features/core/pom.xml
@@ -65,6 +65,11 @@
             <artifactId>org.apache.karaf.util</artifactId>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.resolver</artifactId>
+            <scope>provided</scope>
+        </dependency>
 
         <dependency>
             <groupId>org.slf4j</groupId>
@@ -107,15 +112,19 @@
                             org.apache.karaf.features.management.codec;
                                 -noimport:=true
                         </Export-Package>
+                        <Provide-Capability>
+                            service-reference;effective:=active;objectClass=org.apache.karaf.features.FeaturesService
+                        </Provide-Capability>
                         <Private-Package>
-                            org.apache.karaf.features.internal,
-                            org.apache.karaf.features.internal.model,
-                            org.apache.karaf.features.internal.osgi,
-                            org.apache.karaf.features.management.internal,
+                            org.apache.karaf.features.internal.*,
+                            org.apache.felix.resolver,
                             org.apache.felix.utils.version,
                             org.apache.felix.utils.manifest,
                             org.apache.karaf.util.collections,
-                            org.apache.karaf.util.tracker
+                            org.apache.karaf.util.json,
+                            org.apache.karaf.util.tracker,
+                            org.osgi.service.resolver,
+                            org.osgi.service.repository
                         </Private-Package>
                         <Bundle-Activator>
                             org.apache.karaf.features.internal.osgi.Activator

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/Capability.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/Capability.java b/features/core/src/main/java/org/apache/karaf/features/Capability.java
new file mode 100644
index 0000000..d329708
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/Capability.java
@@ -0,0 +1,23 @@
+/*
+ * 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;
+
+public interface Capability {
+
+    String getValue();
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/Feature.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/Feature.java b/features/core/src/main/java/org/apache/karaf/features/Feature.java
index 4dfb624..2f9f001 100644
--- a/features/core/src/main/java/org/apache/karaf/features/Feature.java
+++ b/features/core/src/main/java/org/apache/karaf/features/Feature.java
@@ -55,4 +55,9 @@ public interface Feature {
     int getStartLevel();
 
     String getRegion();
+
+    List<? extends Capability> getCapabilities();
+
+    List<? extends Requirement> getRequirements();
+
 }

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/FeaturesNamespaces.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/FeaturesNamespaces.java b/features/core/src/main/java/org/apache/karaf/features/FeaturesNamespaces.java
index 8e09748..282ff71 100644
--- a/features/core/src/main/java/org/apache/karaf/features/FeaturesNamespaces.java
+++ b/features/core/src/main/java/org/apache/karaf/features/FeaturesNamespaces.java
@@ -26,14 +26,16 @@ public interface FeaturesNamespaces {
     String URI_1_0_0 = "http://karaf.apache.org/xmlns/features/v1.0.0";
     String URI_1_1_0 = "http://karaf.apache.org/xmlns/features/v1.1.0";
     String URI_1_2_0 = "http://karaf.apache.org/xmlns/features/v1.2.0";
+    String URI_1_3_0 = "http://karaf.apache.org/xmlns/features/v1.3.0";
 
-    String URI_CURRENT = URI_1_2_0;
+    String URI_CURRENT = URI_1_3_0;
 
     QName FEATURES_0_0_0 = new QName("features");
     QName FEATURES_1_0_0 = new QName(URI_1_0_0, "features");
     QName FEATURES_1_1_0 = new QName(URI_1_1_0, "features");
     QName FEATURES_1_2_0 = new QName(URI_1_2_0, "features");
+    QName FEATURES_1_3_0 = new QName(URI_1_3_0, "features");
 
-    QName FEATURES_CURRENT = FEATURES_1_2_0;
+    QName FEATURES_CURRENT = FEATURES_1_3_0;
 
 }

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/Requirement.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/Requirement.java b/features/core/src/main/java/org/apache/karaf/features/Requirement.java
new file mode 100644
index 0000000..4446335
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/Requirement.java
@@ -0,0 +1,23 @@
+/*
+ * 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;
+
+public interface Requirement {
+
+    String getValue();
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/internal/Artifact.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/Artifact.java b/features/core/src/main/java/org/apache/karaf/features/internal/Artifact.java
deleted file mode 100644
index 94e72e3..0000000
--- a/features/core/src/main/java/org/apache/karaf/features/internal/Artifact.java
+++ /dev/null
@@ -1,56 +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;
-
-import java.net.URI;
-
-/**
- * Simple abstraction of a maven artifact to avoid external deps
- */
-public class Artifact {
-    String groupId;
-    String artifactId;
-    String version;
-    String extension;
-    String classifier;
-    
-    public Artifact(String coords) {
-        String[] coordsAr = coords.split(":");
-        if (coordsAr.length != 5) {
-            throw new IllegalArgumentException("Maven URL " + coords + " is malformed or not complete");
-        }
-        this.groupId = coordsAr[0];
-        this.artifactId = coordsAr[1];
-        this.version = coordsAr[4];
-        this.extension = coordsAr[2];
-        this.classifier = coordsAr[3];
-    }
-    
-    public Artifact(String coords, String version) {
-        this(coords);
-        this.version = version;
-    }
-    
-    public URI getPaxUrlForArtifact(String version) {
-        String uriSt = "mvn:" + this.groupId + "/" + this.artifactId + "/" + version + "/" + this.extension + "/" + this.classifier;
-        try {
-            return new URI(uriSt);
-        } catch (Exception e) {
-            return null;
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/internal/BootFeaturesInstaller.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/BootFeaturesInstaller.java b/features/core/src/main/java/org/apache/karaf/features/internal/BootFeaturesInstaller.java
deleted file mode 100644
index 1b4fa9d..0000000
--- a/features/core/src/main/java/org/apache/karaf/features/internal/BootFeaturesInstaller.java
+++ /dev/null
@@ -1,171 +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;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.EnumSet;
-import java.util.HashSet;
-import java.util.Hashtable;
-import java.util.List;
-import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import org.apache.karaf.features.BootFinished;
-import org.apache.karaf.features.Feature;
-import org.apache.karaf.features.FeaturesService;
-import org.apache.karaf.features.FeaturesService.Option;
-import org.osgi.framework.BundleContext;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Manages installation of the boot features in the background
- */
-public class BootFeaturesInstaller {
-	private static final Logger LOGGER = LoggerFactory.getLogger(BootFeaturesInstaller.class);
-
-    public static String VERSION_PREFIX = "version=";
-
-    private final BundleContext bundleContext;
-    private final FeaturesService featuresService;
-    private final String boot;
-    private final boolean bootAsynchronous;
-
-    /**
-     * 
-     * @param featuresService
-     * @param boot list of boot features separated by comma. Optionally contains ;version=x.x.x to specify a specific feature version
-     */
-    public BootFeaturesInstaller(BundleContext bundleContext, FeaturesService featuresService, String boot, boolean bootAsynchronous) {
-		this.bundleContext = bundleContext;
-        this.featuresService = featuresService;
-		this.boot = boot;
-        this.bootAsynchronous = bootAsynchronous;
-	}
-    
-    /**
-     * Install boot features
-     * @throws Exception
-     */
-    public void start() {
-        if (boot != null) {
-            if (bootAsynchronous) {
-                new Thread() {
-                    public void run() {
-                        installBootFeatures();
-                        publishBootFinished();
-                    }
-                }.start();
-            } else {
-                installBootFeatures();
-                publishBootFinished();
-            }
-        } else {
-            publishBootFinished();
-        }
-    }
-    
-	void installBootFeatures() {
-        try {
-            List<Feature> installedFeatures = Arrays.asList(featuresService.listInstalledFeatures());
-            List<Set<String>> stagedFeatureNames = parseBootFeatures(boot);
-            List<Set<Feature>> stagedFeatures = toFeatureSetList(stagedFeatureNames);
-
-            for (Set<Feature> features : stagedFeatures) {
-                features.removeAll(installedFeatures);
-                featuresService.installFeatures(features, EnumSet.of(Option.NoCleanIfFailure, Option.ContinueBatchOnFailure));                
-            }
-        } catch (Exception e) {
-            LOGGER.error("Error installing boot features", e);
-        }
-	}
-	
-	private List<Set<Feature>> toFeatureSetList(List<Set<String>> stagedFeatures) {
-	    ArrayList<Set<Feature>> result = new ArrayList<Set<Feature>>();
-	    for (Set<String> features : stagedFeatures) {
-	        HashSet<Feature> featureSet = new HashSet<Feature>();
-            for (String featureName : features) {
-                try {
-                    Feature feature = getFeature(featureName);
-                    if (feature == null) {
-                        LOGGER.error("Error Boot feature " + featureName + " not found");
-                    } else {
-                        featureSet.add(feature);
-                    }
-                } catch (Exception e) {
-                    LOGGER.error("Error getting feature for feature string " + featureName, e);
-                }
-            }
-            result.add(featureSet);
-        }
-        return result;
-	}
-	
-	/**
-	 * 
-	 * @param featureSt either feature name or <featurename>;version=<version>
-	 * @return feature matching the feature string
-	 * @throws Exception
-	 */
-    private Feature getFeature(String featureSt) throws Exception {
-        String[] parts = featureSt.trim().split(";");
-        String featureName = parts[0];
-        String featureVersion = null;
-        for (String part : parts) {
-            // if the part starts with "version=" it contains the version info
-            if (part.startsWith(VERSION_PREFIX)) {
-                featureVersion = part.substring(VERSION_PREFIX.length());
-            }
-        }
-        if (featureVersion == null) {
-            // no version specified - use default version
-            featureVersion = org.apache.karaf.features.internal.model.Feature.DEFAULT_VERSION;
-        }
-        return featuresService.getFeature(featureName, featureVersion);
-    }
-    
-    protected List<Set<String>> parseBootFeatures(String bootFeatures) {
-        Pattern pattern = Pattern.compile("(\\((.+))\\),|.+");
-        Matcher matcher = pattern.matcher(bootFeatures);
-        List<Set<String>> result = new ArrayList<Set<String>>();
-        while (matcher.find()) {
-            String group = matcher.group(2) != null ? matcher.group(2) : matcher.group();
-            result.add(parseFeatureList(group));
-        }
-        return result;
-    }
-
-    private Set<String> parseFeatureList(String group) {
-        HashSet<String> features = new HashSet<String>();
-        for (String feature : Arrays.asList(group.trim().split("\\s*,\\s*"))) {
-            if (feature.length() > 0) {
-                features.add(feature);
-            }
-        }
-        return features;
-    }
-
-    private void publishBootFinished() {
-        if (bundleContext != null) {
-            BootFinished bootFinished = new BootFinished() {};
-            bundleContext.registerService(BootFinished.class, bootFinished, new Hashtable<String, String>());
-        }
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/internal/BundleManager.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/BundleManager.java b/features/core/src/main/java/org/apache/karaf/features/internal/BundleManager.java
deleted file mode 100644
index be4fca7..0000000
--- a/features/core/src/main/java/org/apache/karaf/features/internal/BundleManager.java
+++ /dev/null
@@ -1,498 +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;
-
-import java.io.BufferedInputStream;
-import java.io.File;
-import java.io.FilterInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InterruptedIOException;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.jar.JarInputStream;
-import java.util.jar.Manifest;
-
-import org.apache.felix.utils.manifest.Clause;
-import org.apache.felix.utils.manifest.Parser;
-import org.apache.felix.utils.version.VersionRange;
-import org.apache.karaf.features.FeaturesService.Option;
-import org.apache.karaf.features.RegionsPersistence;
-import org.apache.karaf.features.Resolver;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.BundleException;
-import org.osgi.framework.Constants;
-import org.osgi.framework.Filter;
-import org.osgi.framework.FrameworkEvent;
-import org.osgi.framework.FrameworkListener;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.InvalidSyntaxException;
-import org.osgi.framework.Version;
-import org.osgi.framework.startlevel.BundleStartLevel;
-import org.osgi.framework.wiring.FrameworkWiring;
-import org.osgi.service.url.URLStreamHandlerService;
-import org.osgi.util.tracker.ServiceTracker;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class BundleManager {
-
-    private static final Logger LOGGER = LoggerFactory.getLogger(BundleManager.class);
-    private final BundleContext bundleContext;
-    private final long refreshTimeout;
-    private RegionsPersistence regionsPersistence;
-
-    public BundleManager(BundleContext bundleContext) {
-        this(bundleContext, 5000);
-    }
-
-    public BundleManager(BundleContext bundleContext, long refreshTimeout) {
-        this.bundleContext = bundleContext;
-        this.refreshTimeout = refreshTimeout;
-    }
-
-    public void setRegionsPersistence(RegionsPersistence regionsPersistence) {
-        this.regionsPersistence = regionsPersistence;
-    }
-
-    public BundleInstallerResult installBundleIfNeeded(String bundleLocation, int startLevel, String regionName) throws IOException, BundleException {
-        BundleInstallerResult result = doInstallBundleIfNeeded(bundleLocation, startLevel, regionName);
-        installToRegion(regionName, result.bundle, result.isNew);
-        return result;
-    }
-
-    private void installToRegion(String region, Bundle bundle, boolean isNew) throws BundleException {
-        if (region != null && isNew) {
-            if (regionsPersistence != null) {
-                regionsPersistence.install(bundle, region);
-            }
-        }
-    }
-
-    private BundleInstallerResult doInstallBundleIfNeeded(String bundleLocation, int startLevel, String regionName) throws IOException, BundleException {
-        InputStream is = getInputStreamForBundle(bundleLocation);
-        try {
-            is.mark(256 * 1024);
-            @SuppressWarnings("resource")
-            JarInputStream jar = new JarInputStream(is);
-            Manifest m = jar.getManifest();
-            if (m == null) {
-                throw new BundleException("Manifest not present in the first entry of the zip " + bundleLocation);
-            }
-            String sn = m.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME);
-            if (sn == null) {
-                throw new BundleException("Jar is not a bundle, no Bundle-SymbolicName " + bundleLocation);
-            }
-            // remove attributes from the symbolic name (like
-            // ;blueprint.graceperiod:=false suffix)
-            int attributeIndexSep = sn.indexOf(';');
-            if (attributeIndexSep != -1) {
-                sn = sn.substring(0, attributeIndexSep);
-            }
-            String vStr = m.getMainAttributes().getValue(Constants.BUNDLE_VERSION);
-            Version v = vStr == null ? Version.emptyVersion : Version.parseVersion(vStr);
-            Bundle existingBundle = findInstalled(sn, v);
-            if (existingBundle != null) {
-                LOGGER.debug("Found installed bundle: " + existingBundle);
-                return new BundleInstallerResult(existingBundle, false);
-            }
-            try {
-                is.reset();
-            } catch (IOException e) {
-                is.close();
-                is = new URL(bundleLocation).openStream();
-                // is = new BufferedInputStream(new
-                // URL(bundleLocation).openStream());
-            }
-            is = new BufferedInputStream(new FilterInputStream(is) {
-                @Override
-                public int read(byte b[], int off, int len) throws IOException {
-                    if (Thread.currentThread().isInterrupted()) {
-                        throw new InterruptedIOException();
-                    }
-                    return super.read(b, off, len);
-                }
-            });
-
-            LOGGER.debug("Installing bundle " + bundleLocation);
-            Bundle b = bundleContext.installBundle(bundleLocation, is);
-
-            if (startLevel > 0) {
-                b.adapt(BundleStartLevel.class).setStartLevel(startLevel);
-            }
-
-            return new BundleInstallerResult(b, true);
-        } finally {
-            is.close();
-        }
-    }
-
-    private Bundle findInstalled(String symbolicName, Version version) {
-        String vStr;
-        for (Bundle b : bundleContext.getBundles()) {
-            if (b.getSymbolicName() != null && b.getSymbolicName().equals(symbolicName)) {
-                vStr = (String) b.getHeaders().get(Constants.BUNDLE_VERSION);
-                Version bv = vStr == null ? Version.emptyVersion : Version.parseVersion(vStr);
-                if (version.equals(bv)) {
-                    return b;
-                }
-            }
-        }
-        return null;
-    }
-
-    private InputStream getInputStreamForBundle(String bundleLocation) throws MalformedURLException, IOException {
-        InputStream is;
-        LOGGER.debug("Checking " + bundleLocation);
-        try {
-            int protocolIndex = bundleLocation.indexOf(":");
-            if (protocolIndex != -1) {
-                String protocol = bundleLocation.substring(0, protocolIndex);
-                waitForUrlHandler(protocol);
-            }
-            URL bundleUrl = new URL(bundleLocation);
-            is = new BufferedInputStream(bundleUrl.openStream());
-        } catch (RuntimeException e) {
-            LOGGER.error(e.getMessage());
-            throw e;
-        }
-        return is;
-    }
-
-    /**
-     * Will wait for the {@link URLStreamHandlerService} service for the
-     * specified protocol to be registered.
-     *
-     * @param protocol
-     */
-    private void waitForUrlHandler(String protocol) {
-        try {
-            Filter filter = bundleContext.createFilter("(&(" + Constants.OBJECTCLASS + "=" + URLStreamHandlerService.class.getName() + ")(url.handler.protocol=" + protocol + "))");
-            if (filter == null) {
-                return;
-            }
-            ServiceTracker<URLStreamHandlerService, URLStreamHandlerService> urlHandlerTracker = new ServiceTracker<URLStreamHandlerService, URLStreamHandlerService>(bundleContext, filter, null);
-            try {
-                urlHandlerTracker.open();
-                urlHandlerTracker.waitForService(30000);
-            } catch (InterruptedException e) {
-                LOGGER.debug("Interrupted while waiting for URL handler for protocol {}.", protocol);
-            } finally {
-                urlHandlerTracker.close();
-            }
-        } catch (Exception ex) {
-            LOGGER.error("Error creating service tracker.", ex);
-        }
-    }
-
-    protected Set<Bundle> findBundlesToRefresh(Set<Bundle> existing, Set<Bundle> installed) {
-        Set<Bundle> bundles = new HashSet<Bundle>();
-        bundles.addAll(findBundlesWithOptionalPackagesToRefresh(existing, installed));
-        bundles.addAll(findBundlesWithFragmentsToRefresh(existing, installed));
-        return bundles;
-    }
-
-    protected Set<Bundle> findBundlesWithFragmentsToRefresh(Set<Bundle> existing, Set<Bundle> installed) {
-        Set<Bundle> bundles = new HashSet<Bundle>();
-        Set<Bundle> oldBundles = new HashSet<Bundle>(existing);
-        oldBundles.removeAll(installed);
-        if (!oldBundles.isEmpty()) {
-            for (Bundle b : installed) {
-                String hostHeader = (String) b.getHeaders().get(Constants.FRAGMENT_HOST);
-                if (hostHeader != null) {
-                    Clause[] clauses = Parser.parseHeader(hostHeader);
-                    if (clauses != null && clauses.length > 0) {
-                        Clause path = clauses[0];
-                        for (Bundle hostBundle : oldBundles) {
-                            if (hostBundle.getSymbolicName().equals(path.getName())) {
-                                String ver = path.getAttribute(Constants.BUNDLE_VERSION_ATTRIBUTE);
-                                if (ver != null) {
-                                    VersionRange v = VersionRange.parseVersionRange(ver);
-                                    if (v.contains(hostBundle.getVersion())) {
-                                        bundles.add(hostBundle);
-                                    }
-                                } else {
-                                    bundles.add(hostBundle);
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-        }
-        return bundles;
-    }
-
-    protected Set<Bundle> findBundlesWithOptionalPackagesToRefresh(Set<Bundle> existing, Set<Bundle> installed) {
-        // First pass: include all bundles contained in these features
-        Set<Bundle> bundles = new HashSet<Bundle>(existing);
-        bundles.removeAll(installed);
-        if (bundles.isEmpty()) {
-            return bundles;
-        }
-        // Second pass: for each bundle, check if there is any unresolved
-        // optional package that could be resolved
-        Map<Bundle, List<Clause>> imports = new HashMap<Bundle, List<Clause>>();
-        for (Iterator<Bundle> it = bundles.iterator(); it.hasNext(); ) {
-            Bundle b = it.next();
-            String importsStr = (String) b.getHeaders().get(
-                    Constants.IMPORT_PACKAGE);
-            List<Clause> importsList = getOptionalImports(importsStr);
-            if (importsList.isEmpty()) {
-                it.remove();
-            } else {
-                imports.put(b, importsList);
-            }
-        }
-        if (bundles.isEmpty()) {
-            return bundles;
-        }
-        // Third pass: compute a list of packages that are exported by our
-        // bundles and see if
-        // some exported packages can be wired to the optional imports
-        List<Clause> exports = new ArrayList<Clause>();
-        for (Bundle b : installed) {
-            String exportsStr = (String) b.getHeaders().get(
-                    Constants.EXPORT_PACKAGE);
-            if (exportsStr != null) {
-                Clause[] exportsList = Parser.parseHeader(exportsStr);
-                exports.addAll(Arrays.asList(exportsList));
-            }
-        }
-        for (Iterator<Bundle> it = bundles.iterator(); it.hasNext(); ) {
-            Bundle b = it.next();
-            List<Clause> importsList = imports.get(b);
-            for (Iterator<Clause> itpi = importsList.iterator(); itpi.hasNext(); ) {
-                Clause pi = itpi.next();
-                boolean matching = false;
-                for (Clause pe : exports) {
-                    if (pi.getName().equals(pe.getName())) {
-                        String evStr = pe
-                                .getAttribute(Constants.VERSION_ATTRIBUTE);
-                        String ivStr = pi
-                                .getAttribute(Constants.VERSION_ATTRIBUTE);
-                        Version exported = evStr != null ? Version
-                                .parseVersion(evStr) : Version.emptyVersion;
-                        VersionRange imported = ivStr != null ? VersionRange
-                                .parseVersionRange(ivStr)
-                                : VersionRange.ANY_VERSION;
-                        if (imported.contains(exported)) {
-                            matching = true;
-                            break;
-                        }
-                    }
-                }
-                if (!matching) {
-                    itpi.remove();
-                }
-            }
-            if (importsList.isEmpty()) {
-                it.remove();
-            } else {
-                LOGGER.debug(
-                        "Refeshing bundle {} ({}) to solve the following optional imports",
-                        b.getSymbolicName(), b.getBundleId());
-                for (Clause p : importsList) {
-                    LOGGER.debug("    {}", p);
-                }
-
-            }
-        }
-        return bundles;
-    }
-
-    /*
-     * Get the list of optional imports from an OSGi Import-Package string
-     */
-    protected List<Clause> getOptionalImports(String importsStr) {
-        Clause[] imports = Parser.parseHeader(importsStr);
-        List<Clause> result = new LinkedList<Clause>();
-        for (Clause anImport : imports) {
-            String resolution = anImport
-                    .getDirective(Constants.RESOLUTION_DIRECTIVE);
-            if (Constants.RESOLUTION_OPTIONAL.equals(resolution)) {
-                result.add(anImport);
-            }
-        }
-        return result;
-    }
-
-    protected void refreshPackages(Collection<Bundle> bundles) {
-        final Object refreshLock = new Object();
-        FrameworkWiring wiring = bundleContext.getBundle().adapt(FrameworkWiring.class);
-        if (wiring != null) {
-            synchronized (refreshLock) {
-                wiring.refreshBundles(bundles, new FrameworkListener() {
-                    public void frameworkEvent(FrameworkEvent event) {
-                        if (event.getType() == FrameworkEvent.PACKAGES_REFRESHED) {
-                            synchronized (refreshLock) {
-                                refreshLock.notifyAll();
-                            }
-                        }
-                    }
-                });
-                try {
-                    refreshLock.wait(refreshTimeout);
-                } catch (InterruptedException e) {
-                    LOGGER.warn(e.getMessage(), e);
-                }
-            }
-        }
-    }
-
-    public void uninstall(Set<Bundle> bundles) {
-        uninstall(bundles, true);
-    }
-
-    public void uninstall(Set<Bundle> bundles, boolean refresh) {
-        for (Bundle b : bundles) {
-            try {
-                b.uninstall();
-            } catch (Exception e2) {
-                // Ignore
-            }
-        }
-        if (refresh) {
-            refreshPackages(null);
-        }
-    }
-
-    public void uninstall(List<Bundle> bundles) {
-        uninstall(bundles, true);
-    }
-
-    public void uninstall(List<Bundle> bundles, boolean refresh) {
-        for (Bundle b : bundles) {
-            try {
-                b.uninstall();
-            } catch (Exception e2) {
-                // Ignore
-            }
-        }
-        if (refresh) {
-            refreshPackages(null);
-        }
-    }
-
-    public void uninstallById(Set<Long> bundles) throws BundleException, InterruptedException {
-        uninstallById(bundles, true);
-    }
-
-    public void uninstallById(Set<Long> bundles, boolean refresh) throws BundleException,
-            InterruptedException {
-        for (long bundleId : bundles) {
-            Bundle b = bundleContext.getBundle(bundleId);
-            if (b != null) {
-                b.uninstall();
-            }
-        }
-        if (refresh) {
-            refreshPackages(null);
-        }
-    }
-
-    public File getDataFile(String fileName) {
-        return bundleContext.getDataFile(fileName);
-    }
-
-    EventAdminListener createAndRegisterEventAdminListener() {
-        EventAdminListener listener = null;
-        try {
-            getClass().getClassLoader().loadClass(
-                    "org.osgi.service.event.EventAdmin");
-            listener = new EventAdminListener(bundleContext);
-        } catch (Throwable t) {
-            // Ignore, if the EventAdmin package is not available, just don't
-            // use it
-            LOGGER.debug("EventAdmin package is not available, just don't use it");
-        }
-        return listener;
-    }
-
-    @SuppressWarnings({"rawtypes", "unchecked"})
-    public ServiceTracker createServiceTrackerForResolverName(String resolver)
-            throws InvalidSyntaxException {
-        String filter = "(&(" + Constants.OBJECTCLASS + "="
-                + Resolver.class.getName() + ")(name=" + resolver + "))";
-        return new ServiceTracker(bundleContext,
-                FrameworkUtil.createFilter(filter), null);
-    }
-
-    public void refreshBundles(Set<Bundle> existing, Set<Bundle> installed,
-                               EnumSet<Option> options) {
-        boolean print = options.contains(Option.PrintBundlesToRefresh);
-        boolean refresh = !options.contains(Option.NoAutoRefreshBundles);
-        if (print || refresh) {
-            Set<Bundle> bundlesToRefresh = findBundlesToRefresh(existing,
-                    installed);
-            StringBuilder sb = new StringBuilder();
-            for (Bundle b : bundlesToRefresh) {
-                if (sb.length() > 0) {
-                    sb.append(", ");
-                }
-                sb.append(b.getSymbolicName()).append(" (")
-                        .append(b.getBundleId()).append(")");
-            }
-            LOGGER.debug("Bundles to refresh: {}", sb.toString());
-            if (!bundlesToRefresh.isEmpty()) {
-                if (print) {
-                    if (refresh) {
-                        System.out.println("Refreshing bundles "
-                                + sb.toString());
-                    } else {
-                        System.out
-                                .println("The following bundles may need to be refreshed: "
-                                        + sb.toString());
-                    }
-                }
-                if (refresh) {
-                    LOGGER.debug("Refreshing bundles: {}", sb.toString());
-                    refreshPackages(bundlesToRefresh);
-                }
-            }
-        }
-    }
-
-    public BundleContext getBundleContext() {
-        return this.bundleContext;
-    }
-
-    public static class BundleInstallerResult {
-        Bundle bundle;
-        boolean isNew;
-
-        public BundleInstallerResult(Bundle bundle, boolean isNew) {
-            super();
-            this.bundle = bundle;
-            this.isNew = isNew;
-        }
-
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/internal/EventAdminListener.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/EventAdminListener.java b/features/core/src/main/java/org/apache/karaf/features/internal/EventAdminListener.java
deleted file mode 100644
index d463282..0000000
--- a/features/core/src/main/java/org/apache/karaf/features/internal/EventAdminListener.java
+++ /dev/null
@@ -1,92 +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;
-
-import java.util.Dictionary;
-import java.util.Hashtable;
-
-import org.apache.karaf.features.EventConstants;
-import org.apache.karaf.features.FeatureEvent;
-import org.apache.karaf.features.FeaturesListener;
-import org.apache.karaf.features.RepositoryEvent;
-import org.osgi.framework.BundleContext;
-import org.osgi.service.event.Event;
-import org.osgi.service.event.EventAdmin;
-import org.osgi.util.tracker.ServiceTracker;
-
-/**
- * A listener to publish events to EventAdmin
- */
-public class EventAdminListener implements FeaturesListener {
-
-    private final ServiceTracker<EventAdmin, EventAdmin> tracker;
-
-    public EventAdminListener(BundleContext context) {
-        tracker = new ServiceTracker<EventAdmin, EventAdmin>(context, EventAdmin.class.getName(), null);
-        tracker.open();
-    }
-
-    public void featureEvent(FeatureEvent event) {
-        EventAdmin eventAdmin = tracker.getService();
-        if (eventAdmin == null) {
-            return;
-        }
-        Dictionary<String, Object> props = new Hashtable<String, Object>();
-        props.put(EventConstants.TYPE, event.getType());
-        props.put(EventConstants.EVENT, event);
-        props.put(EventConstants.TIMESTAMP, System.currentTimeMillis());
-        props.put(EventConstants.FEATURE_NAME, event.getFeature().getName());
-        props.put(EventConstants.FEATURE_VERSION, event.getFeature().getVersion());
-        String topic;
-        switch (event.getType()) {
-            case FeatureInstalled:
-                topic = EventConstants.TOPIC_FEATURES_INSTALLED;
-                break;
-            case FeatureUninstalled:
-                topic = EventConstants.TOPIC_FEATURES_UNINSTALLED;
-                break;
-            default:
-                throw new IllegalStateException("Unknown features event type: " + event.getType());
-        }
-        eventAdmin.postEvent(new Event(topic, props));
-    }
-
-    public void repositoryEvent(RepositoryEvent event) {
-        EventAdmin eventAdmin = tracker.getService();
-        if (eventAdmin == null) {
-            return;
-        }
-        Dictionary<String, Object> props = new Hashtable<String, Object>();
-        props.put(EventConstants.TYPE, event.getType());
-        props.put(EventConstants.EVENT, event);
-        props.put(EventConstants.TIMESTAMP, System.currentTimeMillis());
-        props.put(EventConstants.REPOSITORY_NAME, event.getRepository().getName());
-        props.put(EventConstants.REPOSITORY_URI, event.getRepository().getURI().toString());
-        String topic;
-        switch (event.getType()) {
-            case RepositoryAdded:
-                topic = EventConstants.TOPIC_REPOSITORY_ADDED;
-                break;
-            case RepositoryRemoved:
-                topic = EventConstants.TOPIC_REPOSITORY_REMOVED;
-                break;
-            default:
-                throw new IllegalStateException("Unknown repository event type: " + event.getType());
-        }
-        eventAdmin.postEvent(new Event(topic, props));
-    }
-}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/internal/FeatureConfigInstaller.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/FeatureConfigInstaller.java b/features/core/src/main/java/org/apache/karaf/features/internal/FeatureConfigInstaller.java
deleted file mode 100644
index 5946a3a..0000000
--- a/features/core/src/main/java/org/apache/karaf/features/internal/FeatureConfigInstaller.java
+++ /dev/null
@@ -1,166 +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;
-
-import java.io.BufferedInputStream;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.Dictionary;
-import java.util.Hashtable;
-
-import org.apache.karaf.features.ConfigFileInfo;
-import org.apache.karaf.features.Feature;
-import org.osgi.framework.Constants;
-import org.osgi.framework.InvalidSyntaxException;
-import org.osgi.service.cm.Configuration;
-import org.osgi.service.cm.ConfigurationAdmin;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class FeatureConfigInstaller {
-	private static final Logger LOGGER = LoggerFactory.getLogger(FeaturesServiceImpl.class);
-    private static final String CONFIG_KEY = "org.apache.karaf.features.configKey";
-
-    private final ConfigurationAdmin configAdmin;
-    
-    public FeatureConfigInstaller(ConfigurationAdmin configAdmin) {
-		this.configAdmin = configAdmin;
-	}
-
-    private String[] parsePid(String pid) {
-        int n = pid.indexOf('-');
-        if (n > 0) {
-            String factoryPid = pid.substring(n + 1);
-            pid = pid.substring(0, n);
-            return new String[]{pid, factoryPid};
-        } else {
-            return new String[]{pid, null};
-        }
-    }
-
-    private Configuration createConfiguration(ConfigurationAdmin configurationAdmin,
-                                                String pid, String factoryPid) throws IOException, InvalidSyntaxException {
-        if (factoryPid != null) {
-            return configurationAdmin.createFactoryConfiguration(factoryPid, null);
-        } else {
-            return configurationAdmin.getConfiguration(pid, null);
-        }
-    }
-
-    private Configuration findExistingConfiguration(ConfigurationAdmin configurationAdmin,
-                                                      String pid, String factoryPid) throws IOException, InvalidSyntaxException {
-        String filter;
-        if (factoryPid == null) {
-            filter = "(" + Constants.SERVICE_PID + "=" + pid + ")";
-        } else {
-            String key = createConfigurationKey(pid, factoryPid);
-            filter = "(" + CONFIG_KEY + "=" + key + ")";
-        }
-        Configuration[] configurations = configurationAdmin.listConfigurations(filter);
-        if (configurations != null && configurations.length > 0) {
-            return configurations[0];
-        }
-        return null;
-    }
-
-    void installFeatureConfigs(Feature feature, boolean verbose) throws IOException, InvalidSyntaxException {
-        for (String config : feature.getConfigurations().keySet()) {
-            Dictionary<String,String> props = new Hashtable<String, String>(feature.getConfigurations().get(config));
-            String[] pid = parsePid(config);
-            Configuration cfg = findExistingConfiguration(configAdmin, pid[0], pid[1]);
-            if (cfg == null) {
-                cfg = createConfiguration(configAdmin, pid[0], pid[1]);
-                String key = createConfigurationKey(pid[0], pid[1]);
-                props.put(CONFIG_KEY, key);
-                if (cfg.getBundleLocation() != null) {
-                    cfg.setBundleLocation(null);
-                }
-                cfg.update(props);
-            }
-        }
-        for (ConfigFileInfo configFile : feature.getConfigurationFiles()) {
-            installConfigurationFile(configFile.getLocation(), configFile.getFinalname(), configFile.isOverride(), verbose);
-        }
-    }
-
-    private String createConfigurationKey(String pid, String factoryPid) {
-        return factoryPid == null ? pid : pid + "-" + factoryPid;
-    }
-
-    private void installConfigurationFile(String fileLocation, String finalname, boolean override, boolean verbose) throws IOException {
-    	LOGGER.debug("Checking configuration file " + fileLocation);
-        if (verbose) {
-            System.out.println("Checking configuration file " + fileLocation);
-        }
-    	
-    	String basePath = System.getProperty("karaf.base");
-    	
-    	if (finalname.indexOf("${") != -1) {
-    		//remove any placeholder or variable part, this is not valid.
-    		int marker = finalname.indexOf("}");
-    		finalname = finalname.substring(marker+1);
-    	}
-    	
-    	finalname = basePath + File.separator + finalname;
-    	
-    	File file = new File(finalname); 
-    	if (file.exists() && !override) {
-    		LOGGER.debug("configFile already exist, don't override it");
-    		return;
-    	}
-
-        InputStream is = null;
-        FileOutputStream fop = null;
-        try {
-            is = new BufferedInputStream(new URL(fileLocation).openStream());
-
-            if (!file.exists()) {
-                File parentFile = file.getParentFile();
-                if (parentFile != null)
-                    parentFile.mkdirs();
-                file.createNewFile();
-            }
-
-            fop = new FileOutputStream(file);
-        
-            int bytesRead;
-            byte[] buffer = new byte[1024];
-            
-            while ((bytesRead = is.read(buffer)) != -1) {
-                fop.write(buffer, 0, bytesRead);
-            }
-        } catch (RuntimeException e) {
-            LOGGER.error(e.getMessage());
-            throw e;
-        } catch (MalformedURLException e) {
-        	LOGGER.error(e.getMessage());
-            throw e;
-		} finally {
-			if (is != null)
-				is.close();
-            if (fop != null) {
-			    fop.flush();
-			    fop.close();
-            }
-		}
-            
-    }
-}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/internal/FeatureFinder.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/FeatureFinder.java b/features/core/src/main/java/org/apache/karaf/features/internal/FeatureFinder.java
deleted file mode 100644
index d3215fa..0000000
--- a/features/core/src/main/java/org/apache/karaf/features/internal/FeatureFinder.java
+++ /dev/null
@@ -1,59 +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;
-
-import java.net.URI;
-import java.util.Dictionary;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.Map;
-
-import org.osgi.service.cm.ConfigurationException;
-import org.osgi.service.cm.ManagedService;
-
-public class FeatureFinder implements ManagedService {
-
-    Map<String, String> nameToArtifactMap = new HashMap<String, String>();
-
-    public String[] getNames() {
-        return nameToArtifactMap.keySet().toArray(new String[] {});
-    }
-
-    public URI getUriFor(String name, String version) {
-        String coords = nameToArtifactMap.get(name);
-        if (coords == null) {
-            return null;
-        }
-        Artifact artifact = new Artifact(coords);
-        return artifact.getPaxUrlForArtifact(version);
-    }
-
-    @SuppressWarnings("rawtypes")
-    public void updated(Dictionary properties) throws ConfigurationException {
-        if (properties != null) {
-            nameToArtifactMap.clear();
-            Enumeration keys = properties.keys();
-            while (keys.hasMoreElements()) {
-                String key = (String)keys.nextElement();
-                if (!"felix.fileinstall.filename".equals(key) && !"service.pid".equals(key)) {
-                    nameToArtifactMap.put(key, (String)properties.get(key));
-                }
-            }
-        }
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/internal/FeatureValidationUtil.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/FeatureValidationUtil.java b/features/core/src/main/java/org/apache/karaf/features/internal/FeatureValidationUtil.java
deleted file mode 100644
index dfb7a7d..0000000
--- a/features/core/src/main/java/org/apache/karaf/features/internal/FeatureValidationUtil.java
+++ /dev/null
@@ -1,110 +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;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URI;
-import java.net.URLConnection;
-import javax.xml.XMLConstants;
-import javax.xml.namespace.QName;
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.transform.dom.DOMSource;
-import javax.xml.transform.stream.StreamSource;
-import javax.xml.validation.Schema;
-import javax.xml.validation.SchemaFactory;
-import javax.xml.validation.Validator;
-
-import org.apache.karaf.features.FeaturesNamespaces;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.w3c.dom.Document;
-import org.xml.sax.SAXException;
-
-/**
- * Utility class which fires XML Schema validation.
- */
-public class FeatureValidationUtil {
-
-    public static final QName FEATURES_0_0 = new QName("features");
-    public static final QName FEATURES_1_0 = new QName("http://karaf.apache.org/xmlns/features/v1.0.0", "features");
-    public static final QName FEATURES_1_1 = new QName("http://karaf.apache.org/xmlns/features/v1.1.0", "features");
-    public static final QName FEATURES_1_2 = new QName("http://karaf.apache.org/xmlns/features/v1.2.0", "features");
-    private static final Logger LOGGER = LoggerFactory.getLogger(FeatureValidationUtil.class);
-
-    /**
-     * Runs schema validation.
-     * 
-     * @param uri Uri to validate.
-     * @throws Exception When validation fails.
-     */
-    public static void validate(URI uri) throws Exception {
-        Document doc = load(uri);
-
-        QName name = new QName(doc.getDocumentElement().getNamespaceURI(), doc.getDocumentElement().getLocalName());
-
-        if (FeaturesNamespaces.FEATURES_0_0_0.equals(name)) {
-            LOGGER.warn("Old style feature file without namespace found (URI: {}). This format is deprecated and support for it will soon be removed", uri);
-            return;
-        } else if (FeaturesNamespaces.FEATURES_1_0_0.equals(name)) {
-            validate(doc, "/org/apache/karaf/features/karaf-features-1.0.0.xsd");
-        } else if (FeaturesNamespaces.FEATURES_1_1_0.equals(name)) {
-            validate(doc, "/org/apache/karaf/features/karaf-features-1.1.0.xsd");
-        } else if (FeaturesNamespaces.FEATURES_1_2_0.equals(name)) {
-            validate(doc, "/org/apache/karaf/features/karaf-features-1.2.0.xsd");
-        }
-        else {
-            throw new IllegalArgumentException("Unrecognized root element: " + name);
-        }
-    }
-
-    private static Document load(URI uri) throws IOException, SAXException, ParserConfigurationException {
-        InputStream stream = null;
-        try {
-            URLConnection conn;
-            try {
-                conn = uri.toURL().openConnection();
-            } catch (IllegalArgumentException e) {
-                throw new IllegalArgumentException("invalid URI: " + uri, e);
-            }
-            conn.setDefaultUseCaches(false);
-            stream = conn.getInputStream();
-            // load document and check the root element for namespace declaration
-            DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
-            dFactory.setNamespaceAware(true);
-            return dFactory.newDocumentBuilder().parse(stream);
-        } finally {
-            if (stream != null) {
-                stream.close();
-            }
-        }
-    }
-
-    private static void validate(Document doc, String schemaLocation) throws SAXException {
-        SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
-        // root element has namespace - we can use schema validation
-        Schema schema = factory.newSchema(new StreamSource(FeatureValidationUtil.class.getResourceAsStream(schemaLocation)));
-        // create schema by reading it from an XSD file:
-        Validator validator = schema.newValidator();
-        try {
-            validator.validate(new DOMSource(doc));
-        } catch (Exception e) {
-            throw new IllegalArgumentException("Unable to validate " + doc.getDocumentURI(), e);
-        }
-    }
-
-}


[06/13] [KARAF-2888] New FeaturesService based on the real OSGi resolver

Posted by gn...@apache.org.
http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/test/java/org/apache/karaf/features/FeaturesServiceTest.java
----------------------------------------------------------------------
diff --git a/features/core/src/test/java/org/apache/karaf/features/FeaturesServiceTest.java b/features/core/src/test/java/org/apache/karaf/features/FeaturesServiceTest.java
index bce3735..db7a4fc 100644
--- a/features/core/src/test/java/org/apache/karaf/features/FeaturesServiceTest.java
+++ b/features/core/src/test/java/org/apache/karaf/features/FeaturesServiceTest.java
@@ -30,28 +30,25 @@ import static org.junit.Assert.fail;
 import java.io.File;
 import java.io.FileWriter;
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
 import java.io.PrintWriter;
 import java.net.MalformedURLException;
 import java.net.URI;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.EnumSet;
 import java.util.List;
 import java.util.concurrent.CopyOnWriteArraySet;
 
-import org.apache.karaf.features.internal.BundleManager;
-import org.apache.karaf.features.internal.BundleManager.BundleInstallerResult;
-import org.apache.karaf.features.internal.FeaturesServiceImpl;
-import org.apache.karaf.features.internal.TestBase;
+import org.apache.karaf.features.internal.service.FeaturesServiceImpl;
+import org.apache.karaf.features.internal.service.StateStorage;
 import org.easymock.EasyMock;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
-import org.osgi.framework.BundleException;
-import org.osgi.service.packageadmin.PackageAdmin;
 
 public class FeaturesServiceTest extends TestBase {
     private static final String FEATURE_WITH_INVALID_BUNDLE = "<features name='test' xmlns='http://karaf.apache.org/xmlns/features/v1.0.0'>"
@@ -74,6 +71,9 @@ public class FeaturesServiceTest extends TestBase {
         return tmp.toURI();
     }
 
+    /*
+       TODO: migrate those tests
+
     @Test
     public void testInstallFeature() throws Exception {
         URI uri = createTempRepo(
@@ -322,27 +322,20 @@ public class FeaturesServiceTest extends TestBase {
                 + "  <feature name='f2' version='0.2'><bundle>%s</bundle></feature>"
                 + "</features>", bundleVer01Uri, bundleVer02Uri);
         
-        BundleManager bundleManager = EasyMock.createMock(BundleManager.class);
         BundleContext bundleContext = EasyMock.createMock(BundleContext.class);
-        Bundle bundleVer01 = createDummyBundle(12345L, "bundleVer01", headers());
-        expect(bundleManager.getDataFile(EasyMock.<String>anyObject())).andReturn(dataFile).anyTimes();
-        expect(bundleManager.installBundleIfNeeded(bundleVer01Uri, 0, null)).andReturn(new BundleInstallerResult(bundleVer01, true));
-        expect(bundleManager.installBundleIfNeeded(bundleVer01Uri, 0, null)).andReturn(new BundleInstallerResult(bundleVer01, false));
-        expect(bundleManager.getBundleContext()).andReturn(bundleContext);
-        ignoreRefreshes(bundleManager);
-        bundleManager.uninstall(Collections.EMPTY_LIST, true);
-        
-        EasyMock.expectLastCall().times(2);
+        expect(bundleContext.getBundles()).andReturn(new Bundle[0]);
+        replay(bundleContext);
 
-        replay(bundleManager);
-        FeaturesServiceImpl svc = new FeaturesServiceImpl(bundleManager);
+        FeaturesServiceImpl svc = new FeaturesServiceImpl(null, bundleContext, new Storage(), null, null, null, null);
         svc.addRepository(uri);
         svc.installFeature("f2", "0.1");
         svc.installFeature("f1", "0.1");
         svc.uninstallFeature("f1", "0.1");
         svc.uninstallFeature("f2", "0.1");
-        verify(bundleManager);
+
+        verify(bundleContext);
     }
+    */
 
     @Test
     public void testGetFeaturesShouldHandleDifferentVersionPatterns() throws Exception {
@@ -351,14 +344,9 @@ public class FeaturesServiceTest extends TestBase {
                 + "  <feature name='f2' version='0.1'><bundle>bundle1</bundle></feature>"
                 + "  <feature name='f2' version='0.2'><bundle>bundle2</bundle></feature>"
                 + "</features>");
-        
-        BundleManager bundleManager = EasyMock.createMock(BundleManager.class);
-        expect(bundleManager.getDataFile(EasyMock.<String>anyObject())).andReturn(dataFile).anyTimes();
-        
-        replay(bundleManager);
-        FeaturesServiceImpl svc = new FeaturesServiceImpl(bundleManager);
+
+        FeaturesServiceImpl svc = new FeaturesServiceImpl(null, null, new Storage(), null, null, null, null);
         svc.addRepository(uri);
-        verify(bundleManager);
 
         assertEquals(feature("f2", "0.2"), svc.getFeature("f2", "[0.1,0.3)"));
         assertEquals(feature("f2", "0.2"), svc.getFeature("f2", "0.0.0"));
@@ -367,104 +355,17 @@ public class FeaturesServiceTest extends TestBase {
     }
 
     @Test
-    public void testInstallBatchFeatureWithContinueOnFailureNoClean() throws Exception {
-        String bundle1Uri = "bundle1";
-        String bundle2Uri = "bundle2";
-
-        URI uri = createTempRepo(FEATURE_WITH_INVALID_BUNDLE, bundle1Uri, bundle2Uri);
-        
-        BundleManager bundleManager = EasyMock.createMock(BundleManager.class);
-        Bundle installedBundle1 = createDummyBundle(12345L, "bundle1", headers());
-        Bundle installedBundle2 = createDummyBundle(54321L, "bundle2", headers());
-        expect(bundleManager.getDataFile(EasyMock.<String>anyObject())).andReturn(dataFile).anyTimes();
-        expect(bundleManager.installBundleIfNeeded(bundle1Uri, 0, null)).andReturn(new BundleInstallerResult(installedBundle1, true));
-        expect(bundleManager.installBundleIfNeeded(bundle2Uri, 0, null)).andReturn(new BundleInstallerResult(installedBundle2, true));
-        expect(bundleManager.installBundleIfNeeded("zfs:unknown", 0, null)).andThrow(new MalformedURLException());
-        EasyMock.expectLastCall();
-        ignoreRefreshes(bundleManager);
-
-        replay(bundleManager);
-        FeaturesServiceImpl svc = new FeaturesServiceImpl(bundleManager);
-        svc.addRepository(uri);
-        svc.installFeatures(new CopyOnWriteArraySet<Feature>(Arrays.asList(svc.listFeatures())),
-                            EnumSet.of(FeaturesService.Option.ContinueBatchOnFailure, FeaturesService.Option.NoCleanIfFailure));
-        verify(bundleManager);
-    }
-    
-    @Test
-    public void testInstallBatchFeatureWithContinueOnFailureClean() throws Exception {
-        String bundle1Uri = "file:bundle1";
-        String bundle2Uri = "file:bundle2";
-
-        URI uri = createTempRepo(FEATURE_WITH_INVALID_BUNDLE, bundle1Uri, bundle2Uri);
-
-        BundleManager bundleManager = EasyMock.createMock(BundleManager.class);
-        Bundle installedBundle1 = createDummyBundle(12345L, "bundle1", headers());
-        Bundle installedBundle2 = createDummyBundle(54321L, "bundle2", headers());
-        expect(bundleManager.getDataFile(EasyMock.<String>anyObject())).andReturn(dataFile).anyTimes();
-        expect(bundleManager.installBundleIfNeeded(bundle1Uri, 0, null)).andReturn(new BundleInstallerResult(installedBundle1, true));
-        expect(bundleManager.installBundleIfNeeded(bundle2Uri, 0, null)).andReturn(new BundleInstallerResult(installedBundle2, true));
-        expect(bundleManager.installBundleIfNeeded("zfs:unknown", 0, null)).andThrow(new MalformedURLException());
-        bundleManager.uninstall(setOf(installedBundle1));
-        EasyMock.expectLastCall();
-        ignoreRefreshes(bundleManager);
-
-        replay(bundleManager);
-        FeaturesServiceImpl svc = new FeaturesServiceImpl(bundleManager);
-        svc.addRepository(uri);
-        svc.installFeatures(new CopyOnWriteArraySet<Feature>(Arrays.asList(svc.listFeatures())),
-                            EnumSet.of(FeaturesService.Option.ContinueBatchOnFailure));
-        verify(bundleManager);
-    }
-
-    @Test
-    public void testInstallBatchFeatureWithoutContinueOnFailureNoClean() throws Exception {
-        String bundle1Uri = "file:bundle1";
-        String bundle2Uri = "file:bundle2";
-
-        URI uri = createTempRepo(FEATURE_WITH_INVALID_BUNDLE, bundle1Uri, bundle2Uri);
-
-        BundleManager bundleManager = EasyMock.createMock(BundleManager.class);
-        Bundle installedBundle1 = createDummyBundle(12345L, bundle1Uri, headers());
-        Bundle installedBundle2 = createDummyBundle(54321L, bundle2Uri, headers());
-        expect(bundleManager.getDataFile(EasyMock.<String>anyObject())).andReturn(dataFile).anyTimes();
-        expect(bundleManager.installBundleIfNeeded(bundle1Uri, 0, null)).andReturn(new BundleInstallerResult(installedBundle1, true));
-        expect(bundleManager.installBundleIfNeeded(bundle2Uri, 0, null)).andReturn(new BundleInstallerResult(installedBundle2, true));
-        expect(bundleManager.installBundleIfNeeded("zfs:unknown", 0, null)).andThrow(new MalformedURLException());
-        
-        replay(bundleManager);
-        FeaturesServiceImpl svc = new FeaturesServiceImpl(bundleManager);
-        svc.addRepository(uri);
-        try {
-            List<Feature> features = Arrays.asList(svc.listFeatures());
-            Collections.reverse(features);
-            svc.installFeatures(new CopyOnWriteArraySet<Feature>(features),
-                                EnumSet.of(FeaturesService.Option.NoCleanIfFailure));
-            fail("Call should have thrown an exception");
-        } catch (MalformedURLException e) {
-        }
-        verify(bundleManager);
-    }
-
-    @Test
-    public void testInstallBatchFeatureWithoutContinueOnFailureClean() throws Exception {
+    public void testInstallBatchFeatureWithFailure() throws Exception {
         String bundle1Uri = "file:bundle1";
         String bundle2Uri = "file:bundle2";
 
         URI uri = createTempRepo(FEATURE_WITH_INVALID_BUNDLE, bundle1Uri, bundle2Uri);
         
-        BundleManager bundleManager = EasyMock.createMock(BundleManager.class);
-        Bundle installedBundle1 = createDummyBundle(12345L, "bundle1", headers());
-        Bundle installedBundle2 = createDummyBundle(54321L, "bundle2", headers());
-        expect(bundleManager.getDataFile(EasyMock.<String>anyObject())).andReturn(dataFile).anyTimes();
-        expect(bundleManager.installBundleIfNeeded(bundle1Uri, 0, null)).andReturn(new BundleInstallerResult(installedBundle1, true));
-        expect(bundleManager.installBundleIfNeeded(bundle2Uri, 0, null)).andReturn(new BundleInstallerResult(installedBundle2, true));
-        expect(bundleManager.installBundleIfNeeded("zfs:unknown", 0, null)).andThrow(new MalformedURLException());
-        bundleManager.uninstall(setOf(installedBundle1, installedBundle2));
-        EasyMock.expectLastCall();
+        BundleContext bundleContext = EasyMock.createMock(BundleContext.class);
+        expect(bundleContext.getBundles()).andReturn(new Bundle[0]);
+        replay(bundleContext);
 
-        replay(bundleManager);
-        FeaturesServiceImpl svc = new FeaturesServiceImpl(bundleManager);
+        FeaturesServiceImpl svc = new FeaturesServiceImpl(null, bundleContext, new Storage(), null, null, null, null);
         svc.addRepository(uri);
         try {
             List<Feature> features = Arrays.asList(svc.listFeatures());
@@ -474,7 +375,7 @@ public class FeaturesServiceTest extends TestBase {
             fail("Call should have thrown an exception");
         } catch (MalformedURLException e) {
         }
-        verify(bundleManager);
+        verify(bundleContext);
     }
 
     /**
@@ -485,18 +386,13 @@ public class FeaturesServiceTest extends TestBase {
         URI uri = createTempRepo("<features name='test' xmlns='http://karaf.apache.org/xmlns/features/v1.0.0'>"
                 + "  <featur><bundle>somebundle</bundle></featur></features>");
 
-        BundleManager bundleManager = EasyMock.createMock(BundleManager.class);
-        expect(bundleManager.getDataFile(EasyMock.<String>anyObject())).andReturn(dataFile).anyTimes();
-
-        replay(bundleManager);
-        FeaturesServiceImpl svc = new FeaturesServiceImpl(bundleManager);
+        FeaturesServiceImpl svc = new FeaturesServiceImpl(null, null, new Storage(), null, null, null, null);
         try {
             svc.addRepository(uri);
             fail("exception expected");
         } catch (Exception e) {
             assertTrue(e.getMessage().contains("Unable to validate"));
         }
-        verify(bundleManager);
     }
 
     /**
@@ -508,11 +404,7 @@ public class FeaturesServiceTest extends TestBase {
                 + "  <feature name='f1'><bundle>file:bundle1</bundle><bundle>file:bundle2</bundle></feature>"
                 + "</features>");
 
-        BundleManager bundleManager = EasyMock.createMock(BundleManager.class);
-        expect(bundleManager.getDataFile(EasyMock.<String>anyObject())).andReturn(dataFile).anyTimes();
-
-        replay(bundleManager);
-        FeaturesServiceImpl svc = new FeaturesServiceImpl(bundleManager);
+        FeaturesServiceImpl svc = new FeaturesServiceImpl(null, null, new Storage(), null, null, null, null);
         svc.addRepository(uri);
         Feature feature = svc.getFeature("f1");
         Assert.assertNotNull("No feature named fi found", feature);        
@@ -520,4 +412,15 @@ public class FeaturesServiceTest extends TestBase {
         Assert.assertEquals(2, bundles.size());
     }
 
+    static class Storage extends StateStorage {
+        @Override
+        protected InputStream getInputStream() throws IOException {
+            return null;
+        }
+        @Override
+        protected OutputStream getOutputStream() throws IOException {
+            return null;
+        }
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/test/java/org/apache/karaf/features/RepositoryTest.java
----------------------------------------------------------------------
diff --git a/features/core/src/test/java/org/apache/karaf/features/RepositoryTest.java b/features/core/src/test/java/org/apache/karaf/features/RepositoryTest.java
index cf97a20..164dc79 100644
--- a/features/core/src/test/java/org/apache/karaf/features/RepositoryTest.java
+++ b/features/core/src/test/java/org/apache/karaf/features/RepositoryTest.java
@@ -19,7 +19,9 @@ package org.apache.karaf.features;
 import java.net.URI;
 
 import junit.framework.TestCase;
-import org.apache.karaf.features.internal.RepositoryImpl;
+import org.apache.karaf.features.internal.resolver.FeatureResource;
+import org.apache.karaf.features.internal.service.RepositoryImpl;
+import org.osgi.resource.Resource;
 
 
 public class RepositoryTest extends TestCase {
@@ -107,7 +109,25 @@ public class RepositoryTest extends TestCase {
         assertEquals(true, features[2].getConfigurationFiles().get(0).isOverride());
         assertEquals("cfloc", features[2].getConfigurationFiles().get(0).getLocation());
     }
-    
+
+    public void testLoadRepoWithCapabilitiesAndRequirement() throws Exception {
+        RepositoryImpl r = new RepositoryImpl(getClass().getResource("repo3.xml").toURI());
+        // Check features
+        Feature[] features = r.getFeatures();
+        assertNotNull(features);
+        assertEquals(1, features.length);
+        assertNotNull(features[0]);
+        assertEquals("f1", features[0].getName());
+        assertEquals(1, features[0].getCapabilities().size());
+        assertEquals("cap", features[0].getCapabilities().get(0).getValue().trim());
+        assertEquals(1, features[0].getRequirements().size());
+        assertEquals("req", features[0].getRequirements().get(0).getValue().trim());
+
+        Resource res = FeatureResource.build(features[0], null, null);
+        assertEquals(1, res.getCapabilities("cap").size());
+        assertEquals(1, res.getRequirements("req").size());
+    }
+
     public void testShowWrongUriInException() throws Exception {
         String uri = "src/test/resources/org/apache/karaf/shell/features/repo1.xml";
         RepositoryImpl r = new RepositoryImpl(new URI(uri));

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/test/java/org/apache/karaf/features/TestBase.java
----------------------------------------------------------------------
diff --git a/features/core/src/test/java/org/apache/karaf/features/TestBase.java b/features/core/src/test/java/org/apache/karaf/features/TestBase.java
new file mode 100644
index 0000000..ac8f3d9
--- /dev/null
+++ b/features/core/src/test/java/org/apache/karaf/features/TestBase.java
@@ -0,0 +1,105 @@
+/*
+ * 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;
+
+import java.util.Arrays;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.Set;
+
+import org.easymock.EasyMock;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.startlevel.BundleStartLevel;
+
+import static java.util.Arrays.asList;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+
+public class TestBase {
+    public Bundle createDummyBundle(long id, final String symbolicName, Dictionary<String,String> headers) {
+        Bundle bundle = EasyMock.createNiceMock(Bundle.class);
+        
+        // Be aware that this means all bundles are treated as different
+        expect(bundle.compareTo(EasyMock.<Bundle>anyObject())).andReturn(1).anyTimes();
+
+        expect(bundle.getBundleId()).andReturn(id).anyTimes();
+        expect(bundle.getSymbolicName()).andReturn(symbolicName).anyTimes();
+        expect(bundle.getHeaders()).andReturn(headers).anyTimes();
+        BundleStartLevel sl = EasyMock.createMock(BundleStartLevel.class);
+        expect(sl.isPersistentlyStarted()).andReturn(true).anyTimes();
+        expect(bundle.adapt(BundleStartLevel.class)).andReturn(sl).anyTimes();
+        replay(bundle, sl);
+        return bundle;
+    }
+    
+    public Dictionary<String, String> headers(String ... keyAndHeader) {
+        Hashtable<String, String> headersTable = new Hashtable<String, String>();
+        int c=0;
+        while (c < keyAndHeader.length) {
+            String key = keyAndHeader[c++];
+            String value = keyAndHeader[c++];
+            headersTable.put(key, value);
+        }
+        return headersTable;
+    }
+    
+    public Map<String, Map<String, Feature>> features(Feature ... features) {
+        final Map<String, Map<String, Feature>> featuresMap = new HashMap<String, Map<String,Feature>>();
+        for (Feature feature : features) {
+            Map<String, Feature> featureVersion = getOrCreate(featuresMap, feature);
+            featureVersion.put(feature.getVersion(), feature);
+        }
+        return featuresMap;
+    }
+    
+    private Map<String, Feature> getOrCreate(final Map<String, Map<String, Feature>> featuresMap, Feature feature) {
+        Map<String, Feature> featureVersion = featuresMap.get(feature.getName());
+        if (featureVersion == null) {
+            featureVersion = new HashMap<String, Feature>();
+            featuresMap.put(feature.getName(), featureVersion);
+        }
+        return featureVersion;
+    }
+
+    public Feature feature(String name) {
+        return feature(name, null);
+    }
+
+    public Feature feature(String name, String version) {
+        return new org.apache.karaf.features.internal.model.Feature(name, version);
+    }
+    
+    public Set<Bundle> setOf(Bundle ... elements) {
+        return new HashSet<Bundle>(Arrays.asList(elements));
+    }
+    
+    public Set<Long> setOf(Long ... elements) {
+        return new HashSet<Long>(Arrays.asList(elements));
+    }
+    
+    public Set<String> setOf(String ... elements) {
+        return new HashSet<String>(asList(elements));
+    }
+    
+    public Set<Feature> setOf(Feature ... elements) {
+        return new HashSet<Feature>(Arrays.asList(elements));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/test/java/org/apache/karaf/features/internal/BootFeaturesInstallerTest.java
----------------------------------------------------------------------
diff --git a/features/core/src/test/java/org/apache/karaf/features/internal/BootFeaturesInstallerTest.java b/features/core/src/test/java/org/apache/karaf/features/internal/BootFeaturesInstallerTest.java
deleted file mode 100644
index b59806d..0000000
--- a/features/core/src/test/java/org/apache/karaf/features/internal/BootFeaturesInstallerTest.java
+++ /dev/null
@@ -1,103 +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;
-
-import static java.util.Arrays.asList;
-import static org.easymock.EasyMock.expect;
-import static org.easymock.EasyMock.replay;
-
-import java.util.EnumSet;
-
-import org.apache.karaf.features.Feature;
-import org.apache.karaf.features.FeaturesService.Option;
-import org.easymock.EasyMock;
-import org.junit.Assert;
-import org.junit.Test;
-
-public class BootFeaturesInstallerTest extends TestBase {
-
-    @Test
-    @SuppressWarnings("unchecked")
-    public void testParser() {
-        BootFeaturesInstaller installer = new BootFeaturesInstaller(null, null, "", false);
-        Assert.assertEquals(asList(setOf("test1", "test2"),setOf("test3")), installer.parseBootFeatures("(test1, test2), test3"));
-        Assert.assertEquals(asList(setOf("test1", "test2", "test3")), installer.parseBootFeatures("test1, test2, test3"));
-    }
-    
-    @Test
-    public void testDefaultBootFeatures() throws Exception  {
-        FeaturesServiceImpl impl = EasyMock.createMock(FeaturesServiceImpl.class);
-        Feature configFeature = feature("config", "1.0.0");
-        Feature standardFeature = feature("standard", "1.0.0");
-        Feature regionFeature = feature("region", "1.0.0");
-        expect(impl.listInstalledFeatures()).andStubReturn(new Feature[]{});
-        expect(impl.getFeature("config", "0.0.0")).andReturn(configFeature);
-        expect(impl.getFeature("standard", "0.0.0")).andReturn(standardFeature);
-        expect(impl.getFeature("region", "0.0.0")).andReturn(regionFeature);
-
-        impl.installFeatures(setOf(configFeature, standardFeature, regionFeature), EnumSet.of(Option.NoCleanIfFailure, Option.ContinueBatchOnFailure));
-        EasyMock.expectLastCall();
-        
-        replay(impl);
-        BootFeaturesInstaller bootFeatures = new BootFeaturesInstaller(null, impl, "config,standard,region", false);
-        bootFeatures.installBootFeatures();
-        EasyMock.verify(impl);        
-    }
-
-    /**
-     * This test checks KARAF-388 which allows you to specify version of boot feature.
-     * @throws Exception 
-     */
-    @Test
-    public void testStartDoesNotFailWithNonExistentVersion() throws Exception  {
-        FeaturesServiceImpl impl = EasyMock.createMock(FeaturesServiceImpl.class);
-        expect(impl.listInstalledFeatures()).andReturn(new Feature[]{});
-        EasyMock.expectLastCall();
-        Feature sshFeature = feature("ssh", "1.0.0");
-        expect(impl.getFeature("ssh", "1.0.0")).andReturn(sshFeature);
-        expect(impl.getFeature("transaction", "1.2")).andReturn(null);
-        
-        // Only the ssh feature should get installed
-        impl.installFeatures(setOf(sshFeature), EnumSet.of(Option.NoCleanIfFailure, Option.ContinueBatchOnFailure));
-        EasyMock.expectLastCall();
-        
-        replay(impl);
-        BootFeaturesInstaller bootFeatures = new BootFeaturesInstaller(null, impl , "transaction;version=1.2,ssh;version=1.0.0", false);
-        bootFeatures.installBootFeatures();
-        EasyMock.verify(impl);        
-    }
-    
-    @Test
-    public void testStagedBoot() throws Exception  {
-        FeaturesServiceImpl impl = EasyMock.createStrictMock(FeaturesServiceImpl.class);
-        Feature sshFeature = feature("ssh", "1.0.0");
-        Feature transactionFeature = feature("transaction", "2.0.0");
-        expect(impl.listInstalledFeatures()).andStubReturn(new Feature[]{});
-        expect(impl.getFeature("transaction", "0.0.0")).andStubReturn(transactionFeature);
-        expect(impl.getFeature("ssh", "0.0.0")).andStubReturn(sshFeature);
-
-        impl.installFeatures(setOf(transactionFeature), EnumSet.of(Option.NoCleanIfFailure, Option.ContinueBatchOnFailure));
-        EasyMock.expectLastCall();
-        impl.installFeatures(setOf(sshFeature), EnumSet.of(Option.NoCleanIfFailure, Option.ContinueBatchOnFailure));
-        EasyMock.expectLastCall();
-        
-        replay(impl);
-        BootFeaturesInstaller bootFeatures = new BootFeaturesInstaller(null, impl , "(transaction), ssh", false);
-        bootFeatures.installBootFeatures();
-        EasyMock.verify(impl);        
-    }
-}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/test/java/org/apache/karaf/features/internal/BundleManagerTest.java
----------------------------------------------------------------------
diff --git a/features/core/src/test/java/org/apache/karaf/features/internal/BundleManagerTest.java b/features/core/src/test/java/org/apache/karaf/features/internal/BundleManagerTest.java
deleted file mode 100644
index 0a7c6fe..0000000
--- a/features/core/src/test/java/org/apache/karaf/features/internal/BundleManagerTest.java
+++ /dev/null
@@ -1,74 +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;
-
-import static org.easymock.EasyMock.replay;
-
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Set;
-
-import junit.framework.Assert;
-
-import org.easymock.EasyMock;
-import org.junit.Test;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.Constants;
-
-public class BundleManagerTest extends TestBase {
-    
-    @Test
-    public void testfindBundlestoRefreshWithHostToRefresh() throws Exception {
-        Bundle hostBundle = createDummyBundle(12345l, "Host", headers());
-        Bundle fragmentBundle = createDummyBundle(54321l, "fragment", headers(Constants.FRAGMENT_HOST, "Host"));
-
-        BundleContext bundleContext = EasyMock.createMock(BundleContext.class);
-        BundleManager bundleManager = new BundleManager(bundleContext);
-
-        // Host was already installed, fragment is new
-        Set<Bundle> existing = new HashSet<Bundle>(Arrays.asList(hostBundle, fragmentBundle));
-        Set<Bundle> installed = new HashSet<Bundle>(Arrays.asList(fragmentBundle));
-        
-        replay(bundleContext);
-        Set<Bundle> bundles = bundleManager.findBundlesWithFragmentsToRefresh(existing, installed);
-        EasyMock.verify(bundleContext);
-
-        Assert.assertEquals(1, bundles.size());
-        Assert.assertEquals(hostBundle, bundles.iterator().next());
-    }
-    
-    @Test
-    public void testfindBundlestoRefreshWithOptionalPackages() throws Exception {
-        Bundle exporterBundle = createDummyBundle(12345l, "exporter", headers(Constants.EXPORT_PACKAGE, "org.my.package"));
-        Bundle importerBundle = createDummyBundle(54321l, "importer", headers(Constants.IMPORT_PACKAGE, "org.my.package;resolution:=optional"));
-
-        BundleContext bundleContext = EasyMock.createMock(BundleContext.class);
-        BundleManager bundleManager = new BundleManager(bundleContext);
-
-        // Importer was already installed, exporter is new
-        Set<Bundle> existing = new HashSet<Bundle>(Arrays.asList(importerBundle, exporterBundle));
-        Set<Bundle> installed = new HashSet<Bundle>(Arrays.asList(exporterBundle));
-        
-        replay(bundleContext);
-        Set<Bundle> bundles = bundleManager.findBundlesWithOptionalPackagesToRefresh(existing, installed);
-        EasyMock.verify(bundleContext);
-
-        Assert.assertEquals(1, bundles.size());
-        Assert.assertEquals(importerBundle, bundles.iterator().next());
-    }
-}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/test/java/org/apache/karaf/features/internal/FeaturesServiceImplTest.java
----------------------------------------------------------------------
diff --git a/features/core/src/test/java/org/apache/karaf/features/internal/FeaturesServiceImplTest.java b/features/core/src/test/java/org/apache/karaf/features/internal/FeaturesServiceImplTest.java
deleted file mode 100644
index 4776bf4..0000000
--- a/features/core/src/test/java/org/apache/karaf/features/internal/FeaturesServiceImplTest.java
+++ /dev/null
@@ -1,178 +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;
-
-import static org.easymock.EasyMock.expect;
-import static org.easymock.EasyMock.replay;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.fail;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.EnumSet;
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.felix.utils.manifest.Clause;
-import org.apache.karaf.features.Feature;
-import org.apache.karaf.features.internal.BundleManager.BundleInstallerResult;
-import org.easymock.EasyMock;
-import org.junit.Before;
-import org.junit.Test;
-
-/**
- * Test cases for {@link FeaturesServiceImpl}
- */
-public class FeaturesServiceImplTest extends TestBase {
-    
-    File dataFile;
-
-    @Before
-    public void setUp() throws IOException {
-        dataFile = File.createTempFile("features", null, null);
-    }
-
-    @Test
-    public void testGetFeature() throws Exception {
-        Feature transactionFeature = feature("transaction", "1.0.0");
-        final Map<String, Map<String, Feature>> features = features(transactionFeature);
-        final FeaturesServiceImpl impl = new FeaturesServiceImpl(null, null) {
-            protected Map<String,Map<String,Feature>> getFeatures() throws Exception {
-                return features;
-            };
-        };
-        assertNotNull(impl.getFeature("transaction", org.apache.karaf.features.internal.model.Feature.DEFAULT_VERSION));
-        assertSame(transactionFeature, impl.getFeature("transaction", org.apache.karaf.features.internal.model.Feature.DEFAULT_VERSION));
-    }
-    
-    @Test
-    public void testGetFeatureStripVersion() throws Exception {
-        final FeaturesServiceImpl impl = new FeaturesServiceImpl(null, null) {
-            protected Map<String,Map<String,Feature>> getFeatures() throws Exception {
-                return features(feature("transaction", "1.0.0"));
-            };
-        };
-        Feature feature = impl.getFeature("transaction", "  1.0.0  ");
-        assertNotNull(feature);
-        assertSame("transaction", feature.getName());
-    }
-    
-    @Test
-    public void testGetFeatureNotAvailable() throws Exception {
-        final FeaturesServiceImpl impl = new FeaturesServiceImpl(null, null) {
-            protected Map<String,Map<String,Feature>> getFeatures() throws Exception {
-                return features(feature("transaction", "1.0.0"));
-            };
-        };
-        assertNull(impl.getFeature("activemq", org.apache.karaf.features.internal.model.Feature.DEFAULT_VERSION));
-    }
-    
-    @Test
-    public void testGetFeatureHighestAvailable() throws Exception {
-        final Map<String, Map<String, Feature>> features = features(
-                feature("transaction", "1.0.0"),
-                feature("transaction", "2.0.0")
-        );
-        final FeaturesServiceImpl impl = new FeaturesServiceImpl(null, null) {
-            protected Map<String,Map<String,Feature>> getFeatures() throws Exception {
-                return features;
-            };
-        };
-        assertNotNull(impl.getFeature("transaction", org.apache.karaf.features.internal.model.Feature.DEFAULT_VERSION));
-        assertSame("2.0.0", impl.getFeature("transaction", org.apache.karaf.features.internal.model.Feature.DEFAULT_VERSION).getVersion());
-    }
-
-    @Test
-    public void testStartDoesNotFailWithOneInvalidUri()  {
-        BundleManager bundleManager = EasyMock.createMock(BundleManager.class);
-        expect(bundleManager.getDataFile(EasyMock.<String>anyObject())).andReturn(dataFile).anyTimes();
-        expect(bundleManager.createAndRegisterEventAdminListener()).andReturn(null);
-        replay(bundleManager);
-        FeaturesServiceImpl service = new FeaturesServiceImpl(bundleManager, null);
-        try {
-            service.setUrls("mvn:inexistent/features/1.0/xml/features");
-            service.start();
-        } catch (Exception e) {
-            fail(String.format("Service should not throw start-up exception but log the error instead: %s", e));
-        }
-    }
-
-
-    
-    /**
-     * This test ensures that every feature get installed only once, even if it appears multiple times in the list
-     * of transitive feature dependencies (KARAF-1600)
-     */
-    @Test
-    @SuppressWarnings("unchecked")
-    public void testNoDuplicateFeaturesInstallation() throws Exception {
-        final List<Feature> installed = new LinkedList<Feature>();
-        BundleManager bundleManager = EasyMock.createMock(BundleManager.class);
-        expect(bundleManager.installBundleIfNeeded(EasyMock.anyObject(String.class), EasyMock.anyInt(), EasyMock.anyObject(String.class)))
-            .andReturn(new BundleInstallerResult(createDummyBundle(1l, "", headers()), true)).anyTimes();
-        bundleManager.refreshBundles(EasyMock.anyObject(Set.class), EasyMock.anyObject(Set.class), EasyMock.anyObject(EnumSet.class));
-        EasyMock.expectLastCall();
-        final FeaturesServiceImpl impl = new FeaturesServiceImpl(bundleManager, null) {
-            // override methods which refers to bundle context to avoid mocking everything
-            @Override
-            protected boolean loadState() {
-                return true;
-            }
-
-            @Override
-            protected void saveState() {
-
-            }
-
-            @Override
-            protected void doInstallFeature(InstallationState state, Feature feature, boolean verbose) throws Exception {
-                installed.add(feature);
-
-                super.doInstallFeature(state, feature, verbose);
-            }
-
-        };
-        replay(bundleManager);
-        impl.addRepository(getClass().getResource("repo2.xml").toURI());
-        impl.installFeature("all");
-
-        // copying the features to a set to filter out the duplicates
-        Set<Feature> noduplicates = new HashSet<Feature>();
-        noduplicates.addAll(installed);
-
-        assertEquals("Every feature should only have been installed once", installed.size(), noduplicates.size());
-    }
-
-    @Test
-    public void testGetOptionalImportsOnly() {
-        BundleManager bundleManager = new BundleManager(null, 0l);
-
-        List<Clause> result = bundleManager.getOptionalImports("org.apache.karaf,org.apache.karaf.optional;resolution:=optional");
-        assertEquals("One optional import expected", 1, result.size());
-        assertEquals("org.apache.karaf.optional", result.get(0).getName());
-
-        result = bundleManager.getOptionalImports(null);
-        assertNotNull(result);
-        assertEquals("No optional imports expected", 0, result.size());
-    }
-}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/test/java/org/apache/karaf/features/internal/FeaturesValidationTest.java
----------------------------------------------------------------------
diff --git a/features/core/src/test/java/org/apache/karaf/features/internal/FeaturesValidationTest.java b/features/core/src/test/java/org/apache/karaf/features/internal/FeaturesValidationTest.java
deleted file mode 100644
index 09f9c62..0000000
--- a/features/core/src/test/java/org/apache/karaf/features/internal/FeaturesValidationTest.java
+++ /dev/null
@@ -1,60 +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;
-
-import org.junit.Test;
-
-import static org.junit.Assert.fail;
-
-public class FeaturesValidationTest {
-
-    @Test
-    public void testNoNs() throws Exception {
-        FeatureValidationUtil.validate(getClass().getResource("f01.xml").toURI());
-    }
-
-    @Test
-    public void testNs10() throws Exception {
-        FeatureValidationUtil.validate(getClass().getResource("f02.xml").toURI());
-    }
-
-    @Test
-    public void testNs10NoName() throws Exception {
-        FeatureValidationUtil.validate(getClass().getResource("f03.xml").toURI());
-    }
-
-    @Test
-    public void testNs11() throws Exception {
-        FeatureValidationUtil.validate(getClass().getResource("f04.xml").toURI());
-    }
-
-    @Test
-    public void testNs12() throws Exception {
-        FeatureValidationUtil.validate(getClass().getResource("f06.xml").toURI());
-    }
-
-    @Test
-    public void testNs13() throws Exception {
-        try {
-            FeatureValidationUtil.validate(getClass().getResource("f05.xml").toURI());
-            fail("Validation should have failed");
-        } catch (Exception e) {
-            // ok
-        }
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/test/java/org/apache/karaf/features/internal/OverridesTest.java
----------------------------------------------------------------------
diff --git a/features/core/src/test/java/org/apache/karaf/features/internal/OverridesTest.java b/features/core/src/test/java/org/apache/karaf/features/internal/OverridesTest.java
deleted file mode 100644
index 99feb32..0000000
--- a/features/core/src/test/java/org/apache/karaf/features/internal/OverridesTest.java
+++ /dev/null
@@ -1,243 +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;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.Writer;
-import java.util.Arrays;
-import java.util.List;
-
-import org.apache.felix.utils.manifest.Clause;
-import org.apache.karaf.features.BundleInfo;
-import org.apache.karaf.features.internal.model.Bundle;
-import org.junit.Before;
-import org.junit.Test;
-import org.ops4j.pax.tinybundles.core.TinyBundles;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-
-public class OverridesTest {
-
-    private String bsn = "bsn";
-    private File b100;
-    private File b101;
-    private File b102;
-    private File b110;
-    private File c100;
-    private File c101;
-    private File c110;
-
-    @Before
-    public void setUp() throws IOException {
-        b100 = File.createTempFile("karaf", "-100.jar");
-        copy(TinyBundles.bundle()
-                .set("Bundle-SymbolicName", bsn)
-                .set("Bundle-Version", "1.0.0")
-                .build(),
-                new FileOutputStream(b100));
-
-        b101 = File.createTempFile("karaf", "-101.jar");
-        copy(TinyBundles.bundle()
-                .set("Bundle-SymbolicName", bsn)
-                .set("Bundle-Version", "1.0.1")
-                .build(),
-                new FileOutputStream(b101));
-
-        b102 = File.createTempFile("karaf", "-102.jar");
-        copy(TinyBundles.bundle()
-                .set("Bundle-SymbolicName", bsn)
-                .set("Bundle-Version", "1.0.2")
-                .build(),
-                new FileOutputStream(b102));
-
-        b110 = File.createTempFile("karaf", "-110.jar");
-        copy(TinyBundles.bundle()
-                .set("Bundle-SymbolicName", bsn)
-                .set("Bundle-Version", "1.1.0")
-                .build(),
-                new FileOutputStream(b110));
-
-        c100 = File.createTempFile("karafc", "-100.jar");
-        copy(TinyBundles.bundle()
-                .set("Bundle-SymbolicName", bsn)
-                .set("Bundle-Version", "1.0.0")
-                .set("Bundle-Vendor", "Apache")
-                .build(),
-                new FileOutputStream(c100));
-
-        c101 = File.createTempFile("karafc", "-101.jar");
-        copy(TinyBundles.bundle()
-                .set("Bundle-SymbolicName", bsn)
-                .set("Bundle-Version", "1.0.1")
-                .set("Bundle-Vendor", "NotApache")
-                .build(),
-                new FileOutputStream(c101)); 
-
-        c110 = File.createTempFile("karafc", "-110.jar");
-        copy(TinyBundles.bundle()
-                .set("Bundle-SymbolicName", bsn)
-                .set("Bundle-Version", "1.1.0")
-                .set("Bundle-Vendor", "NotApache")
-                .build(),
-                new FileOutputStream(c110));
-    }
-
-    @Test
-    public void testDifferentVendors() throws IOException {
-        File props = File.createTempFile("karaf", "properties");
-        Writer w = new FileWriter(props);
-        w.write(c101.toURI().toString());
-        w.write("\n");
-        w.write(c110.toURI().toString());
-        w.write("\n");
-        w.close();
-
-        List<BundleInfo> res = Overrides.override(
-                Arrays.<BundleInfo>asList(new Bundle(c100.toURI().toString())),
-                props.toURI().toString());
-        assertNotNull(res);
-        assertEquals(1, res.size());
-        BundleInfo out = res.get(0);
-        assertNotNull(out);
-        assertEquals(c101.toURI().toString(), out.getLocation());
-    }
-
-    @Test
-    public void testMatching101() throws IOException {
-        File props = File.createTempFile("karaf", "properties");
-        Writer w = new FileWriter(props);
-        w.write(b101.toURI().toString());
-        w.write("\n");
-        w.write(b110.toURI().toString());
-        w.write("\n");
-        w.close();
-
-        List<BundleInfo> res = Overrides.override(
-                Arrays.<BundleInfo>asList(new Bundle(b100.toURI().toString())),
-                props.toURI().toString());
-        assertNotNull(res);
-        assertEquals(1, res.size());
-        BundleInfo out = res.get(0);
-        assertNotNull(out);
-        assertEquals(b101.toURI().toString(), out.getLocation());
-    }
-
-    @Test
-    public void testMatching102() throws IOException {
-        File props = File.createTempFile("karaf", "properties");
-        Writer w = new FileWriter(props);
-        w.write(b101.toURI().toString());
-        w.write("\n");
-        w.write(b102.toURI().toString());
-        w.write("\n");
-        w.write(b110.toURI().toString());
-        w.write("\n");
-        w.close();
-
-        List<BundleInfo> res = Overrides.override(
-                Arrays.<BundleInfo>asList(new Bundle(b100.toURI().toString())),
-                props.toURI().toString());
-        assertNotNull(res);
-        assertEquals(1, res.size());
-        BundleInfo out = res.get(0);
-        assertNotNull(out);
-        assertEquals(b102.toURI().toString(), out.getLocation());
-    }
-
-    @Test
-    public void testMatchingRange() throws IOException {
-        File props = File.createTempFile("karaf", "properties");
-        Writer w = new FileWriter(props);
-        w.write(b101.toURI().toString());
-        w.write("\n");
-        w.write(b110.toURI().toString());
-        w.write(";range=\"[1.0, 2.0)\"\n");
-        w.close();
-
-        List<BundleInfo> res = Overrides.override(
-                Arrays.<BundleInfo>asList(new Bundle(b100.toURI().toString())),
-                props.toURI().toString());
-        assertNotNull(res);
-        assertEquals(1, res.size());
-        BundleInfo out = res.get(0);
-        assertNotNull(out);
-        assertEquals(b110.toURI().toString(), out.getLocation());
-    }
-
-    @Test
-    public void testNotMatching() throws IOException {
-        File props = File.createTempFile("karaf", "properties");
-        Writer w = new FileWriter(props);
-        w.write(b110.toURI().toString());
-        w.write("\n");
-        w.close();
-
-        List<BundleInfo> res = Overrides.override(
-                Arrays.<BundleInfo>asList(new Bundle(b100.toURI().toString())),
-                props.toURI().toString());
-        assertNotNull(res);
-        assertEquals(1, res.size());
-        BundleInfo out = res.get(0);
-        assertNotNull(out);
-        assertEquals(b100.toURI().toString(), out.getLocation());
-    }
-
-    @Test
-    public void testLoadOverrides() {
-        List<Clause> overrides = Overrides.loadOverrides(getClass().getResource("overrides.properties").toExternalForm());
-        assertEquals(2, overrides.size());
-
-        Clause karafAdminCommand = null;
-        Clause karafAdminCore = null;
-        for (Clause clause : overrides) {
-            if (clause.getName().equals("mvn:org.apache.karaf.admin/org.apache.karaf.admin.command/2.3.0.redhat-61033X")) {
-                karafAdminCommand = clause;
-            }
-            if (clause.getName().equals("mvn:org.apache.karaf.admin/org.apache.karaf.admin.core/2.3.0.redhat-61033X")) {
-                karafAdminCore = clause;
-            }
-        }
-        assertNotNull("Missing admin.command bundle override", karafAdminCommand);
-        assertNotNull("Missing admin.core bundle override", karafAdminCore);
-        assertNotNull("Missing range on admin.core override", karafAdminCore.getAttribute(Overrides.OVERRIDE_RANGE));
-    }
-
-    /**
-     * Copies the content of {@link java.io.InputStream} to {@link java.io.OutputStream}.
-     *
-     * @param input
-     * @param output
-     * @throws java.io.IOException
-     */
-    private void copy(final InputStream input, final OutputStream output) throws IOException {
-        byte[] buffer = new byte[1024 * 16];
-        int n;
-        while (-1 != (n = input.read(buffer))) {
-            output.write(buffer, 0, n);
-            output.flush();
-        }
-        input.close();
-        output.close();
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/test/java/org/apache/karaf/features/internal/TestBase.java
----------------------------------------------------------------------
diff --git a/features/core/src/test/java/org/apache/karaf/features/internal/TestBase.java b/features/core/src/test/java/org/apache/karaf/features/internal/TestBase.java
deleted file mode 100644
index e651a6c..0000000
--- a/features/core/src/test/java/org/apache/karaf/features/internal/TestBase.java
+++ /dev/null
@@ -1,108 +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;
-
-import static java.util.Arrays.asList;
-import static org.easymock.EasyMock.expect;
-import static org.easymock.EasyMock.replay;
-
-import java.util.Arrays;
-import java.util.Dictionary;
-import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Hashtable;
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.karaf.features.Feature;
-import org.easymock.EasyMock;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.startlevel.BundleStartLevel;
-
-public class TestBase {
-    public Bundle createDummyBundle(long id, final String symbolicName, Dictionary<String,String> headers) {
-        Bundle bundle = EasyMock.createNiceMock(Bundle.class);
-        
-        // Be aware that this means all bundles are treated as different
-        expect(bundle.compareTo(EasyMock.<Bundle>anyObject())).andReturn(1).anyTimes();
-
-        expect(bundle.getBundleId()).andReturn(id).anyTimes();
-        expect(bundle.getSymbolicName()).andReturn(symbolicName).anyTimes();
-        expect(bundle.getHeaders()).andReturn(headers).anyTimes();
-        BundleStartLevel sl = EasyMock.createMock(BundleStartLevel.class);
-        expect(sl.isPersistentlyStarted()).andReturn(true).anyTimes();
-        expect(bundle.adapt(BundleStartLevel.class)).andReturn(sl).anyTimes();
-        replay(bundle, sl);
-        return bundle;
-    }
-    
-    public Dictionary<String, String> headers(String ... keyAndHeader) {
-        Hashtable<String, String> headersTable = new Hashtable<String, String>();
-        int c=0;
-        while (c < keyAndHeader.length) {
-            String key = keyAndHeader[c++];
-            String value = keyAndHeader[c++];
-            headersTable.put(key, value);
-        }
-        return headersTable;
-    }
-    
-    public Map<String, Map<String, Feature>> features(Feature ... features) {
-        final Map<String, Map<String, Feature>> featuresMap = new HashMap<String, Map<String,Feature>>();
-        for (Feature feature : features) {
-            Map<String, Feature> featureVersion = getOrCreate(featuresMap, feature);
-            featureVersion.put(feature.getVersion(), feature);
-        }
-        return featuresMap;
-    }
-    
-    private Map<String, Feature> getOrCreate(final Map<String, Map<String, Feature>> featuresMap, Feature feature) {
-        Map<String, Feature> featureVersion = featuresMap.get(feature.getName());
-        if (featureVersion == null) {
-            featureVersion = new HashMap<String, Feature>();
-            featuresMap.put(feature.getName(), featureVersion);
-        }
-        return featureVersion;
-    }
-
-    public Feature feature(String name, String version) {
-        return new org.apache.karaf.features.internal.model.Feature(name, version);
-    }
-    
-    public Set<Bundle> setOf(Bundle ... elements) {
-        return new HashSet<Bundle>(Arrays.asList(elements));
-    }
-    
-    public Set<Long> setOf(Long ... elements) {
-        return new HashSet<Long>(Arrays.asList(elements));
-    }
-    
-    public Set<String> setOf(String ... elements) {
-        return new HashSet<String>(asList(elements));
-    }
-    
-    public Set<Feature> setOf(Feature ... elements) {
-        return new HashSet<Feature>(Arrays.asList(elements));
-    }
-
-    @SuppressWarnings("unchecked")
-    public void ignoreRefreshes(BundleManager bundleManager) {
-        bundleManager.refreshBundles(EasyMock.anyObject(Set.class), EasyMock.anyObject(Set.class), EasyMock.anyObject(EnumSet.class));        
-        EasyMock.expectLastCall().anyTimes();
-    }
-}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/test/java/org/apache/karaf/features/internal/service/BootFeaturesInstallerTest.java
----------------------------------------------------------------------
diff --git a/features/core/src/test/java/org/apache/karaf/features/internal/service/BootFeaturesInstallerTest.java b/features/core/src/test/java/org/apache/karaf/features/internal/service/BootFeaturesInstallerTest.java
new file mode 100644
index 0000000..ab11b6b
--- /dev/null
+++ b/features/core/src/test/java/org/apache/karaf/features/internal/service/BootFeaturesInstallerTest.java
@@ -0,0 +1,128 @@
+/*
+ * 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.service;
+
+import static java.util.Arrays.asList;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.junit.Assert.fail;
+
+import java.net.URI;
+import java.util.EnumSet;
+
+import org.apache.karaf.features.Feature;
+import org.apache.karaf.features.FeaturesService.Option;
+import org.apache.karaf.features.TestBase;
+import org.easymock.EasyMock;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class BootFeaturesInstallerTest extends TestBase {
+
+    @Test
+    @SuppressWarnings("unchecked")
+    public void testParser() {
+        BootFeaturesInstaller installer = new BootFeaturesInstaller(null, null, "", "", false);
+        Assert.assertEquals(asList(setOf("test1", "test2"),setOf("test3")), installer.parseBootFeatures("(test1, test2), test3"));
+        Assert.assertEquals(asList(setOf("test1", "test2", "test3")), installer.parseBootFeatures("test1, test2, test3"));
+    }
+    
+    @Test
+    public void testDefaultBootFeatures() throws Exception  {
+        FeaturesServiceImpl impl = EasyMock.createMock(FeaturesServiceImpl.class);
+        Feature configFeature = feature("config", "1.0.0");
+        Feature standardFeature = feature("standard", "1.0.0");
+        Feature regionFeature = feature("region", "1.0.0");
+        expect(impl.listInstalledFeatures()).andStubReturn(new Feature[]{});
+        expect(impl.getFeature("config", "0.0.0")).andReturn(configFeature);
+        expect(impl.getFeature("standard", "0.0.0")).andReturn(standardFeature);
+        expect(impl.getFeature("region", "0.0.0")).andReturn(regionFeature);
+
+        impl.installFeatures(setOf(configFeature, standardFeature, regionFeature), EnumSet.of(Option.NoCleanIfFailure, Option.ContinueBatchOnFailure));
+        EasyMock.expectLastCall();
+
+        impl.bootDone();
+        EasyMock.expectLastCall();
+
+        replay(impl);
+        BootFeaturesInstaller bootFeatures = new BootFeaturesInstaller(null, impl, "", "config,standard,region", false);
+        bootFeatures.installBootFeatures();
+        EasyMock.verify(impl);        
+    }
+
+    /**
+     * This test checks KARAF-388 which allows you to specify version of boot feature.
+     * @throws Exception 
+     */
+    @Test
+    public void testStartDoesNotFailWithNonExistentVersion() throws Exception  {
+        FeaturesServiceImpl impl = EasyMock.createMock(FeaturesServiceImpl.class);
+        Feature sshFeature = feature("ssh", "1.0.0");
+        expect(impl.getFeature("ssh", "1.0.0")).andReturn(sshFeature);
+        expect(impl.getFeature("transaction", "1.2")).andReturn(null);
+        
+        // Only the ssh feature should get installed
+        impl.installFeatures(setOf(sshFeature), EnumSet.of(Option.NoCleanIfFailure, Option.ContinueBatchOnFailure));
+        EasyMock.expectLastCall();
+
+        impl.bootDone();
+        EasyMock.expectLastCall();
+        
+        replay(impl);
+        BootFeaturesInstaller bootFeatures = new BootFeaturesInstaller(null, impl , "", "transaction;version=1.2,ssh;version=1.0.0", false);
+        bootFeatures.installBootFeatures();
+        EasyMock.verify(impl);        
+    }
+    
+    @Test
+    public void testStagedBoot() throws Exception  {
+        FeaturesServiceImpl impl = EasyMock.createStrictMock(FeaturesServiceImpl.class);
+        Feature sshFeature = feature("ssh", "1.0.0");
+        Feature transactionFeature = feature("transaction", "2.0.0");
+        expect(impl.getFeature("transaction", "0.0.0")).andStubReturn(transactionFeature);
+        expect(impl.getFeature("ssh", "0.0.0")).andStubReturn(sshFeature);
+
+        impl.installFeatures(setOf(transactionFeature), EnumSet.of(Option.NoCleanIfFailure, Option.ContinueBatchOnFailure));
+        EasyMock.expectLastCall();
+        impl.installFeatures(setOf(sshFeature), EnumSet.of(Option.NoCleanIfFailure, Option.ContinueBatchOnFailure));
+        EasyMock.expectLastCall();
+
+        impl.bootDone();
+        EasyMock.expectLastCall();
+
+        replay(impl);
+        BootFeaturesInstaller bootFeatures = new BootFeaturesInstaller(null, impl , "", "(transaction), ssh", false);
+        bootFeatures.installBootFeatures();
+        EasyMock.verify(impl);        
+    }
+
+    @Test
+    public void testStartDoesNotFailWithOneInvalidUri() throws Exception {
+        FeaturesServiceImpl impl = EasyMock.createStrictMock(FeaturesServiceImpl.class);
+        impl.addRepository(URI.create("mvn:inexistent/features/1.0/xml/features"));
+        EasyMock.expectLastCall().andThrow(new IllegalArgumentException());
+
+        impl.bootDone();
+        EasyMock.expectLastCall();
+
+        replay(impl);
+        BootFeaturesInstaller bootFeatures = new BootFeaturesInstaller(null, impl, "mvn:inexistent/features/1.0/xml/features", "", false);
+        bootFeatures.installBootFeatures();
+        EasyMock.verify(impl);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/test/java/org/apache/karaf/features/internal/service/BundleManagerTest.java
----------------------------------------------------------------------
diff --git a/features/core/src/test/java/org/apache/karaf/features/internal/service/BundleManagerTest.java b/features/core/src/test/java/org/apache/karaf/features/internal/service/BundleManagerTest.java
new file mode 100644
index 0000000..bed8104
--- /dev/null
+++ b/features/core/src/test/java/org/apache/karaf/features/internal/service/BundleManagerTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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.service;
+
+import org.apache.karaf.features.TestBase;
+
+public class BundleManagerTest extends TestBase {
+
+    /*
+    @Test
+    public void testfindBundlestoRefreshWithHostToRefresh() throws Exception {
+        Bundle hostBundle = createDummyBundle(12345l, "Host", headers());
+        Bundle fragmentBundle = createDummyBundle(54321l, "fragment", headers(Constants.FRAGMENT_HOST, "Host"));
+
+        BundleContext bundleContext = EasyMock.createMock(BundleContext.class);
+        BundleManager bundleManager = new BundleManager(bundleContext);
+
+        // Host was already installed, fragment is new
+        Set<Bundle> existing = new HashSet<Bundle>(Arrays.asList(hostBundle, fragmentBundle));
+        Set<Bundle> installed = new HashSet<Bundle>(Arrays.asList(fragmentBundle));
+        
+        replay(bundleContext);
+        Set<Bundle> bundles = bundleManager.findBundlesWithFragmentsToRefresh(existing, installed);
+        EasyMock.verify(bundleContext);
+
+        Assert.assertEquals(1, bundles.size());
+        Assert.assertEquals(hostBundle, bundles.iterator().next());
+    }
+    
+    @Test
+    public void testfindBundlestoRefreshWithOptionalPackages() throws Exception {
+        Bundle exporterBundle = createDummyBundle(12345l, "exporter", headers(Constants.EXPORT_PACKAGE, "org.my.package"));
+        Bundle importerBundle = createDummyBundle(54321l, "importer", headers(Constants.IMPORT_PACKAGE, "org.my.package;resolution:=optional"));
+
+        BundleContext bundleContext = EasyMock.createMock(BundleContext.class);
+        BundleManager bundleManager = new BundleManager(bundleContext);
+
+        // Importer was already installed, exporter is new
+        Set<Bundle> existing = new HashSet<Bundle>(Arrays.asList(importerBundle, exporterBundle));
+        Set<Bundle> installed = new HashSet<Bundle>(Arrays.asList(exporterBundle));
+        
+        replay(bundleContext);
+        Set<Bundle> bundles = bundleManager.findBundlesWithOptionalPackagesToRefresh(existing, installed);
+        EasyMock.verify(bundleContext);
+
+        Assert.assertEquals(1, bundles.size());
+        Assert.assertEquals(importerBundle, bundles.iterator().next());
+    }
+    */
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/test/java/org/apache/karaf/features/internal/service/FeaturesServiceImplTest.java
----------------------------------------------------------------------
diff --git a/features/core/src/test/java/org/apache/karaf/features/internal/service/FeaturesServiceImplTest.java b/features/core/src/test/java/org/apache/karaf/features/internal/service/FeaturesServiceImplTest.java
new file mode 100644
index 0000000..8339151
--- /dev/null
+++ b/features/core/src/test/java/org/apache/karaf/features/internal/service/FeaturesServiceImplTest.java
@@ -0,0 +1,167 @@
+/*
+ * 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.service;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.fail;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Map;
+
+import org.apache.karaf.features.Feature;
+import org.apache.karaf.features.TestBase;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test cases for {@link org.apache.karaf.features.internal.service.FeaturesServiceImpl}
+ */
+public class FeaturesServiceImplTest extends TestBase {
+    
+    File dataFile;
+
+    @Before
+    public void setUp() throws IOException {
+        dataFile = File.createTempFile("features", null, null);
+    }
+
+    @Test
+    public void testGetFeature() throws Exception {
+        Feature transactionFeature = feature("transaction", "1.0.0");
+        final Map<String, Map<String, Feature>> features = features(transactionFeature);
+        final FeaturesServiceImpl impl = new FeaturesServiceImpl(null, null, new Storage(), null, null, null, "") {
+            protected Map<String,Map<String,Feature>> getFeatures() throws Exception {
+                return features;
+            }
+        };
+        assertNotNull(impl.getFeature("transaction", org.apache.karaf.features.internal.model.Feature.DEFAULT_VERSION));
+        assertSame(transactionFeature, impl.getFeature("transaction", org.apache.karaf.features.internal.model.Feature.DEFAULT_VERSION));
+    }
+    
+    @Test
+    public void testGetFeatureStripVersion() throws Exception {
+        final FeaturesServiceImpl impl = new FeaturesServiceImpl(null, null, new Storage(), null, null, null, "") {
+            protected Map<String,Map<String,Feature>> getFeatures() throws Exception {
+                return features(feature("transaction", "1.0.0"));
+            }
+        };
+        Feature feature = impl.getFeature("transaction", "  1.0.0  ");
+        assertNotNull(feature);
+        assertSame("transaction", feature.getName());
+    }
+    
+    @Test
+    public void testGetFeatureNotAvailable() throws Exception {
+        final FeaturesServiceImpl impl = new FeaturesServiceImpl(null, null, new Storage(), null, null, null, "") {
+            protected Map<String,Map<String,Feature>> getFeatures() throws Exception {
+                return features(feature("transaction", "1.0.0"));
+            }
+        };
+        assertNull(impl.getFeature("activemq", org.apache.karaf.features.internal.model.Feature.DEFAULT_VERSION));
+    }
+    
+    @Test
+    public void testGetFeatureHighestAvailable() throws Exception {
+        final Map<String, Map<String, Feature>> features = features(
+                feature("transaction", "1.0.0"),
+                feature("transaction", "2.0.0")
+        );
+        final FeaturesServiceImpl impl = new FeaturesServiceImpl(null, null, new Storage(), null, null, null, "") {
+            protected Map<String,Map<String,Feature>> getFeatures() throws Exception {
+                return features;
+            }
+        };
+        assertNotNull(impl.getFeature("transaction", org.apache.karaf.features.internal.model.Feature.DEFAULT_VERSION));
+        assertSame("2.0.0", impl.getFeature("transaction", org.apache.karaf.features.internal.model.Feature.DEFAULT_VERSION).getVersion());
+    }
+
+    /**
+     * This test ensures that every feature get installed only once, even if it appears multiple times in the list
+     * of transitive feature dependencies (KARAF-1600)
+     */
+    /*
+    @Test
+    @SuppressWarnings("unchecked")
+    public void testNoDuplicateFeaturesInstallation() throws Exception {
+        final List<Feature> installed = new LinkedList<Feature>();
+        BundleManager bundleManager = EasyMock.createMock(BundleManager.class);
+        expect(bundleManager.installBundleIfNeeded(EasyMock.anyObject(String.class), EasyMock.anyInt(), EasyMock.anyObject(String.class)))
+            .andReturn(new BundleInstallerResult(createDummyBundle(1l, "", headers()), true)).anyTimes();
+        bundleManager.refreshBundles(EasyMock.anyObject(Set.class), EasyMock.anyObject(Set.class), EasyMock.anyObject(EnumSet.class));
+        EasyMock.expectLastCall();
+        final FeaturesServiceImpl impl = new FeaturesServiceImpl(bundleManager, null) {
+            // override methods which refers to bundle context to avoid mocking everything
+            @Override
+            protected boolean loadState() {
+                return true;
+            }
+
+            @Override
+            protected void saveState() {
+
+            }
+
+            @Override
+            protected void doInstallFeature(InstallationState state, Feature feature, boolean verbose) throws Exception {
+                installed.add(feature);
+
+                super.doInstallFeature(state, feature, verbose);
+            }
+
+        };
+        replay(bundleManager);
+        impl.addRepository(getClass().getResource("repo2.xml").toURI());
+        impl.installFeature("all");
+
+        // copying the features to a set to filter out the duplicates
+        Set<Feature> noduplicates = new HashSet<Feature>();
+        noduplicates.addAll(installed);
+
+        assertEquals("Every feature should only have been installed once", installed.size(), noduplicates.size());
+    }
+
+    @Test
+    public void testGetOptionalImportsOnly() {
+        BundleManager bundleManager = new BundleManager(null, 0l);
+
+        List<Clause> result = bundleManager.getOptionalImports("org.apache.karaf,org.apache.karaf.optional;resolution:=optional");
+        assertEquals("One optional import expected", 1, result.size());
+        assertEquals("org.apache.karaf.optional", result.get(0).getName());
+
+        result = bundleManager.getOptionalImports(null);
+        assertNotNull(result);
+        assertEquals("No optional imports expected", 0, result.size());
+    }
+    */
+
+    static class Storage extends StateStorage {
+        @Override
+        protected InputStream getInputStream() throws IOException {
+            return null;
+        }
+        @Override
+        protected OutputStream getOutputStream() throws IOException {
+            return null;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/test/java/org/apache/karaf/features/internal/service/FeaturesValidationTest.java
----------------------------------------------------------------------
diff --git a/features/core/src/test/java/org/apache/karaf/features/internal/service/FeaturesValidationTest.java b/features/core/src/test/java/org/apache/karaf/features/internal/service/FeaturesValidationTest.java
new file mode 100644
index 0000000..f3ca2e6
--- /dev/null
+++ b/features/core/src/test/java/org/apache/karaf/features/internal/service/FeaturesValidationTest.java
@@ -0,0 +1,65 @@
+/*
+ * 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.service;
+
+import org.junit.Test;
+
+import static org.junit.Assert.fail;
+
+public class FeaturesValidationTest {
+
+    @Test
+    public void testNoNs() throws Exception {
+        FeatureValidationUtil.validate(getClass().getResource("f01.xml").toURI());
+    }
+
+    @Test
+    public void testNs10() throws Exception {
+        FeatureValidationUtil.validate(getClass().getResource("f02.xml").toURI());
+    }
+
+    @Test
+    public void testNs10NoName() throws Exception {
+        FeatureValidationUtil.validate(getClass().getResource("f03.xml").toURI());
+    }
+
+    @Test
+    public void testNs11() throws Exception {
+        FeatureValidationUtil.validate(getClass().getResource("f04.xml").toURI());
+    }
+
+    @Test
+    public void testNs12() throws Exception {
+        FeatureValidationUtil.validate(getClass().getResource("f06.xml").toURI());
+    }
+
+    @Test
+    public void testNs11NoName() throws Exception {
+        try {
+            FeatureValidationUtil.validate(getClass().getResource("f05.xml").toURI());
+            fail("Validation should have failed");
+        } catch (Exception e) {
+            // ok
+        }
+    }
+
+    @Test
+    public void testNs13() throws Exception {
+        FeatureValidationUtil.validate(getClass().getResource("f07.xml").toURI());
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/test/java/org/apache/karaf/features/internal/service/OverridesTest.java
----------------------------------------------------------------------
diff --git a/features/core/src/test/java/org/apache/karaf/features/internal/service/OverridesTest.java b/features/core/src/test/java/org/apache/karaf/features/internal/service/OverridesTest.java
new file mode 100644
index 0000000..c4976cf
--- /dev/null
+++ b/features/core/src/test/java/org/apache/karaf/features/internal/service/OverridesTest.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.service;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Writer;
+import java.net.URI;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.felix.utils.manifest.Clause;
+import org.apache.felix.utils.manifest.Parser;
+import org.apache.karaf.features.BundleInfo;
+import org.apache.karaf.features.internal.model.Bundle;
+import org.apache.karaf.features.internal.resolver.ResourceBuilder;
+import org.apache.karaf.features.internal.resolver.UriNamespace;
+import org.junit.Before;
+import org.junit.Test;
+import org.ops4j.pax.tinybundles.core.TinyBundles;
+import org.osgi.framework.BundleException;
+import org.osgi.resource.Resource;
+
+import static org.apache.karaf.features.internal.resolver.UriNamespace.getUri;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+public class OverridesTest {
+
+    private String bsn = "bsn";
+    private Resource b100;
+    private Resource b101;
+    private Resource b102;
+    private Resource b110;
+    private Resource c100;
+    private Resource c101;
+    private Resource c110;
+
+    @Before
+    public void setUp() throws BundleException {
+        b100 = resource("karaf-100.jar")
+                .set("Bundle-SymbolicName", bsn)
+                .set("Bundle-Version", "1.0.0")
+                .build();
+
+        b101 = resource("karaf-101.jar")
+                .set("Bundle-SymbolicName", bsn)
+                .set("Bundle-Version", "1.0.1")
+                .build();
+
+        b102 = resource("karaf-102.jar")
+                .set("Bundle-SymbolicName", bsn)
+                .set("Bundle-Version", "1.0.2")
+                .build();
+
+        b110 = resource("karaf-110.jar")
+                .set("Bundle-SymbolicName", bsn)
+                .set("Bundle-Version", "1.1.0")
+                .build();
+
+        c100 = resource("karafc-100.jar")
+                .set("Bundle-SymbolicName", bsn)
+                .set("Bundle-Version", "1.0.0")
+                .set("Bundle-Vendor", "Apache")
+                .build();
+
+        c101 = resource("karafc-101.jar")
+                .set("Bundle-SymbolicName", bsn)
+                .set("Bundle-Version", "1.0.1")
+                .set("Bundle-Vendor", "NotApache")
+                .build();
+
+        c110 = resource("karafc-110.jar")
+                .set("Bundle-SymbolicName", bsn)
+                .set("Bundle-Version", "1.1.0")
+                .set("Bundle-Vendor", "NotApache")
+                .build();
+    }
+
+    @Test
+    public void testDifferentVendors() throws IOException {
+        Map<String, Resource> map = asResourceMap(c100, c101, c110);
+        assertEquals(c100, map.get(getUri(c100)));
+        Overrides.override(map, Arrays.asList(getUri(c101), getUri(c110)));
+        assertEquals(c101, map.get(getUri(c100)));
+    }
+
+    @Test
+    public void testMatching101() throws IOException {
+        Map<String, Resource> map = asResourceMap(b100, b101, b110);
+        assertEquals(b100, map.get(getUri(b100)));
+        Overrides.override(map, Arrays.asList(getUri(b101), getUri(b110)));
+        assertEquals(b101, map.get(getUri(b100)));
+    }
+
+    @Test
+    public void testMatching102() throws IOException {
+        Map<String, Resource> map = asResourceMap(b100, b101, b102, b110);
+        assertEquals(b100, map.get(getUri(b100)));
+        Overrides.override(map, Arrays.asList(getUri(b101), getUri(b102), getUri(b110)));
+        assertEquals(b102, map.get(getUri(b100)));
+    }
+
+    @Test
+    public void testMatchingRange() throws IOException {
+        Map<String, Resource> map = asResourceMap(b100, b101, b110);
+        assertEquals(b100, map.get(getUri(b100)));
+        Overrides.override(map, Arrays.asList(getUri(b101), getUri(b110) + ";range=\"[1.0, 2.0)\""));
+        assertEquals(b110, map.get(getUri(b100)));
+    }
+
+    @Test
+    public void testNotMatching() throws IOException {
+        Map<String, Resource> map = asResourceMap(b100, b110);
+        assertEquals(b100, map.get(getUri(b100)));
+        Overrides.override(map, Arrays.asList(getUri(b110)));
+        assertEquals(b100, map.get(getUri(b100)));
+    }
+
+    @Test
+    public void testLoadOverrides() {
+        Set<String> overrides = Overrides.loadOverrides(getClass().getResource("overrides.properties").toExternalForm());
+        assertEquals(2, overrides.size());
+
+        Clause karafAdminCommand = null;
+        Clause karafAdminCore = null;
+        for (Clause clause : Parser.parseClauses(overrides.toArray(new String[overrides.size()]))) {
+            if (clause.getName().equals("mvn:org.apache.karaf.admin/org.apache.karaf.admin.command/2.3.0.redhat-61033X")) {
+                karafAdminCommand = clause;
+            }
+            if (clause.getName().equals("mvn:org.apache.karaf.admin/org.apache.karaf.admin.core/2.3.0.redhat-61033X")) {
+                karafAdminCore = clause;
+            }
+        }
+        assertNotNull("Missing admin.command bundle override", karafAdminCommand);
+        assertNotNull("Missing admin.core bundle override", karafAdminCore);
+        assertNotNull("Missing range on admin.core override", karafAdminCore.getAttribute(Overrides.OVERRIDE_RANGE));
+    }
+
+    /**
+     * Copies the content of {@link java.io.InputStream} to {@link java.io.OutputStream}.
+     *
+     * @param input
+     * @param output
+     * @throws java.io.IOException
+     */
+    private void copy(final InputStream input, final OutputStream output) throws IOException {
+        byte[] buffer = new byte[1024 * 16];
+        int n;
+        while (-1 != (n = input.read(buffer))) {
+            output.write(buffer, 0, n);
+            output.flush();
+        }
+        input.close();
+        output.close();
+    }
+
+    static Builder resource(String uri) {
+        return new Builder(uri);
+    }
+
+    static Map<String, Resource> asResourceMap(Resource... resources) {
+        Map<String, Resource> map = new HashMap<String, Resource>();
+        for (Resource resource : resources) {
+            map.put(getUri(resource), resource);
+        }
+        return map;
+    }
+
+    static class Builder {
+        String uri;
+        Map<String,String> headers = new HashMap<String,String>();
+        Builder(String uri) {
+            this.uri = uri;
+            this.headers.put("Bundle-ManifestVersion", "2");
+        }
+        Builder set(String key, String value) {
+            this.headers.put(key, value);
+            return this;
+        }
+        Resource build() throws BundleException {
+            return ResourceBuilder.build(uri, headers);
+        }
+    }
+
+}


[11/13] [KARAF-2888] New FeaturesService based on the real OSGi resolver

Posted by gn...@apache.org.
http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/internal/management/FeaturesServiceMBeanImpl.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/management/FeaturesServiceMBeanImpl.java b/features/core/src/main/java/org/apache/karaf/features/internal/management/FeaturesServiceMBeanImpl.java
new file mode 100644
index 0000000..350679f
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/management/FeaturesServiceMBeanImpl.java
@@ -0,0 +1,274 @@
+/*
+ * Licensed 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.management;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.EnumSet;
+import java.util.Hashtable;
+import java.util.List;
+
+import javax.management.MBeanNotificationInfo;
+import javax.management.MBeanRegistration;
+import javax.management.MBeanServer;
+import javax.management.NotCompliantMBeanException;
+import javax.management.Notification;
+import javax.management.ObjectName;
+import javax.management.openmbean.TabularData;
+
+import org.apache.karaf.features.*;
+import org.apache.karaf.features.management.FeaturesServiceMBean;
+import org.apache.karaf.features.management.codec.JmxFeature;
+import org.apache.karaf.features.management.codec.JmxFeatureEvent;
+import org.apache.karaf.features.management.codec.JmxRepository;
+import org.apache.karaf.features.management.codec.JmxRepositoryEvent;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+
+/**
+ * Implementation of {@link FeaturesServiceMBean}.
+ */
+public class FeaturesServiceMBeanImpl extends StandardEmitterMBean implements
+        MBeanRegistration, FeaturesServiceMBean {
+
+    private ServiceRegistration<FeaturesListener> registration;
+
+    private BundleContext bundleContext;
+
+    private ObjectName objectName;
+
+    private volatile long sequenceNumber = 0;
+
+    private org.apache.karaf.features.FeaturesService featuresService;
+
+    public FeaturesServiceMBeanImpl() throws NotCompliantMBeanException {
+        super(FeaturesServiceMBean.class);
+    }
+
+    public ObjectName preRegister(MBeanServer server, ObjectName name) throws Exception {
+        objectName = name;
+        return name;
+    }
+
+    public void postRegister(Boolean registrationDone) {
+        registration = bundleContext.registerService(FeaturesListener.class,
+                getFeaturesListener(), new Hashtable<String, String>());
+    }
+
+    public void preDeregister() throws Exception {
+        registration.unregister();
+    }
+
+    public void postDeregister() {
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public TabularData getFeatures() throws Exception {
+        try {
+            List<Feature> allFeatures = Arrays.asList(featuresService.listFeatures());
+            List<Feature> insFeatures = Arrays.asList(featuresService.listInstalledFeatures());
+            ArrayList<JmxFeature> features = new ArrayList<JmxFeature>();
+            for (Feature feature : allFeatures) {
+                try {
+                    features.add(new JmxFeature(feature, insFeatures.contains(feature)));
+                } catch (Throwable t) {
+                    t.printStackTrace();
+                }
+            }
+            TabularData table = JmxFeature.tableFrom(features);
+            return table;
+        } catch (Throwable t) {
+            t.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public TabularData getRepositories() throws Exception {
+        try {
+            List<Repository> allRepositories = Arrays.asList(featuresService.listRepositories());
+            ArrayList<JmxRepository> repositories = new ArrayList<JmxRepository>();
+            for (Repository repository : allRepositories) {
+                try {
+                    repositories.add(new JmxRepository(repository));
+                } catch (Throwable t) {
+                    t.printStackTrace();
+                }
+            }
+            TabularData table = JmxRepository.tableFrom(repositories);
+            return table;
+        } catch (Throwable t) {
+            t.printStackTrace();
+            return null;
+        }
+    }
+
+    public void addRepository(String uri) throws Exception {
+        featuresService.addRepository(new URI(uri));
+    }
+
+    public void addRepository(String uri, boolean install) throws Exception {
+        featuresService.addRepository(new URI(uri), install);
+    }
+
+    public void removeRepository(String uri) throws Exception {
+        featuresService.removeRepository(new URI(uri));
+    }
+
+    public void removeRepository(String uri, boolean uninstall) throws Exception {
+        featuresService.removeRepository(new URI(uri), uninstall);
+    }
+
+    public void installFeature(String name) throws Exception {
+        featuresService.installFeature(name);
+    }
+
+    public void installFeature(String name, boolean noClean, boolean noRefresh) throws Exception {
+        EnumSet<org.apache.karaf.features.FeaturesService.Option> options = EnumSet.noneOf(org.apache.karaf.features.FeaturesService.Option.class);
+        if (noClean) {
+            options.add(org.apache.karaf.features.FeaturesService.Option.NoCleanIfFailure);
+        }
+        if (noRefresh) {
+            options.add(org.apache.karaf.features.FeaturesService.Option.NoAutoRefreshBundles);
+        }
+        featuresService.installFeature(name, options);
+    }
+
+    public void installFeature(String name, String version) throws Exception {
+        featuresService.installFeature(name, version);
+    }
+
+    public void installFeature(String name, String version, boolean noClean, boolean noRefresh) throws Exception {
+        EnumSet<org.apache.karaf.features.FeaturesService.Option> options = EnumSet.noneOf(org.apache.karaf.features.FeaturesService.Option.class);
+        if (noClean) {
+            options.add(org.apache.karaf.features.FeaturesService.Option.NoCleanIfFailure);
+        }
+        if (noRefresh) {
+            options.add(org.apache.karaf.features.FeaturesService.Option.NoAutoRefreshBundles);
+        }
+        featuresService.installFeature(name, version, options);
+    }
+
+    public TabularData infoFeature(String name) throws Exception {
+        try {
+            Feature feature = featuresService.getFeature(name);
+            return infoFeature(feature);
+        } catch (Throwable t) {
+            t.printStackTrace();
+            return null;
+        }
+    }
+
+    public TabularData infoFeature(String name, String version) throws Exception {
+        try {
+            Feature feature = featuresService.getFeature(name, version);
+            return infoFeature(feature);
+        } catch (Throwable t) {
+            t.printStackTrace();
+            return null;
+        }
+    }
+
+    private TabularData infoFeature(Feature feature) throws Exception {
+        JmxFeature jmxFeature = null;
+        if (featuresService.isInstalled(feature)) {
+            jmxFeature = new JmxFeature(feature, true);
+        } else {
+            jmxFeature = new JmxFeature(feature, false);
+        }
+        ArrayList<JmxFeature> features = new ArrayList<JmxFeature>();
+        features.add(jmxFeature);
+        TabularData table = JmxFeature.tableFrom(features);
+        return table;
+    }
+
+    public void uninstallFeature(String name) throws Exception {
+        featuresService.uninstallFeature(name);
+    }
+
+    public void uninstallFeature(String name, boolean noRefresh) throws Exception {
+        EnumSet<org.apache.karaf.features.FeaturesService.Option> options = EnumSet.noneOf(org.apache.karaf.features.FeaturesService.Option.class);
+        if (noRefresh) {
+            options.add(org.apache.karaf.features.FeaturesService.Option.NoAutoRefreshBundles);
+        }
+        featuresService.uninstallFeature(name, options);
+    }
+
+    public void uninstallFeature(String name, String version) throws Exception {
+        featuresService.uninstallFeature(name, version);
+    }
+
+    public void uninstallFeature(String name, String version, boolean noRefresh) throws Exception {
+        EnumSet<org.apache.karaf.features.FeaturesService.Option> options = EnumSet.noneOf(org.apache.karaf.features.FeaturesService.Option.class);
+        if (noRefresh) {
+            options.add(org.apache.karaf.features.FeaturesService.Option.NoAutoRefreshBundles);
+        }
+        featuresService.uninstallFeature(name, version, options);
+    }
+
+    public void setBundleContext(BundleContext bundleContext) {
+        this.bundleContext = bundleContext;
+    }
+
+    public void setFeaturesService(org.apache.karaf.features.FeaturesService featuresService) {
+        this.featuresService = featuresService;
+    }
+
+    public FeaturesListener getFeaturesListener() {
+        return new FeaturesListener() {
+            public void featureEvent(FeatureEvent event) {
+                if (!event.isReplay()) {
+                    Notification notification = new Notification(FEATURE_EVENT_TYPE, objectName, sequenceNumber++);
+                    notification.setUserData(new JmxFeatureEvent(event).asCompositeData());
+                    sendNotification(notification);
+                }
+            }
+
+            public void repositoryEvent(RepositoryEvent event) {
+                if (!event.isReplay()) {
+                    Notification notification = new Notification(REPOSITORY_EVENT_TYPE, objectName, sequenceNumber++);
+                    notification.setUserData(new JmxRepositoryEvent(event).asCompositeData());
+                    sendNotification(notification);
+                }
+            }
+
+            public boolean equals(Object o) {
+                if (this == o) {
+                    return true;
+                }
+                return o.equals(this);
+            }
+
+        };
+    }
+
+    public MBeanNotificationInfo[] getNotificationInfo() {
+        return getBroadcastInfo();
+    }
+
+    private static MBeanNotificationInfo[] getBroadcastInfo() {
+        String type = Notification.class.getCanonicalName();
+        MBeanNotificationInfo info1 = new MBeanNotificationInfo(new String[]{FEATURE_EVENT_EVENT_TYPE},
+                type, "Some features notification");
+        MBeanNotificationInfo info2 = new MBeanNotificationInfo(new String[]{REPOSITORY_EVENT_EVENT_TYPE},
+                type, "Some repository notification");
+        return new MBeanNotificationInfo[]{info1, info2};
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/internal/management/StandardEmitterMBean.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/management/StandardEmitterMBean.java b/features/core/src/main/java/org/apache/karaf/features/internal/management/StandardEmitterMBean.java
new file mode 100644
index 0000000..13a4b6c
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/management/StandardEmitterMBean.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed 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.management;
+
+import javax.management.*;
+
+public class StandardEmitterMBean extends StandardMBean implements NotificationEmitter {
+
+    private final NotificationBroadcasterSupport emitter;
+
+    @SuppressWarnings("rawtypes")
+	public StandardEmitterMBean(Class mbeanInterface) throws NotCompliantMBeanException {
+        super(mbeanInterface);
+        this.emitter = new NotificationBroadcasterSupport() {
+            @Override
+            public MBeanNotificationInfo[] getNotificationInfo() {
+                return StandardEmitterMBean.this.getNotificationInfo();
+            }
+        };
+    }
+
+    public void sendNotification(Notification notification) {
+        emitter.sendNotification(notification);
+    }
+
+
+    public void removeNotificationListener(NotificationListener listener, NotificationFilter filter, Object handback) throws ListenerNotFoundException {
+        emitter.removeNotificationListener(listener, filter, handback);
+    }
+
+    public void addNotificationListener(NotificationListener listener, NotificationFilter filter, Object handback) throws IllegalArgumentException {
+        emitter.addNotificationListener(listener, filter, handback);
+    }
+
+    public void removeNotificationListener(NotificationListener listener) throws ListenerNotFoundException {
+        emitter.removeNotificationListener(listener);
+    }
+
+    public MBeanNotificationInfo[] getNotificationInfo() {
+        return new MBeanNotificationInfo[0];
+    }
+
+    @Override
+    public MBeanInfo getMBeanInfo() {
+        MBeanInfo mbeanInfo = super.getMBeanInfo();
+        if (mbeanInfo != null) {
+            MBeanNotificationInfo[] notificationInfo = getNotificationInfo();
+            mbeanInfo = new MBeanInfo(mbeanInfo.getClassName(), mbeanInfo.getDescription(), mbeanInfo.getAttributes(),
+                    mbeanInfo.getConstructors(), mbeanInfo.getOperations(), notificationInfo);
+        }
+        return mbeanInfo;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/internal/model/Capability.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/model/Capability.java b/features/core/src/main/java/org/apache/karaf/features/internal/model/Capability.java
new file mode 100644
index 0000000..b866151
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/model/Capability.java
@@ -0,0 +1,91 @@
+/*
+ * 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.model;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlSchemaType;
+import javax.xml.bind.annotation.XmlType;
+import javax.xml.bind.annotation.XmlValue;
+
+import org.apache.karaf.features.BundleInfo;
+
+
+/**
+ * 
+ * Additional capability for a feature.
+ *             
+ * 
+ * <p>Java class for bundle complex type.
+ * 
+ * <p>The following schema fragment specifies the expected content contained within this class.
+ * 
+ * <pre>
+ * &lt;complexType name="capability">
+ *   &lt;simpleContent>
+ *     &lt;extension base="&lt;http://www.w3.org/2001/XMLSchema>string">
+ *     &lt;/extension>
+ *   &lt;/simpleContent>
+ * &lt;/complexType>
+ * </pre>
+ * 
+ * 
+ */
+@XmlAccessorType(XmlAccessType.FIELD)
+@XmlType(name = "capability", propOrder = {
+    "value"
+})
+public class Capability implements org.apache.karaf.features.Capability {
+
+    @XmlValue
+    protected String value;
+
+
+    public Capability() {
+    }
+
+    public Capability(String value) {
+        this.value = value;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        Capability bundle = (Capability) o;
+
+        if (value != null ? !value.equals(bundle.value) : bundle.value != null) return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = value != null ? value.hashCode() : 0;
+        return result;
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/internal/model/Feature.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/model/Feature.java b/features/core/src/main/java/org/apache/karaf/features/internal/model/Feature.java
index 866c4f7..46580da 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/model/Feature.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/model/Feature.java
@@ -51,6 +51,9 @@ import javax.xml.bind.annotation.XmlType;
  *         &lt;element name="configfile" type="{http://karaf.apache.org/xmlns/features/v1.0.0}configFile" maxOccurs="unbounded" minOccurs="0"/>
  *         &lt;element name="feature" type="{http://karaf.apache.org/xmlns/features/v1.0.0}dependency" maxOccurs="unbounded" minOccurs="0"/>
  *         &lt;element name="bundle" type="{http://karaf.apache.org/xmlns/features/v1.0.0}bundle" maxOccurs="unbounded" minOccurs="0"/>
+ *         &lt;element name="conditional" type="{http://karaf.apache.org/xmlns/features/v1.0.0}conditional" maxOccurs="unbounded" minOccurs="0"/>
+ *         &lt;element name="capability" type="{http://karaf.apache.org/xmlns/features/v1.0.0}capability" maxOccurs="unbounded" minOccurs="0"/>
+ *         &lt;element name="requirement" type="{http://karaf.apache.org/xmlns/features/v1.0.0}requirement" maxOccurs="unbounded" minOccurs="0"/>
  *       &lt;/sequence>
  *       &lt;attribute name="name" use="required" type="{http://karaf.apache.org/xmlns/features/v1.0.0}featureName" />
  *       &lt;attribute name="version" type="{http://www.w3.org/2001/XMLSchema}string" default="0.0.0" />
@@ -70,10 +73,12 @@ import javax.xml.bind.annotation.XmlType;
     "configfile",
     "feature",
     "bundle",
-    "conditional"
+    "conditional",
+    "capability",
+    "requirement"
 })
 public class Feature extends Content implements org.apache.karaf.features.Feature {
-    public static String SPLIT_FOR_NAME_AND_VERSION = "_split_for_name_and_version_";
+    public static String SPLIT_FOR_NAME_AND_VERSION = "/";
     public static String DEFAULT_VERSION = "0.0.0";
 
 
@@ -93,6 +98,8 @@ public class Feature extends Content implements org.apache.karaf.features.Featur
     @XmlAttribute
     protected String region;
     protected List<Conditional> conditional;
+    protected List<Capability> capability;
+    protected List<Requirement> requirement;
 
     public Feature() {
     }
@@ -108,7 +115,7 @@ public class Feature extends Content implements org.apache.karaf.features.Featur
 
 
     public static org.apache.karaf.features.Feature valueOf(String str) {
-    	if (str.indexOf(SPLIT_FOR_NAME_AND_VERSION) >= 0) {
+    	if (str.contains(SPLIT_FOR_NAME_AND_VERSION)) {
     		String strName = str.substring(0, str.indexOf(SPLIT_FOR_NAME_AND_VERSION));
         	String strVersion = str.substring(str.indexOf(SPLIT_FOR_NAME_AND_VERSION)
         			+ SPLIT_FOR_NAME_AND_VERSION.length(), str.length());
@@ -122,7 +129,7 @@ public class Feature extends Content implements org.apache.karaf.features.Featur
 
 
     public String getId() {
-        return getName() + "-" + getVersion();
+        return getName() + SPLIT_FOR_NAME_AND_VERSION + getVersion();
     }
 
     /**
@@ -159,7 +166,7 @@ public class Feature extends Content implements org.apache.karaf.features.Featur
      */
     public String getVersion() {
         if (version == null) {
-            return "0.0.0";
+            return DEFAULT_VERSION;
         } else {
             return version;
         }
@@ -300,7 +307,7 @@ public class Feature extends Content implements org.apache.karaf.features.Featur
      * <p/>
      * <p/>
      * Objects of the following type(s) are allowed in the list
-     * {@link Dependency }
+     * {@link Conditional }
      */
     public List<Conditional> getConditional() {
         if (conditional == null) {
@@ -309,9 +316,22 @@ public class Feature extends Content implements org.apache.karaf.features.Featur
         return this.conditional;
     }
 
+    public List<Capability> getCapabilities() {
+        if (capability == null) {
+            capability = new ArrayList<Capability>();
+        }
+        return this.capability;
+    }
+
+    public List<Requirement> getRequirements() {
+        if (requirement == null) {
+            requirement = new ArrayList<Requirement>();
+        }
+        return this.requirement;
+    }
+
     public String toString() {
-    	String ret = getName() + SPLIT_FOR_NAME_AND_VERSION + getVersion();
-    	return ret;
+    	return getId();
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/internal/model/Requirement.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/model/Requirement.java b/features/core/src/main/java/org/apache/karaf/features/internal/model/Requirement.java
new file mode 100644
index 0000000..f7b5775
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/model/Requirement.java
@@ -0,0 +1,87 @@
+/*
+ * 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.model;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlType;
+import javax.xml.bind.annotation.XmlValue;
+
+
+/**
+ * 
+ * Additional requirement for a feature.
+ *             
+ * 
+ * <p>Java class for bundle complex type.
+ * 
+ * <p>The following schema fragment specifies the expected content contained within this class.
+ * 
+ * <pre>
+ * &lt;complexType name="capability">
+ *   &lt;simpleContent>
+ *     &lt;extension base="&lt;http://www.w3.org/2001/XMLSchema>string">
+ *     &lt;/extension>
+ *   &lt;/simpleContent>
+ * &lt;/complexType>
+ * </pre>
+ * 
+ * 
+ */
+@XmlAccessorType(XmlAccessType.FIELD)
+@XmlType(name = "requirement", propOrder = {
+    "value"
+})
+public class Requirement implements org.apache.karaf.features.Requirement {
+
+    @XmlValue
+    protected String value;
+
+
+    public Requirement() {
+    }
+
+    public Requirement(String value) {
+        this.value = value;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        Requirement bundle = (Requirement) o;
+
+        if (value != null ? !value.equals(bundle.value) : bundle.value != null) return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = value != null ? value.hashCode() : 0;
+        return result;
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/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 138366e..d51e5d5 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
@@ -17,22 +17,25 @@
 package org.apache.karaf.features.internal.osgi;
 
 import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
 import java.io.FileReader;
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
 import java.util.Dictionary;
 import java.util.Hashtable;
 import java.util.Properties;
 
-import javax.management.NotCompliantMBeanException;
-
 import org.apache.karaf.features.FeaturesListener;
 import org.apache.karaf.features.FeaturesService;
-import org.apache.karaf.features.internal.BootFeaturesInstaller;
-import org.apache.karaf.features.internal.BundleManager;
-import org.apache.karaf.features.internal.FeatureConfigInstaller;
-import org.apache.karaf.features.internal.FeatureFinder;
-import org.apache.karaf.features.internal.FeaturesServiceImpl;
-import org.apache.karaf.features.management.internal.FeaturesServiceMBeanImpl;
+import org.apache.karaf.features.internal.service.EventAdminListener;
+import org.apache.karaf.features.internal.service.FeatureConfigInstaller;
+import org.apache.karaf.features.internal.service.FeatureFinder;
+import org.apache.karaf.features.internal.service.BootFeaturesInstaller;
+import org.apache.karaf.features.internal.service.FeaturesServiceImpl;
+import org.apache.karaf.features.internal.service.StateStorage;
+import org.apache.karaf.features.internal.management.FeaturesServiceMBeanImpl;
 import org.apache.karaf.features.RegionsPersistence;
 import org.apache.karaf.util.tracker.BaseActivator;
 import org.apache.karaf.util.tracker.SingleServiceTracker;
@@ -50,6 +53,12 @@ public class Activator extends BaseActivator {
     private FeaturesServiceImpl featuresService;
     private SingleServiceTracker<RegionsPersistence> regionsTracker;
 
+    public Activator() {
+        // Special case here, as we don't want the activator to wait for current job to finish,
+        // else it would forbid the features service to refresh itself
+        setSchedulerStopTimeout(0);
+    }
+
     @Override
     protected void doOpen() throws Exception {
         trackService(URLStreamHandlerService.class, "(url.handler.protocol=mvn)");
@@ -67,7 +76,7 @@ public class Activator extends BaseActivator {
         updated((Dictionary) configuration);
     }
 
-    protected void doStart() throws NotCompliantMBeanException {
+    protected void doStart() throws Exception {
         ConfigurationAdmin configurationAdmin = getTrackedService(ConfigurationAdmin.class);
         URLStreamHandlerService mvnUrlHandler = getTrackedService(URLStreamHandlerService.class);
 
@@ -80,39 +89,62 @@ public class Activator extends BaseActivator {
         props.put(Constants.SERVICE_PID, "org.apache.karaf.features.repos");
         register(ManagedService.class, featureFinder, props);
 
-        final BundleManager bundleManager = new BundleManager(bundleContext);
-        regionsTracker = new SingleServiceTracker<RegionsPersistence>(bundleContext, RegionsPersistence.class,
-                new SingleServiceTracker.SingleServiceListener() {
-                    @Override
-                    public void serviceFound() {
-                        bundleManager.setRegionsPersistence(regionsTracker.getService());
-                    }
-                    @Override
-                    public void serviceLost() {
-                        serviceFound();
-                    }
-                    @Override
-                    public void serviceReplaced() {
-                        serviceFound();
-                    }
-                });
-        regionsTracker.open();
+        // TODO: region support
+//        final BundleManager bundleManager = new BundleManager(bundleContext);
+//        regionsTracker = new SingleServiceTracker<RegionsPersistence>(bundleContext, RegionsPersistence.class,
+//                new SingleServiceTracker.SingleServiceListener() {
+//                    @Override
+//                    public void serviceFound() {
+//                        bundleManager.setRegionsPersistence(regionsTracker.getService());
+//                    }
+//                    @Override
+//                    public void serviceLost() {
+//                        serviceFound();
+//                    }
+//                    @Override
+//                    public void serviceReplaced() {
+//                        serviceFound();
+//                    }
+//                });
+//        regionsTracker.open();
 
 
         FeatureConfigInstaller configInstaller = new FeatureConfigInstaller(configurationAdmin);
-        String featuresRepositories = getString("featuresRepositories", "");
-        boolean respectStartLvlDuringFeatureStartup = getBoolean("respectStartLvlDuringFeatureStartup", true);
-        boolean respectStartLvlDuringFeatureUninstall = getBoolean("respectStartLvlDuringFeatureUninstall", true);
-        long resolverTimeout = getLong("resolverTimeout", 5000);
+        // TODO: honor respectStartLvlDuringFeatureStartup and respectStartLvlDuringFeatureUninstall
+//        boolean respectStartLvlDuringFeatureStartup = getBoolean("respectStartLvlDuringFeatureStartup", true);
+//        boolean respectStartLvlDuringFeatureUninstall = getBoolean("respectStartLvlDuringFeatureUninstall", true);
         String overrides = getString("overrides", new File(System.getProperty("karaf.etc"), "overrides.properties").toString());
-        featuresService = new FeaturesServiceImpl(bundleManager, configInstaller);
-        featuresService.setUrls(featuresRepositories);
-        featuresService.setRespectStartLvlDuringFeatureStartup(respectStartLvlDuringFeatureStartup);
-        featuresService.setRespectStartLvlDuringFeatureUninstall(respectStartLvlDuringFeatureUninstall);
-        featuresService.setResolverTimeout(resolverTimeout);
-        featuresService.setOverrides(overrides);
-        featuresService.setFeatureFinder(featureFinder);
-        featuresService.start();
+        StateStorage stateStorage = new StateStorage() {
+            @Override
+            protected InputStream getInputStream() throws IOException {
+                File file = bundleContext.getDataFile("FeaturesServiceState.properties");
+                if (file.exists()) {
+                    return new FileInputStream(file);
+                } else {
+                    return null;
+                }
+            }
+
+            @Override
+            protected OutputStream getOutputStream() throws IOException {
+                File file = bundleContext.getDataFile("FeaturesServiceState.properties");
+                return new FileOutputStream(file);
+            }
+        };
+        EventAdminListener eventAdminListener;
+        try {
+            eventAdminListener = new EventAdminListener(bundleContext);
+        } catch (Throwable t) {
+            eventAdminListener = null;
+        }
+        featuresService = new FeaturesServiceImpl(
+                                bundleContext.getBundle(),
+                                bundleContext.getBundle(0).getBundleContext(),
+                                stateStorage,
+                                featureFinder,
+                                eventAdminListener,
+                                configInstaller,
+                                overrides);
         register(FeaturesService.class, featuresService);
 
         featuresListenerTracker = new ServiceTracker<FeaturesListener, FeaturesListener>(
@@ -135,9 +167,12 @@ public class Activator extends BaseActivator {
         );
         featuresListenerTracker.open();
 
+        String featuresRepositories = getString("featuresRepositories", "");
         String featuresBoot = getString("featuresBoot", "");
         boolean featuresBootAsynchronous = getBoolean("featuresBootAsynchronous", false);
-        BootFeaturesInstaller bootFeaturesInstaller = new BootFeaturesInstaller(bundleContext, featuresService, featuresBoot, featuresBootAsynchronous);
+        BootFeaturesInstaller bootFeaturesInstaller = new BootFeaturesInstaller(
+                bundleContext, featuresService,
+                featuresRepositories, featuresBoot, featuresBootAsynchronous);
         bootFeaturesInstaller.start();
 
         FeaturesServiceMBeanImpl featuresServiceMBean = new FeaturesServiceMBeanImpl();
@@ -157,7 +192,6 @@ public class Activator extends BaseActivator {
         }
         super.doStop();
         if (featuresService != null) {
-            featuresService.stop();
             featuresService = null;
         }
     }

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/internal/repository/AggregateRepository.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/repository/AggregateRepository.java b/features/core/src/main/java/org/apache/karaf/features/internal/repository/AggregateRepository.java
new file mode 100644
index 0000000..0d5e83d
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/repository/AggregateRepository.java
@@ -0,0 +1,55 @@
+/*
+ * 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.repository;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.osgi.resource.Capability;
+import org.osgi.resource.Requirement;
+import org.osgi.service.repository.Repository;
+
+public class AggregateRepository implements Repository {
+
+    private final Collection<Repository> repositories;
+
+    public AggregateRepository(Collection<Repository> repositories) {
+        this.repositories = repositories;
+    }
+
+    @Override
+    public Map<Requirement, Collection<Capability>> findProviders(Collection<? extends Requirement> requirements) {
+        Map<Requirement, Collection<Capability>> result = new HashMap<Requirement, Collection<Capability>>();
+        for (Requirement requirement : requirements) {
+            List<Capability> caps = new ArrayList<Capability>();
+            for (Repository repository : repositories) {
+                Map<Requirement, Collection<Capability>> resMap =
+                        repository.findProviders(Collections.singleton(requirement));
+                Collection<Capability> res = resMap != null ? resMap.get(requirement) : null;
+                if (res != null) {
+                    caps.addAll(res);
+                }
+            }
+            result.put(requirement, caps);
+        }
+        return result;
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/internal/repository/BaseRepository.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/repository/BaseRepository.java b/features/core/src/main/java/org/apache/karaf/features/internal/repository/BaseRepository.java
new file mode 100644
index 0000000..c4c0d16
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/repository/BaseRepository.java
@@ -0,0 +1,86 @@
+/*
+ * 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.repository;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.karaf.features.internal.resolver.CapabilitySet;
+import org.apache.karaf.features.internal.resolver.RequirementImpl;
+import org.apache.karaf.features.internal.resolver.SimpleFilter;
+import org.osgi.framework.Constants;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Requirement;
+import org.osgi.resource.Resource;
+import org.osgi.service.repository.Repository;
+
+/**
+ */
+public class BaseRepository implements Repository {
+
+    protected final List<Resource> resources;
+    protected final Map<String, CapabilitySet> capSets;
+
+    public BaseRepository() {
+        this.resources = new ArrayList<Resource>();
+        this.capSets = new HashMap<String, CapabilitySet>();
+    }
+
+    protected void addResource(Resource resource) {
+        for (Capability cap : resource.getCapabilities(null)) {
+            String ns = cap.getNamespace();
+            CapabilitySet set = capSets.get(ns);
+            if (set == null) {
+                set = new CapabilitySet(Collections.singletonList(ns));
+                capSets.put(ns, set);
+            }
+            set.addCapability(cap);
+        }
+        resources.add(resource);
+    }
+
+    public List<Resource> getResources() {
+        return resources;
+    }
+
+    @Override
+    public Map<Requirement, Collection<Capability>> findProviders(Collection<? extends Requirement> requirements) {
+        Map<Requirement, Collection<Capability>> result = new HashMap<Requirement, Collection<Capability>>();
+        for (Requirement requirement : requirements) {
+            CapabilitySet set = capSets.get(requirement.getNamespace());
+            if (set != null) {
+                SimpleFilter sf;
+                if (requirement instanceof RequirementImpl) {
+                    sf = ((RequirementImpl) requirement).getFilter();
+                } else {
+                    String filter = requirement.getDirectives().get(Constants.FILTER_DIRECTIVE);
+                    sf = (filter != null)
+                            ? SimpleFilter.parse(filter)
+                            : new SimpleFilter(null, null, SimpleFilter.MATCH_ALL);
+                }
+                result.put(requirement, set.match(sf, true));
+            } else {
+                result.put(requirement, Collections.<Capability>emptyList());
+            }
+        }
+        return result;
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/internal/repository/CacheRepository.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/repository/CacheRepository.java b/features/core/src/main/java/org/apache/karaf/features/internal/repository/CacheRepository.java
new file mode 100644
index 0000000..7916821
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/repository/CacheRepository.java
@@ -0,0 +1,59 @@
+/*
+ * 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.repository;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.osgi.resource.Capability;
+import org.osgi.resource.Requirement;
+import org.osgi.service.repository.Repository;
+
+public class CacheRepository implements Repository {
+
+    private final Repository repository;
+    private final Map<Requirement, Collection<Capability>> cache =
+            new ConcurrentHashMap<Requirement, Collection<Capability>>();
+
+    public CacheRepository(Repository repository) {
+        this.repository = repository;
+    }
+
+    @Override
+    public Map<Requirement, Collection<Capability>> findProviders(Collection<? extends Requirement> requirements) {
+        List<Requirement> missing = new ArrayList<Requirement>();
+        Map<Requirement, Collection<Capability>> result = new HashMap<Requirement, Collection<Capability>>();
+        for (Requirement requirement : requirements) {
+            Collection<Capability> caps = cache.get(requirement);
+            if (caps == null) {
+                missing.add(requirement);
+            } else {
+                result.put(requirement, caps);
+            }
+        }
+        Map<Requirement, Collection<Capability>> newCache = repository.findProviders(missing);
+        for (Requirement requirement : newCache.keySet()) {
+            cache.put(requirement, newCache.get(requirement));
+            result.put(requirement, newCache.get(requirement));
+        }
+        return result;
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/internal/repository/HttpMetadataProvider.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/repository/HttpMetadataProvider.java b/features/core/src/main/java/org/apache/karaf/features/internal/repository/HttpMetadataProvider.java
new file mode 100644
index 0000000..1aecef1
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/repository/HttpMetadataProvider.java
@@ -0,0 +1,88 @@
+/*
+ * 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.repository;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.Map;
+import java.util.zip.GZIPInputStream;
+
+import org.apache.karaf.features.internal.util.JsonReader;
+
+/**
+ */
+public class HttpMetadataProvider implements MetadataProvider {
+
+    public static final String HEADER_ACCEPT_ENCODING = "Accept-Encoding";
+    public static final String HEADER_CONTENT_ENCODING = "Content-Encoding";
+    public static final String GZIP = "gzip";
+
+    private final String url;
+    private long lastModified;
+    private Map<String, Map<String, String>> metadatas;
+
+    public HttpMetadataProvider(String url) {
+        this.url = url;
+    }
+
+    @Override
+    public long getLastModified() {
+        return lastModified;
+    }
+
+    @Override
+    public Map<String, Map<String, String>> getMetadatas() {
+        try {
+            HttpURLConnection.setFollowRedirects(false);
+            HttpURLConnection con = (HttpURLConnection) new URL(url).openConnection();
+            if (lastModified > 0) {
+                con.setIfModifiedSince(lastModified);
+            }
+            con.setRequestProperty(HEADER_ACCEPT_ENCODING, GZIP);
+            if (con.getResponseCode() == HttpURLConnection.HTTP_OK) {
+                lastModified = con.getLastModified();
+                InputStream is = con.getInputStream();
+                if (GZIP.equals(con.getHeaderField(HEADER_CONTENT_ENCODING))) {
+                    is = new GZIPInputStream(is);
+                }
+                metadatas = verify(JsonReader.read(is));
+            } else if (con.getResponseCode() != HttpURLConnection.HTTP_NOT_MODIFIED) {
+                throw new IOException("Unexpected http response: "
+                        + con.getResponseCode() + " " + con.getResponseMessage());
+            }
+            return metadatas;
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private Map<String, Map<String, String>> verify(Object value) {
+        Map<?,?> obj = Map.class.cast(value);
+        for (Map.Entry<?,?> entry : obj.entrySet()) {
+            String.class.cast(entry.getKey());
+            Map<?,?> child = Map.class.cast(entry.getValue());
+            for (Map.Entry<?,?> ce : child.entrySet()) {
+                String.class.cast(ce.getKey());
+                String.class.cast(ce.getValue());
+            }
+        }
+        return (Map<String, Map<String, String>>) obj;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/internal/repository/MetadataProvider.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/repository/MetadataProvider.java b/features/core/src/main/java/org/apache/karaf/features/internal/repository/MetadataProvider.java
new file mode 100644
index 0000000..9ac54a1
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/repository/MetadataProvider.java
@@ -0,0 +1,29 @@
+/*
+ * 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.repository;
+
+import java.util.Map;
+
+/**
+ */
+public interface MetadataProvider {
+
+    long getLastModified();
+
+    Map<String, Map<String, String>> getMetadatas();
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/internal/repository/MetadataRepository.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/repository/MetadataRepository.java b/features/core/src/main/java/org/apache/karaf/features/internal/repository/MetadataRepository.java
new file mode 100644
index 0000000..2d4fbba
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/repository/MetadataRepository.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.features.internal.repository;
+
+import java.util.Map;
+
+import org.apache.karaf.features.internal.resolver.ResourceBuilder;
+import org.osgi.resource.Resource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ */
+public class MetadataRepository extends BaseRepository {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(MetadataRepository.class);
+
+    public MetadataRepository(MetadataProvider provider) {
+        Map<String, Map<String, String>> metadatas = provider.getMetadatas();
+        for (Map.Entry<String, Map<String, String>> metadata : metadatas.entrySet()) {
+            try {
+                Resource resource = ResourceBuilder.build(metadata.getKey(), metadata.getValue());
+                addResource(resource);
+            } catch (Exception e) {
+                LOGGER.info("Unable to build resource for " + metadata.getKey(), e);
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/internal/repository/StaticRepository.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/repository/StaticRepository.java b/features/core/src/main/java/org/apache/karaf/features/internal/repository/StaticRepository.java
new file mode 100644
index 0000000..f289c8d
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/repository/StaticRepository.java
@@ -0,0 +1,33 @@
+/*
+ * 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.repository;
+
+import java.util.Collection;
+
+import org.osgi.resource.Resource;
+
+/**
+ */
+public class StaticRepository extends BaseRepository {
+
+    public StaticRepository(Collection<Resource> resources) {
+        for (Resource resource : resources) {
+            addResource(resource);
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/internal/resolver/BaseClause.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/resolver/BaseClause.java b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/BaseClause.java
new file mode 100644
index 0000000..0653398
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/BaseClause.java
@@ -0,0 +1,114 @@
+/*
+ * 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.resolver;
+
+import java.util.Map;
+
+import org.osgi.framework.Version;
+import org.osgi.resource.Resource;
+
+/**
+ */
+public abstract class BaseClause {
+
+    public abstract Resource getResource();
+
+    public abstract String getNamespace();
+
+    public abstract Map<String, String> getDirectives();
+
+    public abstract Map<String, Object> getAttributes();
+
+    @Override
+    public String toString() {
+        return toString(getResource(), getNamespace(), getAttributes(), getDirectives());
+    }
+
+    public static String toString(Resource res, String namespace, Map<String, Object> attrs, Map<String, String> dirs) {
+        StringBuilder sb = new StringBuilder();
+        if (res != null) {
+            sb.append("[").append(res).append("] ");
+        }
+        sb.append(namespace);
+        for (String key : attrs.keySet()) {
+            sb.append("; ");
+            append(sb, key, attrs.get(key), true);
+        }
+        for (String key : dirs.keySet()) {
+            sb.append("; ");
+            append(sb, key, dirs.get(key), false);
+        }
+        return sb.toString();
+    }
+
+    private static void append(StringBuilder sb, String key, Object val, boolean attribute) {
+        sb.append(key);
+        if (val instanceof Version) {
+            sb.append(":Version=");
+            sb.append(val);
+        } else if (val instanceof Long) {
+            sb.append(":Long=");
+            sb.append(val);
+        } else if (val instanceof Double) {
+            sb.append(":Double=");
+            sb.append(val);
+        } else if (val instanceof Iterable) {
+            Iterable it = (Iterable) val;
+            String scalar = null;
+            for (Object o : it) {
+                String ts;
+                if (o instanceof String) {
+                    ts = "String";
+                } else if (o instanceof Long) {
+                    ts = "Long";
+                } else if (o instanceof Double) {
+                    ts = "Double";
+                } else if (o instanceof Version) {
+                    ts = "Version";
+                } else {
+                    throw new IllegalArgumentException("Unsupported scalar type: " + o);
+                }
+                if (scalar == null) {
+                    scalar = ts;
+                } else if (!scalar.equals(ts)) {
+                    throw new IllegalArgumentException("Unconsistent list type for attribute " + key);
+                }
+            }
+            sb.append(":List<").append(scalar).append(">=");
+            sb.append("\"");
+            boolean first = true;
+            for (Object o : it) {
+                if (first) {
+                    first = false;
+                } else {
+                    sb.append(",");
+                }
+                sb.append(o.toString().replace("\"", "\\\"").replace(",", "\\,"));
+            }
+            sb.append("\"");
+        } else {
+            sb.append(attribute ? "=" : ":=");
+            String s = val.toString();
+            if (s.matches("[0-9a-zA-Z_\\-.]*")) {
+                sb.append(s);
+            } else {
+                sb.append("\"").append(s.replace("\"", "\\\\")).append("\"");
+            }
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/internal/resolver/CandidateComparator.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/resolver/CandidateComparator.java b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/CandidateComparator.java
new file mode 100644
index 0000000..ad4cc85
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/CandidateComparator.java
@@ -0,0 +1,129 @@
+/*
+ * 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.resolver;
+
+import java.util.Comparator;
+
+import org.osgi.framework.Version;
+import org.osgi.framework.namespace.BundleNamespace;
+import org.osgi.framework.namespace.PackageNamespace;
+import org.osgi.framework.wiring.BundleCapability;
+import org.osgi.resource.Capability;
+
+public class CandidateComparator implements Comparator<Capability>
+{
+    public int compare(Capability cap1, Capability cap2)
+    {
+        int c = 0;
+        // Always prefer system bundle
+        if (cap1 instanceof BundleCapability && !(cap2 instanceof BundleCapability)) {
+            c = -1;
+        } else if (!(cap1 instanceof BundleCapability) && cap2 instanceof BundleCapability) {
+            c = 1;
+        }
+        // Compare revision capabilities.
+        if ((c == 0) && cap1.getNamespace().equals(BundleNamespace.BUNDLE_NAMESPACE))
+        {
+            c = ((Comparable) cap1.getAttributes().get(BundleNamespace.BUNDLE_NAMESPACE))
+                    .compareTo(cap2.getAttributes().get(BundleNamespace.BUNDLE_NAMESPACE));
+            if (c == 0)
+            {
+                Version v1 = (!cap1.getAttributes().containsKey(BundleNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE))
+                        ? Version.emptyVersion
+                        : (Version) cap1.getAttributes().get(BundleNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE);
+                Version v2 = (!cap2.getAttributes().containsKey(BundleNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE))
+                        ? Version.emptyVersion
+                        : (Version) cap2.getAttributes().get(BundleNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE);
+                // Compare these in reverse order, since we want
+                // highest version to have priority.
+                c = compareVersions(v2, v1);
+            }
+        }
+        // Compare package capabilities.
+        else if ((c == 0) && cap1.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE))
+        {
+            c = ((Comparable) cap1.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE))
+                    .compareTo(cap2.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE));
+            if (c == 0)
+            {
+                Version v1 = (!cap1.getAttributes().containsKey(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE))
+                        ? Version.emptyVersion
+                        : (Version) cap1.getAttributes().get(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE);
+                Version v2 = (!cap2.getAttributes().containsKey(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE))
+                        ? Version.emptyVersion
+                        : (Version) cap2.getAttributes().get(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE);
+                // Compare these in reverse order, since we want
+                // highest version to have priority.
+                c = compareVersions(v2, v1);
+                // if same version, rather compare on the bundle version
+                if (c == 0)
+                {
+                    v1 = (!cap1.getAttributes().containsKey(BundleNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE))
+                            ? Version.emptyVersion
+                            : (Version) cap1.getAttributes().get(BundleNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE);
+                    v2 = (!cap2.getAttributes().containsKey(BundleNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE))
+                            ? Version.emptyVersion
+                            : (Version) cap2.getAttributes().get(BundleNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE);
+                    // Compare these in reverse order, since we want
+                    // highest version to have priority.
+                    c = compareVersions(v2, v1);
+                }
+            }
+        }
+        // Compare feature capabilities
+        else if ((c == 0) && cap1.getNamespace().equals(FeatureNamespace.FEATURE_NAMESPACE))
+        {
+            c = ((Comparable) cap1.getAttributes().get(FeatureNamespace.FEATURE_NAMESPACE))
+                    .compareTo(cap2.getAttributes().get(FeatureNamespace.FEATURE_NAMESPACE));
+            if (c == 0)
+            {
+                Version v1 = (!cap1.getAttributes().containsKey(FeatureNamespace.CAPABILITY_VERSION_ATTRIBUTE))
+                        ? Version.emptyVersion
+                        : (Version) cap1.getAttributes().get(FeatureNamespace.CAPABILITY_VERSION_ATTRIBUTE);
+                Version v2 = (!cap2.getAttributes().containsKey(FeatureNamespace.CAPABILITY_VERSION_ATTRIBUTE))
+                        ? Version.emptyVersion
+                        : (Version) cap2.getAttributes().get(FeatureNamespace.CAPABILITY_VERSION_ATTRIBUTE);
+                // Compare these in reverse order, since we want
+                // highest version to have priority.
+                c = compareVersions(v2, v1);
+            }
+        }
+        return c;
+    }
+
+    private int compareVersions(Version v1, Version v2) {
+        int c = v1.getMajor() - v2.getMajor();
+        if (c != 0) {
+            return c;
+        }
+        c = v1.getMinor() - v2.getMinor();
+        if (c != 0) {
+            return c;
+        }
+        c = v1.getMicro() - v2.getMicro();
+        if (c != 0) {
+            return c;
+        }
+        String q1 = cleanQualifierForComparison(v1.getQualifier());
+        String q2 = cleanQualifierForComparison(v2.getQualifier());
+        return q1.compareTo(q2);
+    }
+
+    private String cleanQualifierForComparison(String qualifier) {
+        return qualifier.replaceAll("(redhat-[0-9]{3})([0-9]{3})", "$1-$2");
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/internal/resolver/CapabilityImpl.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/resolver/CapabilityImpl.java b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/CapabilityImpl.java
new file mode 100644
index 0000000..bfe9b40
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/CapabilityImpl.java
@@ -0,0 +1,165 @@
+/*
+ * 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.resolver;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringTokenizer;
+
+import org.osgi.framework.Constants;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Resource;
+
+public class CapabilityImpl extends BaseClause implements Capability {
+
+    private final Resource m_resource;
+    private final String m_namespace;
+    private final Map<String, String> m_dirs;
+    private final Map<String, Object> m_attrs;
+    private final List<String> m_uses;
+    private final List<List<String>> m_includeFilter;
+    private final List<List<String>> m_excludeFilter;
+    private final Set<String> m_mandatory;
+
+    public CapabilityImpl(Capability capability) {
+        this(null, capability.getNamespace(), capability.getDirectives(), capability.getAttributes());
+    }
+
+    public CapabilityImpl(Resource resource, String namespace,
+                          Map<String, String> dirs, Map<String, Object> attrs) {
+        m_namespace = namespace;
+        m_resource = resource;
+        m_dirs = dirs;
+        m_attrs = attrs;
+
+        // Find all export directives: uses, mandatory, include, and exclude.
+
+        List<String> uses = Collections.emptyList();
+        String value = m_dirs.get(Constants.USES_DIRECTIVE);
+        if (value != null) {
+            // Parse these uses directive.
+            StringTokenizer tok = new StringTokenizer(value, ",");
+            uses = new ArrayList<String>(tok.countTokens());
+            while (tok.hasMoreTokens()) {
+                uses.add(tok.nextToken().trim());
+            }
+        }
+        m_uses = uses;
+
+        value = m_dirs.get(Constants.INCLUDE_DIRECTIVE);
+        if (value != null) {
+            List<String> filters = ResourceBuilder.parseDelimitedString(value, ",");
+            m_includeFilter = new ArrayList<List<String>>(filters.size());
+            for (String filter : filters) {
+                List<String> substrings = SimpleFilter.parseSubstring(filter);
+                m_includeFilter.add(substrings);
+            }
+        } else {
+            m_includeFilter = null;
+        }
+
+        value = m_dirs.get(Constants.EXCLUDE_DIRECTIVE);
+        if (value != null) {
+            List<String> filters = ResourceBuilder.parseDelimitedString(value, ",");
+            m_excludeFilter = new ArrayList<List<String>>(filters.size());
+            for (String filter : filters) {
+                List<String> substrings = SimpleFilter.parseSubstring(filter);
+                m_excludeFilter.add(substrings);
+            }
+        } else {
+            m_excludeFilter = null;
+        }
+
+        Set<String> mandatory = Collections.emptySet();
+        value = m_dirs.get(Constants.MANDATORY_DIRECTIVE);
+        if (value != null) {
+            List<String> names = ResourceBuilder.parseDelimitedString(value, ",");
+            mandatory = new HashSet<String>(names.size());
+            for (String name : names) {
+                // If attribute exists, then record it as mandatory.
+                if (m_attrs.containsKey(name)) {
+                    mandatory.add(name);
+                }
+                // Otherwise, report an error.
+                else {
+                    throw new IllegalArgumentException("Mandatory attribute '" + name + "' does not exist.");
+                }
+            }
+        }
+        m_mandatory = mandatory;
+    }
+
+    public Resource getResource() {
+        return m_resource;
+    }
+
+    public String getNamespace() {
+        return m_namespace;
+    }
+
+    public Map<String, String> getDirectives() {
+        return m_dirs;
+    }
+
+    public Map<String, Object> getAttributes() {
+        return m_attrs;
+    }
+
+    public boolean isAttributeMandatory(String name) {
+        return !m_mandatory.isEmpty() && m_mandatory.contains(name);
+    }
+
+    public List<String> getUses() {
+        return m_uses;
+    }
+
+    public boolean isIncluded(String name) {
+        if ((m_includeFilter == null) && (m_excludeFilter == null)) {
+            return true;
+        }
+
+        // Get the class name portion of the target class.
+        String className = getClassName(name);
+
+        // If there are no include filters then all classes are included
+        // by default, otherwise try to find one match.
+        boolean included = (m_includeFilter == null);
+        for (int i = 0; !included && m_includeFilter != null && i < m_includeFilter.size(); i++) {
+            included = SimpleFilter.compareSubstring(m_includeFilter.get(i), className);
+        }
+
+        // If there are no exclude filters then no classes are excluded
+        // by default, otherwise try to find one match.
+        boolean excluded = false;
+        for (int i = 0; (!excluded) && (m_excludeFilter != null) && (i < m_excludeFilter.size()); i++) {
+            excluded = SimpleFilter.compareSubstring(m_excludeFilter.get(i), className);
+        }
+        return included && !excluded;
+    }
+
+    private static String getClassName(String className) {
+        if (className == null) {
+            className = "";
+        }
+        return (className.lastIndexOf('.') < 0) ? "" : className.substring(className.lastIndexOf('.') + 1);
+    }
+
+}


[08/13] [KARAF-2888] New FeaturesService based on the real OSGi resolver

Posted by gn...@apache.org.
http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/internal/service/BootFeaturesInstaller.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/BootFeaturesInstaller.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/BootFeaturesInstaller.java
new file mode 100644
index 0000000..a3ed213
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/BootFeaturesInstaller.java
@@ -0,0 +1,193 @@
+/*
+ * 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.service;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.karaf.features.BootFinished;
+import org.apache.karaf.features.Feature;
+import org.apache.karaf.features.FeaturesService;
+import org.osgi.framework.BundleContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class BootFeaturesInstaller {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(BootFeaturesInstaller.class);
+
+    public static String VERSION_PREFIX = "version=";
+
+    private final FeaturesServiceImpl featuresService;
+    private final BundleContext bundleContext;
+    private final String repositories;
+    private final String features;
+    private final boolean asynchronous;
+
+    /**
+     *
+     * @param features list of boot features separated by comma. Optionally contains ;version=x.x.x to specify a specific feature version
+     */
+    public BootFeaturesInstaller(BundleContext bundleContext,
+                                 FeaturesServiceImpl featuresService,
+                                 String repositories,
+                                 String features,
+                                 boolean asynchronous) {
+        this.bundleContext = bundleContext;
+        this.featuresService = featuresService;
+        this.repositories = repositories;
+        this.features = features;
+        this.asynchronous = asynchronous;
+    }
+
+    /**
+     * Install boot features
+     */
+    public void start() {
+        if (featuresService.isBootDone()) {
+            publishBootFinished();
+            return;
+        }
+        if (asynchronous) {
+            new Thread("Initial Features Provisioning") {
+                public void run() {
+                    installBootFeatures();
+                }
+            }.start();
+        } else {
+            installBootFeatures();
+        }
+    }
+
+    protected void installBootFeatures() {
+        try {
+            for (String repo : repositories.split(",")) {
+                repo = repo.trim();
+                if (!repo.isEmpty()) {
+                    try {
+                        featuresService.addRepository(URI.create(repo));
+                    } catch (Exception e) {
+                        LOGGER.error("Error installing boot feature repository " + repo);
+                    }
+                }
+            }
+
+            List<Set<String>> stagedFeatureNames = parseBootFeatures(features);
+            List<Set<Feature>> stagedFeatures = toFeatureSetList(stagedFeatureNames);
+            for (Set<Feature> features : stagedFeatures) {
+                featuresService.installFeatures(features, EnumSet.of(FeaturesService.Option.NoCleanIfFailure, FeaturesService.Option.ContinueBatchOnFailure));
+            }
+            featuresService.bootDone();
+            publishBootFinished();
+        } catch (Exception e) {
+            // Special handling in case the bundle has been refreshed.
+            // In such a case, simply exits without logging any exception
+            // as the restart should cause the feature service to finish
+            // the work.
+            if (e instanceof IllegalStateException) {
+                try {
+                    bundleContext.getBundle();
+                } catch (IllegalStateException ies) {
+                    return;
+                }
+            }
+            LOGGER.error("Error installing boot features", e);
+        }
+    }
+
+    private List<Set<Feature>> toFeatureSetList(List<Set<String>> stagedFeatures) {
+        ArrayList<Set<Feature>> result = new ArrayList<Set<Feature>>();
+        for (Set<String> features : stagedFeatures) {
+            HashSet<Feature> featureSet = new HashSet<Feature>();
+            for (String featureName : features) {
+                try {
+                    Feature feature = getFeature(featureName);
+                    if (feature == null) {
+                        LOGGER.error("Error Boot feature " + featureName + " not found");
+                    } else {
+                        featureSet.add(feature);
+                    }
+                } catch (Exception e) {
+                    LOGGER.error("Error getting feature for feature string " + featureName, e);
+                }
+            }
+            result.add(featureSet);
+        }
+        return result;
+    }
+
+    /**
+     *
+     * @param featureSt either feature name or <featurename>;version=<version>
+     * @return feature matching the feature string
+     * @throws Exception
+     */
+    private Feature getFeature(String featureSt) throws Exception {
+        String[] parts = featureSt.trim().split(";");
+        String featureName = parts[0];
+        String featureVersion = null;
+        for (String part : parts) {
+            // if the part starts with "version=" it contains the version info
+            if (part.startsWith(VERSION_PREFIX)) {
+                featureVersion = part.substring(VERSION_PREFIX.length());
+            }
+        }
+        if (featureVersion == null) {
+            // no version specified - use default version
+            featureVersion = org.apache.karaf.features.internal.model.Feature.DEFAULT_VERSION;
+        }
+        return featuresService.getFeature(featureName, featureVersion);
+    }
+
+    protected List<Set<String>> parseBootFeatures(String bootFeatures) {
+        Pattern pattern = Pattern.compile("(\\((.+))\\),|.+");
+        Matcher matcher = pattern.matcher(bootFeatures);
+        List<Set<String>> result = new ArrayList<Set<String>>();
+        while (matcher.find()) {
+            String group = matcher.group(2) != null ? matcher.group(2) : matcher.group();
+            result.add(parseFeatureList(group));
+        }
+        return result;
+    }
+
+    protected Set<String> parseFeatureList(String group) {
+        HashSet<String> features = new HashSet<String>();
+        for (String feature : Arrays.asList(group.trim().split("\\s*,\\s*"))) {
+            if (feature.length() > 0) {
+                features.add(feature);
+            }
+        }
+        return features;
+    }
+
+    private void publishBootFinished() {
+        if (bundleContext != null) {
+            BootFinished bootFinished = new BootFinished() {};
+            bundleContext.registerService(BootFinished.class, bootFinished, new Hashtable<String, String>());
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/internal/service/EventAdminListener.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/EventAdminListener.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/EventAdminListener.java
new file mode 100644
index 0000000..b6eaae5
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/EventAdminListener.java
@@ -0,0 +1,91 @@
+/*
+ * 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.service;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import org.apache.karaf.features.EventConstants;
+import org.apache.karaf.features.FeatureEvent;
+import org.apache.karaf.features.FeaturesListener;
+import org.apache.karaf.features.RepositoryEvent;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventAdmin;
+import org.osgi.util.tracker.ServiceTracker;
+
+/**
+ * A listener to publish events to EventAdmin
+ */
+public class EventAdminListener implements FeaturesListener {
+
+    private final ServiceTracker<EventAdmin, EventAdmin> tracker;
+
+    public EventAdminListener(BundleContext context) {
+        tracker = new ServiceTracker<EventAdmin, EventAdmin>(context, EventAdmin.class.getName(), null);
+        tracker.open();
+    }
+
+    public void featureEvent(FeatureEvent event) {
+        EventAdmin eventAdmin = tracker.getService();
+        if (eventAdmin == null) {
+            return;
+        }
+        Dictionary<String, Object> props = new Hashtable<String, Object>();
+        props.put(EventConstants.TYPE, event.getType());
+        props.put(EventConstants.EVENT, event);
+        props.put(EventConstants.TIMESTAMP, System.currentTimeMillis());
+        props.put(EventConstants.FEATURE_NAME, event.getFeature().getName());
+        props.put(EventConstants.FEATURE_VERSION, event.getFeature().getVersion());
+        String topic;
+        switch (event.getType()) {
+            case FeatureInstalled:
+                topic = EventConstants.TOPIC_FEATURES_INSTALLED;
+                break;
+            case FeatureUninstalled:
+                topic = EventConstants.TOPIC_FEATURES_UNINSTALLED;
+                break;
+            default:
+                throw new IllegalStateException("Unknown features event type: " + event.getType());
+        }
+        eventAdmin.postEvent(new Event(topic, props));
+    }
+
+    public void repositoryEvent(RepositoryEvent event) {
+        EventAdmin eventAdmin = tracker.getService();
+        if (eventAdmin == null) {
+            return;
+        }
+        Dictionary<String, Object> props = new Hashtable<String, Object>();
+        props.put(EventConstants.TYPE, event.getType());
+        props.put(EventConstants.EVENT, event);
+        props.put(EventConstants.TIMESTAMP, System.currentTimeMillis());
+        props.put(EventConstants.REPOSITORY_URI, event.getRepository().getURI().toString());
+        String topic;
+        switch (event.getType()) {
+            case RepositoryAdded:
+                topic = EventConstants.TOPIC_REPOSITORY_ADDED;
+                break;
+            case RepositoryRemoved:
+                topic = EventConstants.TOPIC_REPOSITORY_REMOVED;
+                break;
+            default:
+                throw new IllegalStateException("Unknown repository event type: " + event.getType());
+        }
+        eventAdmin.postEvent(new Event(topic, props));
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/internal/service/FeatureConfigInstaller.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/FeatureConfigInstaller.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/FeatureConfigInstaller.java
new file mode 100644
index 0000000..0e9038d
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/FeatureConfigInstaller.java
@@ -0,0 +1,167 @@
+/*
+ * 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.service;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import org.apache.karaf.features.ConfigFileInfo;
+import org.apache.karaf.features.Feature;
+import org.osgi.framework.Constants;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class FeatureConfigInstaller {
+	private static final Logger LOGGER = LoggerFactory.getLogger(FeaturesServiceImpl.class);
+    private static final String CONFIG_KEY = "org.apache.karaf.features.configKey";
+
+    private final ConfigurationAdmin configAdmin;
+    
+    public FeatureConfigInstaller(ConfigurationAdmin configAdmin) {
+		this.configAdmin = configAdmin;
+	}
+
+    private String[] parsePid(String pid) {
+        int n = pid.indexOf('-');
+        if (n > 0) {
+            String factoryPid = pid.substring(n + 1);
+            pid = pid.substring(0, n);
+            return new String[]{pid, factoryPid};
+        } else {
+            return new String[]{pid, null};
+        }
+    }
+
+    private Configuration createConfiguration(ConfigurationAdmin configurationAdmin,
+                                                String pid, String factoryPid) throws IOException, InvalidSyntaxException {
+        if (factoryPid != null) {
+            return configurationAdmin.createFactoryConfiguration(factoryPid, null);
+        } else {
+            return configurationAdmin.getConfiguration(pid, null);
+        }
+    }
+
+    private Configuration findExistingConfiguration(ConfigurationAdmin configurationAdmin,
+                                                      String pid, String factoryPid) throws IOException, InvalidSyntaxException {
+        String filter;
+        if (factoryPid == null) {
+            filter = "(" + Constants.SERVICE_PID + "=" + pid + ")";
+        } else {
+            String key = createConfigurationKey(pid, factoryPid);
+            filter = "(" + CONFIG_KEY + "=" + key + ")";
+        }
+        Configuration[] configurations = configurationAdmin.listConfigurations(filter);
+        if (configurations != null && configurations.length > 0) {
+            return configurations[0];
+        }
+        return null;
+    }
+
+    void installFeatureConfigs(Feature feature) throws IOException, InvalidSyntaxException {
+        for (String config : feature.getConfigurations().keySet()) {
+            Dictionary<String,String> props = new Hashtable<String, String>(feature.getConfigurations().get(config));
+            String[] pid = parsePid(config);
+            Configuration cfg = findExistingConfiguration(configAdmin, pid[0], pid[1]);
+            if (cfg == null) {
+                cfg = createConfiguration(configAdmin, pid[0], pid[1]);
+                String key = createConfigurationKey(pid[0], pid[1]);
+                props.put(CONFIG_KEY, key);
+                if (cfg.getBundleLocation() != null) {
+                    cfg.setBundleLocation(null);
+                }
+                cfg.update(props);
+            }
+        }
+        for (ConfigFileInfo configFile : feature.getConfigurationFiles()) {
+            installConfigurationFile(configFile.getLocation(), configFile.getFinalname(), configFile.isOverride());
+        }
+    }
+
+    private String createConfigurationKey(String pid, String factoryPid) {
+        return factoryPid == null ? pid : pid + "-" + factoryPid;
+    }
+
+    private void installConfigurationFile(String fileLocation, String finalname, boolean override) throws IOException {
+    	String basePath = System.getProperty("karaf.base");
+    	
+    	if (finalname.contains("${")) {
+    		//remove any placeholder or variable part, this is not valid.
+    		int marker = finalname.indexOf("}");
+    		finalname = finalname.substring(marker+1);
+    	}
+    	
+    	finalname = basePath + File.separator + finalname;
+    	
+    	File file = new File(finalname); 
+    	if (file.exists()) {
+            if (!override) {
+                LOGGER.debug("Configuration file {} already exist, don't override it", finalname);
+                return;
+            } else {
+                LOGGER.info("Configuration file {} already exist, overriding it", finalname);
+            }
+    	} else {
+            LOGGER.info("Creating configuration file {}", finalname);
+        }
+
+        InputStream is = null;
+        FileOutputStream fop = null;
+        try {
+            is = new BufferedInputStream(new URL(fileLocation).openStream());
+
+            if (!file.exists()) {
+                File parentFile = file.getParentFile();
+                if (parentFile != null)
+                    parentFile.mkdirs();
+                file.createNewFile();
+            }
+
+            fop = new FileOutputStream(file);
+        
+            int bytesRead;
+            byte[] buffer = new byte[1024];
+            
+            while ((bytesRead = is.read(buffer)) != -1) {
+                fop.write(buffer, 0, bytesRead);
+            }
+        } catch (RuntimeException e) {
+            LOGGER.error(e.getMessage());
+            throw e;
+        } catch (MalformedURLException e) {
+        	LOGGER.error(e.getMessage());
+            throw e;
+		} finally {
+			if (is != null)
+				is.close();
+            if (fop != null) {
+			    fop.flush();
+			    fop.close();
+            }
+		}
+            
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/internal/service/FeatureFinder.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/FeatureFinder.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/FeatureFinder.java
new file mode 100644
index 0000000..d6defe0
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/FeatureFinder.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.features.internal.service;
+
+import java.net.URI;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.cm.ManagedService;
+
+public class FeatureFinder implements ManagedService {
+
+    final Map<String, String> nameToArtifactMap = new HashMap<String, String>();
+
+    public String[] getNames() {
+        synchronized (nameToArtifactMap) {
+            Set<String> strings = nameToArtifactMap.keySet();
+            return strings.toArray(new String[strings.size()]);
+        }
+    }
+
+    public URI getUriFor(String name, String version) {
+        String coords;
+        synchronized (nameToArtifactMap) {
+            coords = nameToArtifactMap.get(name);
+        }
+        if (coords == null) {
+            return null;
+        }
+        Artifact artifact = new Artifact(coords);
+        return artifact.getMavenUrl(version);
+    }
+
+    @SuppressWarnings("rawtypes")
+    public void updated(Dictionary properties) throws ConfigurationException {
+        synchronized (nameToArtifactMap) {
+            if (properties != null) {
+                nameToArtifactMap.clear();
+                Enumeration keys = properties.keys();
+                while (keys.hasMoreElements()) {
+                    String key = (String) keys.nextElement();
+                    if (!"felix.fileinstall.filename".equals(key) && !"service.pid".equals(key)) {
+                        nameToArtifactMap.put(key, (String) properties.get(key));
+                    }
+                }
+            }
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/internal/service/FeatureValidationUtil.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/FeatureValidationUtil.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/FeatureValidationUtil.java
new file mode 100644
index 0000000..8fb161e
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/FeatureValidationUtil.java
@@ -0,0 +1,113 @@
+/*
+ * 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.service;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URLConnection;
+
+import javax.xml.XMLConstants;
+import javax.xml.namespace.QName;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamSource;
+import javax.xml.validation.Schema;
+import javax.xml.validation.SchemaFactory;
+import javax.xml.validation.Validator;
+
+import org.apache.karaf.features.FeaturesNamespaces;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Document;
+import org.xml.sax.SAXException;
+
+/**
+ * Utility class which fires XML Schema validation.
+ */
+public class FeatureValidationUtil {
+
+    public static final QName FEATURES_0_0 = new QName("features");
+    public static final QName FEATURES_1_0 = new QName("http://karaf.apache.org/xmlns/features/v1.0.0", "features");
+    public static final QName FEATURES_1_1 = new QName("http://karaf.apache.org/xmlns/features/v1.1.0", "features");
+    public static final QName FEATURES_1_2 = new QName("http://karaf.apache.org/xmlns/features/v1.2.0", "features");
+    private static final Logger LOGGER = LoggerFactory.getLogger(FeatureValidationUtil.class);
+
+    /**
+     * Runs schema validation.
+     * 
+     * @param uri Uri to validate.
+     * @throws Exception When validation fails.
+     */
+    public static void validate(URI uri) throws Exception {
+        Document doc = load(uri);
+
+        QName name = new QName(doc.getDocumentElement().getNamespaceURI(), doc.getDocumentElement().getLocalName());
+
+        if (FeaturesNamespaces.FEATURES_0_0_0.equals(name)) {
+            LOGGER.warn("Old style feature file without namespace found (URI: {}). This format is deprecated and support for it will soon be removed", uri);
+            return;
+        } else if (FeaturesNamespaces.FEATURES_1_0_0.equals(name)) {
+            validate(doc, "/org/apache/karaf/features/karaf-features-1.0.0.xsd");
+        } else if (FeaturesNamespaces.FEATURES_1_1_0.equals(name)) {
+            validate(doc, "/org/apache/karaf/features/karaf-features-1.1.0.xsd");
+        } else if (FeaturesNamespaces.FEATURES_1_2_0.equals(name)) {
+            validate(doc, "/org/apache/karaf/features/karaf-features-1.2.0.xsd");
+        } else if (FeaturesNamespaces.FEATURES_1_3_0.equals(name)) {
+            validate(doc, "/org/apache/karaf/features/karaf-features-1.3.0.xsd");
+        }
+        else {
+            throw new IllegalArgumentException("Unrecognized root element: " + name);
+        }
+    }
+
+    private static Document load(URI uri) throws IOException, SAXException, ParserConfigurationException {
+        InputStream stream = null;
+        try {
+            URLConnection conn;
+            try {
+                conn = uri.toURL().openConnection();
+            } catch (IllegalArgumentException e) {
+                throw new IllegalArgumentException("invalid URI: " + uri, e);
+            }
+            conn.setDefaultUseCaches(false);
+            stream = conn.getInputStream();
+            // load document and check the root element for namespace declaration
+            DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
+            dFactory.setNamespaceAware(true);
+            return dFactory.newDocumentBuilder().parse(stream);
+        } finally {
+            if (stream != null) {
+                stream.close();
+            }
+        }
+    }
+
+    private static void validate(Document doc, String schemaLocation) throws SAXException {
+        SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
+        // root element has namespace - we can use schema validation
+        Schema schema = factory.newSchema(new StreamSource(FeatureValidationUtil.class.getResourceAsStream(schemaLocation)));
+        // create schema by reading it from an XSD file:
+        Validator validator = schema.newValidator();
+        try {
+            validator.validate(new DOMSource(doc));
+        } catch (Exception e) {
+            throw new IllegalArgumentException("Unable to validate " + doc.getDocumentURI(), e);
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/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
new file mode 100644
index 0000000..457b5e1
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java
@@ -0,0 +1,1378 @@
+/*
+ * 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.service;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import org.apache.felix.utils.version.VersionRange;
+import org.apache.felix.utils.version.VersionTable;
+import org.apache.karaf.features.BundleInfo;
+import org.apache.karaf.features.Conditional;
+import org.apache.karaf.features.Dependency;
+import org.apache.karaf.features.Feature;
+import org.apache.karaf.features.FeatureEvent;
+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.deployment.DeploymentBuilder;
+import org.apache.karaf.features.internal.deployment.StreamProvider;
+import org.apache.karaf.features.internal.resolver.FeatureNamespace;
+import org.apache.karaf.features.internal.resolver.UriNamespace;
+import org.apache.karaf.features.internal.util.ChecksumUtils;
+import org.apache.karaf.features.internal.util.Macro;
+import org.apache.karaf.features.internal.util.MultiException;
+import org.apache.karaf.util.collections.CopyOnWriteArrayIdentityList;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.Constants;
+import org.osgi.framework.FrameworkEvent;
+import org.osgi.framework.FrameworkListener;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.Version;
+import org.osgi.framework.namespace.PackageNamespace;
+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.FrameworkWiring;
+import org.osgi.resource.Resource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.apache.felix.resolver.Util.getSymbolicName;
+import static org.apache.felix.resolver.Util.getVersion;
+
+/**
+ *
+ */
+public class FeaturesServiceImpl implements FeaturesService {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(FeaturesServiceImpl.class);
+    private static final String SNAPSHOT = "SNAPSHOT";
+    private static final String MAVEN = "mvn:";
+
+    private final Bundle bundle;
+    private final BundleContext systemBundleContext;
+    private final StateStorage storage;
+    private final FeatureFinder featureFinder;
+    private final EventAdminListener eventAdminListener;
+    private final FeatureConfigInstaller configInstaller;
+    private final String overrides;
+
+    private final List<FeaturesListener> listeners = new CopyOnWriteArrayIdentityList<FeaturesListener>();
+
+    // Synchronized on lock
+    private final Object lock = new Object();
+    private final State state = new State();
+    private final Map<String, Repository> repositoryCache = new HashMap<String, Repository>();
+    private Map<String, Map<String, Feature>> featureCache;
+
+
+
+    public FeaturesServiceImpl(Bundle bundle,
+                               BundleContext systemBundleContext,
+                               StateStorage storage,
+                               FeatureFinder featureFinder,
+                               EventAdminListener eventAdminListener,
+                               FeatureConfigInstaller configInstaller,
+                               String overrides) {
+        this.bundle = bundle;
+        this.systemBundleContext = systemBundleContext;
+        this.storage = storage;
+        this.featureFinder = featureFinder;
+        this.eventAdminListener = eventAdminListener;
+        this.configInstaller = configInstaller;
+        this.overrides = overrides;
+        loadState();
+    }
+
+    //
+    // State support
+    //
+
+    protected void loadState() {
+        try {
+            synchronized (lock) {
+                storage.load(state);
+            }
+        } catch (IOException e) {
+            LOGGER.warn("Error loading FeaturesService state", e);
+        }
+    }
+
+    protected void saveState() {
+        try {
+            synchronized (lock) {
+                storage.save(state);
+            }
+        } catch (IOException e) {
+            LOGGER.warn("Error saving FeaturesService state", e);
+        }
+    }
+
+    boolean isBootDone() {
+        synchronized (lock) {
+            return state.bootDone.get();
+        }
+    }
+
+    void bootDone() {
+        synchronized (lock) {
+            state.bootDone.set(true);
+            saveState();
+        }
+    }
+
+    //
+    // Listeners support
+    //
+
+    public void registerListener(FeaturesListener listener) {
+        listeners.add(listener);
+        try {
+            Set<String> repositories = new TreeSet<String>();
+            Set<String> installedFeatures = new TreeSet<String>();
+            synchronized (lock) {
+                repositories.addAll(state.repositories);
+                installedFeatures.addAll(state.installedFeatures);
+            }
+            for (String uri : repositories) {
+                Repository repository = new RepositoryImpl(URI.create(uri));
+                listener.repositoryEvent(new RepositoryEvent(repository, RepositoryEvent.EventType.RepositoryAdded, true));
+            }
+            for (String id : installedFeatures) {
+                Feature feature = org.apache.karaf.features.internal.model.Feature.valueOf(id);
+                listener.featureEvent(new FeatureEvent(feature, FeatureEvent.EventType.FeatureInstalled, true));
+            }
+        } catch (Exception e) {
+            LOGGER.error("Error notifying listener about the current state", e);
+        }
+    }
+
+    public void unregisterListener(FeaturesListener listener) {
+        listeners.remove(listener);
+    }
+
+    protected void callListeners(FeatureEvent event) {
+        if (eventAdminListener != null) {
+            eventAdminListener.featureEvent(event);
+        }
+        for (FeaturesListener listener : listeners) {
+            listener.featureEvent(event);
+        }
+    }
+
+    protected void callListeners(RepositoryEvent event) {
+        if (eventAdminListener != null) {
+            eventAdminListener.repositoryEvent(event);
+        }
+        for (FeaturesListener listener : listeners) {
+            listener.repositoryEvent(event);
+        }
+    }
+
+    //
+    // Feature Finder support
+    //
+
+    @Override
+    public URI getRepositoryUriFor(String name, String version) {
+        return featureFinder.getUriFor(name, version);
+    }
+
+    @Override
+    public String[] getRepositoryNames() {
+        return featureFinder.getNames();
+    }
+
+
+    //
+    // Repositories support
+    //
+
+    public Repository loadRepository(URI uri) throws Exception {
+        // TODO: merge validation and loading by loading the DOM, validating, unmarshalling
+        FeatureValidationUtil.validate(uri);
+        RepositoryImpl repo = new RepositoryImpl(uri);
+        repo.load();
+        return repo;
+    }
+
+    @Override
+    public void validateRepository(URI uri) throws Exception {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void addRepository(URI uri) throws Exception {
+        addRepository(uri, false);
+    }
+
+    @Override
+    public void addRepository(URI uri, boolean install) throws Exception {
+        if (install) {
+            // TODO: implement
+            throw new UnsupportedOperationException();
+        }
+        Repository repository = loadRepository(uri);
+        synchronized (lock) {
+            // Clean cache
+            repositoryCache.put(uri.toString(), repository);
+            featureCache = null;
+            // Add repo
+            if (!state.repositories.add(uri.toString())) {
+                return;
+            }
+            saveState();
+        }
+        callListeners(new RepositoryEvent(repository, RepositoryEvent.EventType.RepositoryAdded, false));
+    }
+
+    @Override
+    public void removeRepository(URI uri) throws Exception {
+        removeRepository(uri, true);
+    }
+
+    @Override
+    public void removeRepository(URI uri, boolean uninstall) throws Exception {
+        // TODO: check we don't have any feature installed from this repository
+        Repository repo;
+        synchronized (lock) {
+            // Remove repo
+            if (!state.repositories.remove(uri.toString())) {
+                return;
+            }
+            // Clean cache
+            featureCache = null;
+            repo = repositoryCache.get(uri.toString());
+            List<String> toRemove = new ArrayList<String>();
+            toRemove.add(uri.toString());
+            while (!toRemove.isEmpty()) {
+                Repository rep = repositoryCache.remove(toRemove.remove(0));
+                if (rep != null) {
+                    for (URI u : rep.getRepositories()) {
+                        toRemove.add(u.toString());
+                    }
+                }
+            }
+            saveState();
+        }
+        if (repo == null) {
+            repo = new RepositoryImpl(uri);
+        }
+        callListeners(new RepositoryEvent(repo, RepositoryEvent.EventType.RepositoryRemoved, false));
+    }
+
+    @Override
+    public void restoreRepository(URI uri) throws Exception {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void refreshRepository(URI uri) throws Exception {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Repository[] listRepositories() {
+        // TODO: catching this exception is ugly: refactor the api
+        try {
+            getFeatures();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+        synchronized (lock) {
+            return repositoryCache.values().toArray(new Repository[repositoryCache.size()]);
+        }
+    }
+
+    @Override
+    public Repository getRepository(String name) {
+        synchronized (lock) {
+            for (Repository repo : this.repositoryCache.values()) {
+                if (name.equals(repo.getName())) {
+                    return repo;
+                }
+            }
+            return null;
+        }
+    }
+
+    //
+    // Features support
+    //
+
+    public Feature getFeature(String name) throws Exception {
+        return getFeature(name, null);
+    }
+
+    public Feature getFeature(String name, String version) throws Exception {
+        Map<String, Feature> versions = getFeatures().get(name);
+        return getFeatureMatching(versions, version);
+    }
+
+    protected Feature getFeatureMatching(Map<String, Feature> versions, String version) {
+        if (version != null) {
+            version = version.trim();
+            if (version.equals(org.apache.karaf.features.internal.model.Feature.DEFAULT_VERSION)) {
+                version = "";
+            }
+        } else {
+            version = "";
+        }
+        if (versions == null || versions.isEmpty()) {
+            return null;
+        } else {
+            Feature feature = version.isEmpty() ? null : versions.get(version);
+            if (feature == null) {
+                // Compute version range. If an version has been given, assume exact range
+                VersionRange versionRange = version.isEmpty() ?
+                        new VersionRange(Version.emptyVersion) :
+                        new VersionRange(version, true, true);
+                Version latest = Version.emptyVersion;
+                for (String available : versions.keySet()) {
+                    Version availableVersion = VersionTable.getVersion(available);
+                    if (availableVersion.compareTo(latest) >= 0 && versionRange.contains(availableVersion)) {
+                        feature = versions.get(available);
+                        latest = availableVersion;
+                    }
+                }
+            }
+            return feature;
+        }
+    }
+
+    public Feature[] listFeatures() throws Exception {
+        Set<Feature> features = new HashSet<Feature>();
+        for (Map<String, Feature> featureWithDifferentVersion : getFeatures().values()) {
+            for (Feature f : featureWithDifferentVersion.values()) {
+                features.add(f);
+            }
+        }
+        return features.toArray(new Feature[features.size()]);
+    }
+
+    protected Map<String, Map<String, Feature>> getFeatures() throws Exception {
+        List<String> uris;
+        synchronized (lock) {
+            if (featureCache != null) {
+                return featureCache;
+            }
+            uris = new ArrayList<String>(state.repositories);
+        }
+        //the outer map's key is feature name, the inner map's key is feature version
+        Map<String, Map<String, Feature>> map = new HashMap<String, Map<String, Feature>>();
+        // Two phase load:
+        // * first load dependent repositories
+        List<String> toLoad = new ArrayList<String>(uris);
+        while (!toLoad.isEmpty()) {
+            String uri = toLoad.remove(0);
+            Repository repo;
+            synchronized (lock) {
+                repo = repositoryCache.get(uri);
+            }
+            if (repo == null) {
+                RepositoryImpl rep = new RepositoryImpl(URI.create(uri));
+                rep.load();
+                repo = rep;
+                synchronized (lock) {
+                    repositoryCache.put(uri, repo);
+                }
+            }
+            for (URI u : repo.getRepositories()) {
+                toLoad.add(u.toString());
+            }
+        }
+        List<Repository> repos;
+        synchronized (lock) {
+            repos = new ArrayList<Repository>(repositoryCache.values());
+        }
+        // * then load all features
+        for (Repository repo : repos) {
+            for (Feature f : repo.getFeatures()) {
+                if (map.get(f.getName()) == null) {
+                    Map<String, Feature> versionMap = new HashMap<String, Feature>();
+                    versionMap.put(f.getVersion(), f);
+                    map.put(f.getName(), versionMap);
+                } else {
+                    map.get(f.getName()).put(f.getVersion(), f);
+                }
+            }
+        }
+        synchronized (lock) {
+            if (uris.size() == state.repositories.size() &&
+                    state.repositories.containsAll(uris)) {
+                featureCache = map;
+            }
+        }
+        return map;
+    }
+
+    //
+    // Installed features
+    //
+
+    @Override
+    public Feature[] listInstalledFeatures() throws Exception {
+        Set<Feature> features = new HashSet<Feature>();
+        Map<String, Map<String, Feature>> allFeatures = getFeatures();
+        synchronized (lock) {
+            for (Map<String, Feature> featureWithDifferentVersion : allFeatures.values()) {
+                for (Feature f : featureWithDifferentVersion.values()) {
+                    if (isInstalled(f)) {
+                        features.add(f);
+                    }
+                }
+            }
+        }
+        return features.toArray(new Feature[features.size()]);
+    }
+
+    @Override
+    public boolean isInstalled(Feature f) {
+        String id = normalize(f.getId());
+        synchronized (lock) {
+            return state.installedFeatures.contains(id);
+        }
+    }
+
+    //
+    // Installation and uninstallation of features
+    //
+
+    public void installFeature(String name) throws Exception {
+        installFeature(name, EnumSet.noneOf(Option.class));
+    }
+
+    public void installFeature(String name, String version) throws Exception {
+        installFeature(version != null ? name + "/" + version : name, EnumSet.noneOf(Option.class));
+    }
+
+    public void installFeature(String name, EnumSet<Option> options) throws Exception {
+        doAddFeatures(Collections.singleton(name), options);
+    }
+
+    public void installFeature(String name, String version, EnumSet<Option> options) throws Exception {
+        installFeature(version != null ? name + "/" + version : name, options);
+    }
+
+    public void installFeature(Feature feature, EnumSet<Option> options) throws Exception {
+        installFeature(feature.getId());
+    }
+
+    public void installFeatures(Set<Feature> features, EnumSet<Option> options) throws Exception {
+        Set<String> fs = new HashSet<String>();
+        for (Feature f : features) {
+            fs.add(f.getId());
+        }
+        doAddFeatures(fs, options);
+    }
+
+    @Override
+    public void uninstallFeature(String name, String version) throws Exception {
+        uninstallFeature(version != null ? name + "/" + version : name);
+    }
+
+    @Override
+    public void uninstallFeature(String name, String version, EnumSet<Option> options) throws Exception {
+        uninstallFeature(version != null ? name + "/" + version : name, options);
+    }
+
+    @Override
+    public void uninstallFeature(String name) throws Exception {
+        uninstallFeature(name, EnumSet.noneOf(Option.class));
+    }
+
+    @Override
+    public void uninstallFeature(String name, EnumSet<Option> options) throws Exception {
+        doRemoveFeatures(Collections.singleton(name), options);
+    }
+
+
+    //
+    //
+    //
+    //   RESOLUTION
+    //
+    //
+    //
+
+
+
+
+
+
+    public void doAddFeatures(Set<String> features, EnumSet<Option> options) throws Exception {
+        Set<String> required;
+        Set<Long> managed;
+        synchronized (lock) {
+            required = new HashSet<String>(state.features);
+            managed = new HashSet<Long>(state.managedBundles);
+        }
+        List<String> featuresToAdd = new ArrayList<String>();
+        Map<String, Map<String, Feature>> featuresMap = getFeatures();
+        for (String feature : features) {
+            feature = normalize(feature);
+            String name = feature.substring(0, feature.indexOf("/"));
+            String version = feature.substring(feature.indexOf("/") + 1);
+            Feature f = getFeatureMatching(featuresMap.get(name), version);
+            if (f == null) {
+                throw new IllegalArgumentException("No matching features for " + feature);
+            }
+            featuresToAdd.add(normalize(f.getId()));
+        }
+        featuresToAdd = new ArrayList<String>(new LinkedHashSet<String>(featuresToAdd));
+        StringBuilder sb = new StringBuilder();
+        sb.append("Adding features: ");
+        for (int i = 0; i < featuresToAdd.size(); i++) {
+            if (i > 0) {
+                sb.append(", ");
+            }
+            sb.append(featuresToAdd.get(i));
+        }
+        print(sb.toString(), options.contains(Option.Verbose));
+        required.addAll(featuresToAdd);
+        doInstallFeaturesInThread(required, managed, options);
+    }
+
+    public void doRemoveFeatures(Set<String> features, EnumSet<Option> options) throws Exception {
+        Set<String> required;
+        Set<Long> managed;
+        synchronized (lock) {
+            required = new HashSet<String>(state.features);
+            managed = new HashSet<Long>(state.managedBundles);
+        }
+        List<String> featuresToRemove = new ArrayList<String>();
+        for (String feature : new HashSet<String>(features)) {
+            List<String> toRemove = new ArrayList<String>();
+            feature = normalize(feature);
+            if (feature.endsWith("/0.0.0")) {
+                String nameSep = feature.substring(0, feature.indexOf("/") + 1);
+                for (String f : required) {
+                    if (normalize(f).startsWith(nameSep)) {
+                        toRemove.add(f);
+                    }
+                }
+            } else {
+                toRemove.add(feature);
+            }
+            toRemove.retainAll(required);
+            if (toRemove.isEmpty()) {
+                throw new IllegalArgumentException("Feature named '" + feature + "' is not installed");
+            } else if (toRemove.size() > 1) {
+                String name = feature.substring(0, feature.indexOf("/"));
+                StringBuilder sb = new StringBuilder();
+                sb.append("Feature named '").append(name).append("' has multiple versions installed (");
+                for (int i = 0; i < toRemove.size(); i++) {
+                    if (i > 0) {
+                        sb.append(", ");
+                    }
+                    sb.append(toRemove.get(i));
+                }
+                sb.append("). Please specify the version to uninstall.");
+                throw new IllegalArgumentException(sb.toString());
+            }
+            featuresToRemove.addAll(toRemove);
+        }
+        featuresToRemove = new ArrayList<String>(new LinkedHashSet<String>(featuresToRemove));
+        StringBuilder sb = new StringBuilder();
+        sb.append("Removing features: ");
+        for (int i = 0; i < featuresToRemove.size(); i++) {
+            if (i > 0) {
+                sb.append(", ");
+            }
+            sb.append(featuresToRemove.get(i));
+        }
+        print(sb.toString(), options.contains(Option.Verbose));
+        required.removeAll(featuresToRemove);
+        doInstallFeaturesInThread(required, managed, options);
+    }
+
+    protected String normalize(String feature) {
+        if (!feature.contains("/")) {
+            feature += "/0.0.0";
+        }
+        int idx = feature.indexOf("/");
+        String name = feature.substring(0, idx);
+        String version = feature.substring(idx + 1);
+        return name + "/" + VersionTable.getVersion(version).toString();
+    }
+
+    /**
+     * Actual deployment needs to be done in a separate thread.
+     * The reason is that if the console is refreshed, the current thread which is running
+     * the command may be interrupted while waiting for the refresh to be done, leading
+     * to bundles not being started after the refresh.
+     */
+    public void doInstallFeaturesInThread(final Set<String> features,
+                                          final Set<Long> managed,
+                                          final EnumSet<Option> options) throws Exception {
+        ExecutorService executor = Executors.newCachedThreadPool();
+        try {
+            executor.submit(new Callable<Object>() {
+                @Override
+                public Object call() throws Exception {
+                    doInstallFeatures(features, managed, options);
+                    return null;
+                }
+            }).get();
+        } catch (ExecutionException e) {
+            Throwable t = e.getCause();
+            if (t instanceof RuntimeException) {
+                throw ((RuntimeException) t);
+            } else if (t instanceof Error) {
+                throw ((Error) t);
+            } else if (t instanceof Exception) {
+                throw (Exception) t;
+            } else {
+                throw e;
+            }
+        } finally {
+            executor.shutdown();
+        }
+    }
+
+    public void doInstallFeatures(Set<String> features, Set<Long> managed, EnumSet<Option> options) throws Exception {
+        // TODO: make this configurable  through ConfigAdmin
+        // TODO: this needs to be tested a bit
+        // TODO: note that this only applies to managed and updateable bundles
+        boolean updateSnaphots = true;
+
+        // TODO: make this configurable at runtime
+        // TODO: note that integration tests will fail if set to false
+        // TODO: but I think it should be the default anyway
+        boolean noRefreshUnmanaged = true;
+
+        // TODO: make this configurable at runtime
+        boolean noRefreshManaged = true;
+
+        // TODO: make this configurable at runtime
+        boolean noRefresh = false;
+
+        // TODO: make this configurable  through ConfigAdmin
+        // TODO: though opening it as some important effects
+        String featureResolutionRange = "${range;[====,====]}";
+
+        // TODO: make this configurable through ConfigAdmin
+        String bundleUpdateRange = "${range;[==,=+)}";
+
+        boolean verbose = options.contains(Option.Verbose);
+
+        // Get a list of resolved and unmanaged bundles to use as capabilities during resolution
+        List<Resource> systemBundles = new ArrayList<Resource>();
+        Bundle[] bundles = systemBundleContext.getBundles();
+        for (Bundle bundle : bundles) {
+            if (bundle.getState() >= Bundle.RESOLVED && !managed.contains(bundle.getBundleId())) {
+                Resource res = bundle.adapt(BundleRevision.class);
+                systemBundles.add(res);
+            }
+        }
+        // Resolve
+        // TODO: requirements
+        // TODO: bundles
+        // TODO: regions: on isolated regions, we may need different resolution for each region
+        Set<String>  overrides    = Overrides.loadOverrides(this.overrides);
+        Repository[] repositories = listRepositories();
+        DeploymentBuilder builder = createDeploymentBuilder(repositories);
+        builder.setFeatureRange(featureResolutionRange);
+        builder.download(features,
+                         Collections.<String>emptySet(),
+                         Collections.<String>emptySet(),
+                         overrides,
+                         Collections.<String>emptySet());
+        Collection<Resource> allResources = builder.resolve(systemBundles, false);
+        Map<String, StreamProvider> providers = builder.getProviders();
+
+        // Install conditionals
+        List<String> installedFeatureIds = getFeatureIds(allResources);
+        List<Feature> installedFeatures = getFeatures(repositories, installedFeatureIds);
+
+        // TODO: is there are a way to use fragments or on-demand resources
+        // TODO: in the resolver to use a single resolution ?
+        boolean resolveAgain = false;
+        Set<String> featuresAndConditionals = new TreeSet<String>(features);
+        for (Feature feature : installedFeatures) {
+            for (Conditional cond : feature.getConditional()) {
+                boolean condSatisfied = true;
+                for (Dependency dep : cond.getCondition()) {
+                    boolean depSatisfied = false;
+                    String name = dep.getName();
+                    VersionRange range = new VersionRange(dep.getVersion(), false, true);
+                    for (Feature f : installedFeatures) {
+                        if (f.getName().equals(name)) {
+                            if (range.contains(VersionTable.getVersion(f.getVersion()))) {
+                                depSatisfied = true;
+                                break;
+                            }
+                        }
+                    }
+                    if (!depSatisfied) {
+                        condSatisfied = false;
+                        break;
+                    }
+                }
+                if (condSatisfied) {
+                    featuresAndConditionals.add(cond.asFeature(feature.getName(), feature.getVersion()).getId());
+                    resolveAgain = true;
+                }
+            }
+        }
+        if (resolveAgain) {
+            builder.download(featuresAndConditionals,
+                             Collections.<String>emptySet(),
+                             Collections.<String>emptySet(),
+                             overrides,
+                             Collections.<String>emptySet());
+            allResources = builder.resolve(systemBundles, false);
+            providers = builder.getProviders();
+        }
+
+
+        //
+        // Compute list of installable resources (those with uris)
+        //
+        List<Resource> resources = getBundles(allResources);
+
+        // Compute information for each bundle
+        Map<String, BundleInfo> bundleInfos = new HashMap<String, BundleInfo>();
+        for (Feature feature : getFeatures(repositories, getFeatureIds(allResources))) {
+            for (BundleInfo bi : feature.getBundles()) {
+                BundleInfo oldBi = bundleInfos.get(bi.getLocation());
+                if (oldBi != null) {
+                    bi = mergeBundleInfo(bi, oldBi);
+                }
+                bundleInfos.put(bi.getLocation(), bi);
+            }
+        }
+
+
+        //
+        // Compute deployment
+        //
+        Map<String, Long> bundleChecksums = new HashMap<String, Long>();
+        synchronized (lock) {
+            bundleChecksums.putAll(state.bundleChecksums);
+        }
+        Deployment deployment = computeDeployment(managed, updateSnaphots, bundles, providers, resources, bundleChecksums, bundleUpdateRange);
+
+        if (deployment.toDelete.isEmpty() &&
+                deployment.toUpdate.isEmpty() &&
+                deployment.toInstall.isEmpty()) {
+            print("No deployment change.", verbose);
+            return;
+        }
+        //
+        // Log deployment
+        //
+        logDeployment(deployment);
+
+
+        Set<Bundle> toRefresh = new HashSet<Bundle>();
+        Set<Bundle> toStart = new HashSet<Bundle>();
+
+        //
+        // Execute deployment
+        //
+
+        // TODO: handle update on the features service itself
+        if (deployment.toUpdate.containsKey(bundle) ||
+                deployment.toDelete.contains(bundle)) {
+
+            LOGGER.warn("Updating or uninstalling of the FeaturesService is not supported");
+            deployment.toUpdate.remove(bundle);
+            deployment.toDelete.remove(bundle);
+
+        }
+
+        //
+        // Perform bundle operations
+        //
+
+        // Stop bundles by chunks
+        Set<Bundle> toStop = new HashSet<Bundle>();
+        toStop.addAll(deployment.toUpdate.keySet());
+        toStop.addAll(deployment.toDelete);
+        removeFragmentsAndBundlesInState(toStop, Bundle.UNINSTALLED | Bundle.RESOLVED | Bundle.STOPPING);
+        if (!toStop.isEmpty()) {
+            print("Stopping bundles:", verbose);
+            while (!toStop.isEmpty()) {
+                List<Bundle> bs = getBundlesToStop(toStop);
+                for (Bundle bundle : bs) {
+                    print("  " + bundle.getSymbolicName() + " / " + bundle.getVersion(), verbose);
+                    bundle.stop(Bundle.STOP_TRANSIENT);
+                    toStop.remove(bundle);
+                }
+            }
+        }
+        if (!deployment.toDelete.isEmpty()) {
+            print("Uninstalling bundles:", verbose);
+            for (Bundle bundle : deployment.toDelete) {
+                print("  " + bundle.getSymbolicName() + " / " + bundle.getVersion(), verbose);
+                bundle.uninstall();
+                managed.remove(bundle.getBundleId());
+                toRefresh.add(bundle);
+            }
+        }
+        if (!deployment.toUpdate.isEmpty()) {
+            print("Updating bundles:", verbose);
+            for (Map.Entry<Bundle, Resource> entry : deployment.toUpdate.entrySet()) {
+                Bundle bundle = entry.getKey();
+                Resource resource = entry.getValue();
+                String uri = UriNamespace.getUri(resource);
+                print("  " + uri, verbose);
+                InputStream is = getBundleInputStream(resource, providers);
+                bundle.update(is);
+                toRefresh.add(bundle);
+                toStart.add(bundle);
+                BundleInfo bi = bundleInfos.get(uri);
+                if (bi != null && bi.getStartLevel() > 0) {
+                    bundle.adapt(BundleStartLevel.class).setStartLevel(bi.getStartLevel());
+                }
+                // TODO: handle region
+            }
+        }
+        if (!deployment.toInstall.isEmpty()) {
+            print("Installing bundles:", verbose);
+            for (Resource resource : deployment.toInstall) {
+                String uri = UriNamespace.getUri(resource);
+                print("  " + uri, verbose);
+                InputStream is = getBundleInputStream(resource, providers);
+                Bundle bundle = systemBundleContext.installBundle(uri, is);
+                managed.add(bundle.getBundleId());
+                toStart.add(bundle);
+                deployment.resToBnd.put(resource, bundle);
+                // save a checksum of installed snapshot bundle
+                if (isUpdateable(resource) && !deployment.newCheckums.containsKey(bundle.getLocation())) {
+                    deployment.newCheckums.put(bundle.getLocation(), ChecksumUtils.checksum(getBundleInputStream(resource, providers)));
+                }
+                BundleInfo bi = bundleInfos.get(uri);
+                if (bi != null && bi.getStartLevel() > 0) {
+                    bundle.adapt(BundleStartLevel.class).setStartLevel(bi.getStartLevel());
+                }
+                // TODO: handle region
+            }
+        }
+
+        //
+        // Update and save state
+        //
+        List<String> newFeatures = new ArrayList<String>();
+        synchronized (lock) {
+            List<String> allFeatures = new ArrayList<String>();
+            for (Resource resource : allResources) {
+                String name = FeatureNamespace.getName(resource);
+                if (name != null) {
+                    Version version = FeatureNamespace.getVersion(resource);
+                    String id = version != null ? name + "/" + version : name;
+                    allFeatures.add(id);
+                    if (!state.installedFeatures.contains(id)) {
+                        newFeatures.add(id);
+                    }
+                }
+            }
+            state.bundleChecksums.putAll(deployment.newCheckums);
+            state.features.clear();
+            state.features.addAll(features);
+            state.installedFeatures.clear();
+            state.installedFeatures.addAll(allFeatures);
+            state.managedBundles.clear();
+            state.managedBundles.addAll(managed);
+            saveState();
+        }
+
+        //
+        // Install configurations
+        //
+        if (configInstaller != null && !newFeatures.isEmpty()) {
+            for (Repository repository : repositories) {
+                for (Feature feature : repository.getFeatures()) {
+                    if (newFeatures.contains(feature.getId())) {
+                        configInstaller.installFeatureConfigs(feature);
+                    }
+                }
+            }
+        }
+
+        if (!noRefreshManaged) {
+            findBundlesWithOptionalPackagesToRefresh(toRefresh);
+            findBundlesWithFragmentsToRefresh(toRefresh);
+        }
+
+        if (noRefreshUnmanaged) {
+            Set<Bundle> newSet = new HashSet<Bundle>();
+            for (Bundle bundle : toRefresh) {
+                if (managed.contains(bundle.getBundleId())) {
+                    newSet.add(bundle);
+                }
+            }
+            toRefresh = newSet;
+        }
+
+        // TODO: remove this hack, but it avoids loading the class after the bundle is refreshed
+        RequirementSort sort = new RequirementSort();
+
+        if (!noRefresh) {
+            toStop = new HashSet<Bundle>();
+            toStop.addAll(toRefresh);
+            removeFragmentsAndBundlesInState(toStop, Bundle.UNINSTALLED | Bundle.RESOLVED | Bundle.STOPPING);
+            if (!toStop.isEmpty()) {
+                print("Stopping bundles:", verbose);
+                while (!toStop.isEmpty()) {
+                    List<Bundle> bs = getBundlesToStop(toStop);
+                    for (Bundle bundle : bs) {
+                        print("  " + bundle.getSymbolicName() + " / " + bundle.getVersion(), verbose);
+                        bundle.stop(Bundle.STOP_TRANSIENT);
+                        toStop.remove(bundle);
+                        toStart.add(bundle);
+                    }
+                }
+            }
+
+            if (!toRefresh.isEmpty()) {
+                print("Refreshing bundles:", verbose);
+                for (Bundle bundle : toRefresh) {
+                    print("  " + bundle.getSymbolicName() + " / " + bundle.getVersion(), verbose);
+                }
+                if (!toRefresh.isEmpty()) {
+                    refreshPackages(toRefresh);
+                }
+            }
+        }
+
+        // Compute bundles to start
+        removeFragmentsAndBundlesInState(toStart, Bundle.UNINSTALLED | Bundle.ACTIVE | Bundle.STARTING);
+        if (!toStart.isEmpty()) {
+            // Compute correct start order
+            List<Exception> exceptions = new ArrayList<Exception>();
+            print("Starting bundles:", verbose);
+            while (!toStart.isEmpty()) {
+                List<Bundle> bs = getBundlesToStart(toStart);
+                for (Bundle bundle : bs) {
+                    LOGGER.info("  " + bundle.getSymbolicName() + " / " + bundle.getVersion());
+                    try {
+                        bundle.start();
+                    } catch (BundleException e) {
+                        exceptions.add(e);
+                    }
+                    toStart.remove(bundle);
+                }
+            }
+            if (!exceptions.isEmpty()) {
+                throw new MultiException("Error restarting bundles", exceptions);
+            }
+        }
+
+        print("Done.", verbose);
+    }
+
+    protected BundleInfo mergeBundleInfo(BundleInfo bi, BundleInfo oldBi) {
+        // TODO: we need a proper merge strategy when a bundle
+        // TODO: comes from different features
+        return bi;
+    }
+
+    private void print(String message, boolean verbose) {
+        LOGGER.info(message);
+        if (verbose) {
+            System.out.println(message);
+        }
+    }
+
+    private void removeFragmentsAndBundlesInState(Collection<Bundle> bundles, int state) {
+        for (Bundle bundle : new ArrayList<Bundle>(bundles)) {
+            if ((bundle.getState() & state) != 0
+                     || bundle.getHeaders().get(Constants.FRAGMENT_HOST) != null) {
+                bundles.remove(bundle);
+            }
+        }
+    }
+
+    protected void logDeployment(Deployment deployment) {
+        LOGGER.info("Changes to perform:");
+        if (!deployment.toDelete.isEmpty()) {
+            LOGGER.info("  Bundles to uninstall:");
+            for (Bundle bundle : deployment.toDelete) {
+                LOGGER.info("    " + bundle.getSymbolicName() + " / " + bundle.getVersion());
+            }
+        }
+        if (!deployment.toUpdate.isEmpty()) {
+            LOGGER.info("  Bundles to update:");
+            for (Map.Entry<Bundle, Resource> entry : deployment.toUpdate.entrySet()) {
+                LOGGER.info("    " + entry.getKey().getSymbolicName() + " / " + entry.getKey().getVersion() + " with " + UriNamespace.getUri(entry.getValue()));
+            }
+        }
+        if (!deployment.toInstall.isEmpty()) {
+            LOGGER.info("  Bundles to install:");
+            for (Resource resource : deployment.toInstall) {
+                LOGGER.info("    " + UriNamespace.getUri(resource));
+            }
+        }
+    }
+
+    protected Deployment computeDeployment(
+                                Set<Long> managed,
+                                boolean updateSnaphots,
+                                Bundle[] bundles,
+                                Map<String, StreamProvider> providers,
+                                List<Resource> resources,
+                                Map<String, Long> bundleChecksums,
+                                String bundleUpdateRange) throws IOException {
+        Deployment deployment = new Deployment();
+
+        // TODO: regions
+        List<Resource> toDeploy = new ArrayList<Resource>(resources);
+
+        // First pass: go through all installed bundles and mark them
+        // as either to ignore or delete
+        for (Bundle bundle : bundles) {
+            if (bundle.getSymbolicName() != null && bundle.getBundleId() != 0) {
+                Resource resource = null;
+                for (Resource res : toDeploy) {
+                    if (bundle.getSymbolicName().equals(getSymbolicName(res))) {
+                        if (bundle.getVersion().equals(getVersion(res))) {
+                            resource = res;
+                            break;
+                        }
+                    }
+                }
+                // We found a matching bundle
+                if (resource != null) {
+                    // In case of snapshots, check if the snapshot is out of date
+                    // and flag it as to update
+                    if (updateSnaphots && managed.contains(bundle.getBundleId()) && isUpdateable(resource)) {
+                        // if the checksum are different
+                        InputStream is = null;
+                        try {
+                            is = getBundleInputStream(resource, providers);
+                            long newCrc = ChecksumUtils.checksum(is);
+                            long oldCrc = bundleChecksums.containsKey(bundle.getLocation()) ? bundleChecksums.get(bundle.getLocation()) : 0l;
+                            if (newCrc != oldCrc) {
+                                LOGGER.debug("New snapshot available for " + bundle.getLocation());
+                                deployment.toUpdate.put(bundle, resource);
+                                deployment.newCheckums.put(bundle.getLocation(), newCrc);
+                            }
+                        } finally {
+                            if (is != null) {
+                                is.close();
+                            }
+                        }
+                    }
+                    // We're done for this resource
+                    toDeploy.remove(resource);
+                    deployment.resToBnd.put(resource, bundle);
+                // There's no matching resource
+                // If the bundle is managed, we need to delete it
+                } else if (managed.contains(bundle.getBundleId())) {
+                    deployment.toDelete.add(bundle);
+                }
+            }
+        }
+
+        // Second pass on remaining resources
+        for (Resource resource : toDeploy) {
+            TreeMap<Version, Bundle> matching = new TreeMap<Version, Bundle>();
+            VersionRange range = new VersionRange(Macro.transform(bundleUpdateRange, getVersion(resource).toString()));
+            for (Bundle bundle : deployment.toDelete) {
+                if (bundle.getSymbolicName().equals(getSymbolicName(resource)) && range.contains(bundle.getVersion())) {
+                    matching.put(bundle.getVersion(), bundle);
+                }
+            }
+            if (!matching.isEmpty()) {
+                Bundle bundle = matching.lastEntry().getValue();
+                deployment.toUpdate.put(bundle, resource);
+                deployment.toDelete.remove(bundle);
+                deployment.resToBnd.put(resource, bundle);
+            } else {
+                deployment.toInstall.add(resource);
+            }
+        }
+        return deployment;
+    }
+
+    protected List<Resource> getBundles(Collection<Resource> allResources) {
+        Map<String, Resource> deploy = new TreeMap<String, Resource>();
+        for (Resource res : allResources) {
+            String uri = UriNamespace.getUri(res);
+            if (uri != null) {
+                deploy.put(uri, res);
+            }
+        }
+        return new ArrayList<Resource>(deploy.values());
+    }
+
+    protected List<Feature> getFeatures(Repository[] repositories, List<String> featureIds) throws Exception {
+        List<Feature> installedFeatures = new ArrayList<Feature>();
+        for (Repository repository : repositories) {
+            for (Feature feature : repository.getFeatures()) {
+                String id = feature.getName() + "/" + VersionTable.getVersion(feature.getVersion());
+                if (featureIds.contains(id)) {
+                    installedFeatures.add(feature);
+                }
+            }
+        }
+        return installedFeatures;
+    }
+
+    protected List<String> getFeatureIds(Collection<Resource> allResources) {
+        List<String> installedFeatureIds = new ArrayList<String>();
+        for (Resource resource : allResources) {
+            String name = FeatureNamespace.getName(resource);
+            if (name != null) {
+                Version version = FeatureNamespace.getVersion(resource);
+                String id = version != null ? name + "/" + version : name;
+                installedFeatureIds.add(id);
+            }
+        }
+        return installedFeatureIds;
+    }
+
+    protected DeploymentBuilder createDeploymentBuilder(Repository[] repositories) {
+        return new DeploymentBuilder(new SimpleDownloader(), Arrays.asList(repositories));
+    }
+
+
+    protected boolean isUpdateable(Resource resource) {
+        return (getVersion(resource).getQualifier().endsWith(SNAPSHOT) ||
+                UriNamespace.getUri(resource).contains(SNAPSHOT) ||
+                !UriNamespace.getUri(resource).contains(MAVEN));
+    }
+
+    protected List<Bundle> getBundlesToStart(Collection<Bundle> bundles) {
+        // TODO: make this pluggable ?
+        // TODO: honor respectStartLvlDuringFeatureStartup
+
+        // We hit FELIX-2949 if we don't use the correct order as Felix resolver isn't greedy.
+        // In order to minimize that, we make sure we resolve the bundles in the order they
+        // are given back by the resolution, meaning that all root bundles (i.e. those that were
+        // not flagged as dependencies in features) are started before the others.   This should
+        // make sure those important bundles are started first and minimize the problem.
+
+        // Restart the features service last, regardless of any other consideration
+        // so that we don't end up with the service trying to do stuff before we're done
+        boolean restart = bundles.remove(bundle);
+
+        List<BundleRevision> revs = new ArrayList<BundleRevision>();
+        for (Bundle bundle : bundles) {
+            revs.add(bundle.adapt(BundleRevision.class));
+        }
+        List<Bundle> sorted = new ArrayList<Bundle>();
+        for (BundleRevision rev : RequirementSort.sort(revs)) {
+            sorted.add(rev.getBundle());
+        }
+        if (restart) {
+            sorted.add(bundle);
+        }
+        return sorted;
+    }
+
+    protected List<Bundle> getBundlesToStop(Collection<Bundle> bundles) {
+        // TODO: make this pluggable ?
+        // TODO: honor respectStartLvlDuringFeatureUninstall
+
+        List<Bundle> bundlesToDestroy = new ArrayList<Bundle>();
+        for (Bundle bundle : bundles) {
+            ServiceReference[] references = bundle.getRegisteredServices();
+            int usage = 0;
+            if (references != null) {
+                for (ServiceReference reference : references) {
+                    usage += getServiceUsage(reference, bundles);
+                }
+            }
+            LOGGER.debug("Usage for bundle {} is {}", bundle, usage);
+            if (usage == 0) {
+                bundlesToDestroy.add(bundle);
+            }
+        }
+        if (!bundlesToDestroy.isEmpty()) {
+            Collections.sort(bundlesToDestroy, new Comparator<Bundle>() {
+                public int compare(Bundle b1, Bundle b2) {
+                    return (int) (b2.getLastModified() - b1.getLastModified());
+                }
+            });
+            LOGGER.debug("Selected bundles {} for destroy (no services in use)", bundlesToDestroy);
+        } else {
+            ServiceReference ref = null;
+            for (Bundle bundle : bundles) {
+                ServiceReference[] references = bundle.getRegisteredServices();
+                for (ServiceReference reference : references) {
+                    if (getServiceUsage(reference, bundles) == 0) {
+                        continue;
+                    }
+                    if (ref == null || reference.compareTo(ref) < 0) {
+                        LOGGER.debug("Currently selecting bundle {} for destroy (with reference {})", bundle, reference);
+                        ref = reference;
+                    }
+                }
+            }
+            if (ref != null) {
+                bundlesToDestroy.add(ref.getBundle());
+            }
+            LOGGER.debug("Selected bundle {} for destroy (lowest ranking service)", bundlesToDestroy);
+        }
+        return bundlesToDestroy;
+    }
+
+    private static int getServiceUsage(ServiceReference ref, Collection<Bundle> bundles) {
+        Bundle[] usingBundles = ref.getUsingBundles();
+        int nb = 0;
+        if (usingBundles != null) {
+            for (Bundle bundle : usingBundles) {
+                if (bundles.contains(bundle)) {
+                    nb++;
+                }
+            }
+        }
+        return nb;
+    }
+
+    protected InputStream getBundleInputStream(Resource resource, Map<String, StreamProvider> providers) throws IOException {
+        String uri = UriNamespace.getUri(resource);
+        if (uri == null) {
+            throw new IllegalStateException("Resource has no uri");
+        }
+        StreamProvider provider = providers.get(uri);
+        if (provider == null) {
+            throw new IllegalStateException("Resource " + uri + " has no StreamProvider");
+        }
+        return provider.open();
+    }
+
+    protected void findBundlesWithOptionalPackagesToRefresh(Set<Bundle> toRefresh) {
+        // First pass: include all bundles contained in these features
+        if (toRefresh.isEmpty()) {
+            return;
+        }
+        Set<Bundle> bundles = new HashSet<Bundle>(Arrays.asList(systemBundleContext.getBundles()));
+        bundles.removeAll(toRefresh);
+        if (bundles.isEmpty()) {
+            return;
+        }
+        // Second pass: for each bundle, check if there is any unresolved optional package that could be resolved
+        for (Bundle bundle : bundles) {
+            BundleRevision rev = bundle.adapt(BundleRevision.class);
+            boolean matches = false;
+            if (rev != null) {
+                for (BundleRequirement req : rev.getDeclaredRequirements(null)) {
+                    if (PackageNamespace.PACKAGE_NAMESPACE.equals(req.getNamespace())
+                            && PackageNamespace.RESOLUTION_OPTIONAL.equals(req.getDirectives().get(PackageNamespace.REQUIREMENT_RESOLUTION_DIRECTIVE))) {
+                        // This requirement is an optional import package
+                        for (Bundle provider : toRefresh) {
+                            BundleRevision providerRev = provider.adapt(BundleRevision.class);
+                            if (providerRev != null) {
+                                for (BundleCapability cap : providerRev.getDeclaredCapabilities(null)) {
+                                    if (req.matches(cap)) {
+                                        matches = true;
+                                        break;
+                                    }
+                                }
+                            }
+                            if (matches) {
+                                break;
+                            }
+                        }
+                    }
+                    if (matches) {
+                        break;
+                    }
+                }
+            }
+            if (matches) {
+                toRefresh.add(bundle);
+            }
+        }
+    }
+
+    protected void findBundlesWithFragmentsToRefresh(Set<Bundle> toRefresh) {
+        if (toRefresh.isEmpty()) {
+            return;
+        }
+        Set<Bundle> bundles = new HashSet<Bundle>(Arrays.asList(systemBundleContext.getBundles()));
+        bundles.removeAll(toRefresh);
+        if (bundles.isEmpty()) {
+            return;
+        }
+        for (Bundle bundle : new ArrayList<Bundle>(toRefresh)) {
+            BundleRevision rev = bundle.adapt(BundleRevision.class);
+            if (rev != null) {
+                for (BundleRequirement req : rev.getDeclaredRequirements(null)) {
+                    if (BundleRevision.HOST_NAMESPACE.equals(req.getNamespace())) {
+                        for (Bundle hostBundle : bundles) {
+                            if (!toRefresh.contains(hostBundle)) {
+                                BundleRevision hostRev = hostBundle.adapt(BundleRevision.class);
+                                if (hostRev != null) {
+                                    for (BundleCapability cap : hostRev.getDeclaredCapabilities(null)) {
+                                        if (req.matches(cap)) {
+                                            toRefresh.add(hostBundle);
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    protected void refreshPackages(Collection<Bundle> bundles) throws InterruptedException {
+        final CountDownLatch latch = new CountDownLatch(1);
+        FrameworkWiring fw = systemBundleContext.getBundle().adapt(FrameworkWiring.class);
+        fw.refreshBundles(bundles, new FrameworkListener() {
+            @Override
+            public void frameworkEvent(FrameworkEvent event) {
+                if (event.getType() == FrameworkEvent.ERROR) {
+                    LOGGER.error("Framework error", event.getThrowable());
+                }
+                latch.countDown();
+            }
+        });
+        latch.await();
+    }
+
+
+    static class Deployment {
+        Map<String, Long> newCheckums = new HashMap<String, Long>();
+        Map<Resource, Bundle> resToBnd = new HashMap<Resource, Bundle>();
+        List<Resource> toInstall = new ArrayList<Resource>();
+        List<Bundle> toDelete = new ArrayList<Bundle>();
+        Map<Bundle, Resource> toUpdate = new HashMap<Bundle, Resource>();
+    }
+
+}


[12/13] [KARAF-2888] New FeaturesService based on the real OSGi resolver

Posted by gn...@apache.org.
http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/internal/FeaturesServiceImpl.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/FeaturesServiceImpl.java b/features/core/src/main/java/org/apache/karaf/features/internal/FeaturesServiceImpl.java
deleted file mode 100644
index 909a963..0000000
--- a/features/core/src/main/java/org/apache/karaf/features/internal/FeaturesServiceImpl.java
+++ /dev/null
@@ -1,1192 +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;
-
-import org.apache.felix.utils.version.VersionRange;
-import org.apache.felix.utils.version.VersionTable;
-import org.apache.karaf.features.BundleInfo;
-import org.apache.karaf.features.Conditional;
-import org.apache.karaf.features.Dependency;
-import org.apache.karaf.features.Feature;
-import org.apache.karaf.features.FeatureEvent;
-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.Resolver;
-import org.apache.karaf.features.internal.BundleManager.BundleInstallerResult;
-import org.apache.karaf.util.collections.CopyOnWriteArrayIdentityList;
-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.util.tracker.ServiceTracker;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.Closeable;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.Dictionary;
-import java.util.EnumSet;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Properties;
-import java.util.Set;
-import java.util.TreeSet;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import static java.lang.String.format;
-
-/**
- * The Features service implementation.
- * Adding a repository url will load the features contained in this repository and
- * create dummy sub shells.  When invoked, these commands will prompt the user for
- * installing the needed bundles.
- */
-public class FeaturesServiceImpl implements FeaturesService {
-
-    private static final Logger LOGGER = LoggerFactory.getLogger(FeaturesServiceImpl.class);
-
-    private static final int KARAF_BUNDLE_START_LEVEL =
-            Integer.parseInt(System.getProperty("karaf.startlevel.bundle", "80"));
-
-    private final BundleManager bundleManager;
-    private final FeatureConfigInstaller configManager;
-    private final AtomicBoolean stopped = new AtomicBoolean();
-
-    private boolean respectStartLvlDuringFeatureStartup;
-    private boolean respectStartLvlDuringFeatureUninstall;
-    private long resolverTimeout = 5000;
-    private Set<URI> uris;
-    private Map<URI, Repository> repositories = new HashMap<URI, Repository>();
-    private Map<String, Map<String, Feature>> features;
-    private Map<Feature, Set<Long>> installed = new HashMap<Feature, Set<Long>>();
-    private List<FeaturesListener> listeners = new CopyOnWriteArrayIdentityList<FeaturesListener>();
-    private ThreadLocal<Repository> repo = new ThreadLocal<Repository>();
-    private EventAdminListener eventAdminListener;
-    private String overrides;
-    private FeatureFinder featureFinder;
-    
-    public FeaturesServiceImpl(BundleManager bundleManager) {
-        this(bundleManager, null);
-    }
-    
-    public FeaturesServiceImpl(BundleManager bundleManager, FeatureConfigInstaller configManager) {
-        this.bundleManager = bundleManager;
-        this.configManager = configManager;
-    }
-    
-    public long getResolverTimeout() {
-        return resolverTimeout;
-    }
-
-    public void setResolverTimeout(long resolverTimeout) {
-        this.resolverTimeout = resolverTimeout;
-    }
-
-    public void setRespectStartLvlDuringFeatureStartup(boolean respectStartLvlDuringFeatureStartup) {
-        this.respectStartLvlDuringFeatureStartup = respectStartLvlDuringFeatureStartup;
-    }
-
-    public String getOverrides() {
-        return overrides;
-    }
-
-    public void setOverrides(String overrides) {
-        this.overrides = overrides;
-    }
-
-    public FeatureFinder getFeatureFinder() {
-        return featureFinder;
-    }
-
-    public void setFeatureFinder(FeatureFinder featureFinder) {
-        this.featureFinder = featureFinder;
-    }
-
-    public void registerListener(FeaturesListener listener) {
-        listeners.add(listener);
-        try {
-            for (Repository repository : listRepositories()) {
-                listener.repositoryEvent(new RepositoryEvent(repository, RepositoryEvent.EventType.RepositoryAdded, true));
-            }
-            for (Feature feature : listInstalledFeatures()) {
-                listener.featureEvent(new FeatureEvent(feature, FeatureEvent.EventType.FeatureInstalled, true));
-            }
-        } catch (Exception e) {
-            LOGGER.error("Error notifying listener about the current state", e);
-        }
-    }
-
-    public void unregisterListener(FeaturesListener listener) {
-        listeners.remove(listener);
-    }
-
-    public void setUrls(String uris) {
-        String[] s = uris.split(",");
-        this.uris = new HashSet<URI>();
-        for (String value : s) {
-            value = value.trim();
-            if (!value.isEmpty()) {
-                try {
-                    this.uris.add(new URI(value));
-                } catch (URISyntaxException e) {
-                    LOGGER.warn("Invalid features repository URI: " + value);
-                }
-            }
-        }
-    }
-
-    /**
-     * Validate a features repository XML.
-     *
-     * @param uri the features repository URI.
-     */
-    public void validateRepository(URI uri) throws Exception {
-        
-        FeatureValidationUtil.validate(uri);
-    }
-
-    /**
-     * Add a features repository.
-     *
-     * @param uri the features repository URI.
-     * @throws Exception in case of adding failure.
-     */
-    public void addRepository(URI uri) throws Exception {
-        this.addRepository(uri, false);
-    }
-
-    /**
-     * Add a features repository.
-     *
-     * @param uri the features repository URI.
-     * @param install if true, install all features contained in the features repository.
-     * @throws Exception in case of adding failure.
-     */
-    public void addRepository(URI uri, boolean install) throws Exception {
-        if (!repositories.containsKey(uri)) {
-            Repository repositoryImpl = this.internalAddRepository(uri);
-            saveState();
-            if (install) {
-                for (Feature feature : repositoryImpl.getFeatures()) {
-                    installFeature(feature, EnumSet.noneOf(Option.class));
-                }
-            }
-        } else {
-            refreshRepository(uri, install);
-        }
-    }
-    
-    /**
-     * Refresh a features repository.
-     *
-     * @param uri the features repository URI.
-     * @throws Exception in case of refresh failure.
-     */
-    @Override
-    public void refreshRepository(URI uri) throws Exception {
-        this.refreshRepository(uri, false);
-    }
-
-    /**
-     * Refresh a features repository.
-     *
-     * @param uri the features repository URI.
-     * @param install if true, install all features in the features repository.
-     * @throws Exception in case of refresh failure.
-     */
-    protected void refreshRepository(URI uri, boolean install) throws Exception {
-        try {
-            removeRepository(uri, install);
-            addRepository(uri, install);
-        } catch (Exception e) {
-            //get chance to restore previous, fix for KARAF-4
-            restoreRepository(uri);
-            throw new Exception("Unable to refresh features repository " + uri, e);
-        }
-    }
-
-    /**
-     * Add a features repository into the internal container.
-     *
-     * @param uri the features repository URI.
-     * @return the internal <code>RepositoryImpl</code> representation.
-     * @throws Exception in case of adding failure.
-     */
-    protected Repository internalAddRepository(URI uri) throws Exception {
-        validateRepository(uri);
-        RepositoryImpl repo = new RepositoryImpl(uri);
-        repositories.put(uri, repo);
-        repo.load();
-        callListeners(new RepositoryEvent(repo, RepositoryEvent.EventType.RepositoryAdded, false));
-        features = null;
-        return repo;
-        
-    }
-
-    /**
-     * Remove a features repository.
-     *
-     * @param uri the features repository URI.
-     * @throws Exception in case of remove failure.
-     */
-    public void removeRepository(URI uri) throws Exception {
-        this.removeRepository(uri, false);
-    }
-
-    /**
-     * Remove a features repository.
-     *
-     * @param uri the features repository URI.
-     * @param uninstall if true, uninstall all features from the features repository.
-     * @throws Exception in case of remove failure.
-     */
-    public void removeRepository(URI uri, boolean uninstall) throws Exception {
-        if (repositories.containsKey(uri)) {
-            if (uninstall) {
-                Repository repositoryImpl = repositories.get(uri);
-                for (Feature feature : repositoryImpl.getFeatures()) {
-                    this.uninstallFeature(feature.getName(), feature.getVersion());
-                }
-            }
-            internalRemoveRepository(uri);
-            saveState();
-        }
-    }
-
-    /**
-     * Remove a features repository from the internal container.
-     *
-     * @param uri the features repository URI.
-     */
-    protected void internalRemoveRepository(URI uri) {
-        Repository repo = repositories.remove(uri);
-        this.repo.set(repo);
-        callListeners(new RepositoryEvent(repo, RepositoryEvent.EventType.RepositoryRemoved, false));
-        features = null;
-    }
-
-    /**
-     * Restore a features repository.
-     *
-     * @param uri the features repository URI.
-     * @throws Exception in case of restore failure.
-     */
-    public void restoreRepository(URI uri) throws Exception {
-    	repositories.put(uri, repo.get());
-    	callListeners(new RepositoryEvent(repo.get(), RepositoryEvent.EventType.RepositoryAdded, false));
-        features = null;
-    }
-
-    /**
-     * Get the list of features repository.
-     *
-     * @return the list of features repository.
-     */
-    public Repository[] listRepositories() {
-        Collection<Repository> repos = repositories.values();
-        return repos.toArray(new Repository[repos.size()]);
-    }
-    
-    @Override
-    public Repository getRepository(String repoName) {
-        for (Repository repo : this.repositories.values()) {
-            if (repoName.equals(repo.getName())) {
-                return repo;
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Install a feature identified by a name.
-     *
-     * @param name the name of the feature.
-     * @throws Exception in case of install failure.
-     */
-    public void installFeature(String name) throws Exception {
-    	installFeature(name, org.apache.karaf.features.internal.model.Feature.DEFAULT_VERSION);
-    }
-
-    /**
-     * Install a feature identified by a name, including a set of options.
-     *
-     * @param name the name of the feature.
-     * @param options the installation options.
-     * @throws Exception in case of install failure.
-     */
-    public void installFeature(String name, EnumSet<Option> options) throws Exception {
-        installFeature(name, org.apache.karaf.features.internal.model.Feature.DEFAULT_VERSION, options);
-    }
-
-    /**
-     * Install a feature identified by a name and a version.
-     *
-     * @param name the name of the feature.
-     * @param version the version of the feature.
-     * @throws Exception in case of install failure.
-     */
-    public void installFeature(String name, String version) throws Exception {
-        installFeature(name, version, EnumSet.noneOf(Option.class));
-    }
-
-    /**
-     * Install a feature identified by a name and a version, including a set of options.
-     *
-     * @param name the name of the feature.
-     * @param version the version of the feature.
-     * @param options the installation options.
-     * @throws Exception in case of install failure.
-     */
-    public void installFeature(String name, String version, EnumSet<Option> options) throws Exception {
-        Feature f = getFeature(name, version);
-        if (f == null) {
-            throw new Exception("No feature named '" + name
-            		+ "' with version '" + version + "' available");
-        }
-        installFeature(f, options);
-    }
-
-    /**
-     * Install a feature including a set of options.
-     *
-     * @param feature the <code>Feature</code> to install.
-     * @param options the installation options set.
-     * @throws Exception in case of install failure.
-     */
-    public void installFeature(Feature feature, EnumSet<Option> options) throws Exception {
-        installFeatures(Collections.singleton(feature), options);
-    }
-
-    /**
-     * Install a set of features, including a set of options.
-     *
-     * @param features a set of <code>Feature</code>.
-     * @param options the installation options set.
-     * @throws Exception in case of install failure.
-     */
-    public void installFeatures(Set<Feature> features, EnumSet<Option> options) throws Exception {
-
-        final InstallationState state = new InstallationState();
-        final InstallationState failure = new InstallationState();
-        boolean verbose = options.contains(FeaturesService.Option.Verbose);
-        try {
-            // Install everything
-            for (Feature f : features) {
-                InstallationState s = new InstallationState();
-            	try {
-                    doInstallFeature(s, f, verbose);
-                    doInstallFeatureConditionals(s, f, verbose);
-                    //Check if current feature satisfies the conditionals of existing features
-                    for (Feature installedFeature : listInstalledFeatures()) {
-                        doInstallFeatureConditionals(s, installedFeature, verbose);
-                    }
-                    for (Feature installedFeature : state.features.keySet()) {
-                        doInstallFeatureConditionals(s, installedFeature, verbose);
-                    }
-
-                    state.bundleInfos.putAll(s.bundleInfos);
-                    state.bundles.addAll(s.bundles);
-                    state.features.putAll(s.features);
-                    state.installed.addAll(s.installed);
-                    state.bundleStartLevels.putAll(s.bundleStartLevels);
-            	} catch (Exception e) {
-                    failure.bundles.addAll(s.bundles);
-                    failure.features.putAll(s.features);
-                    failure.installed.addAll(s.installed);
-                    if (options.contains(Option.ContinueBatchOnFailure)) {
-                        LOGGER.warn("Error when installing feature {}: {}", f.getName(), e);
-                    } else {
-                        throw e;
-                    }
-            	}
-            }
-            bundleManager.refreshBundles(state.bundles, state.installed, options);
-            // start all bundles sorted by startlvl if wished for
-            List<Bundle> bundlesSortedByStartLvl = new ArrayList<Bundle>(state.bundles);
-            if (respectStartLvlDuringFeatureStartup) {
-                Collections.sort(bundlesSortedByStartLvl, new Comparator<Bundle>() {
-                    @Override
-                    public int compare(Bundle bundle, Bundle bundle1) {
-                        return state.bundleStartLevels.get(bundle) - state.bundleStartLevels.get(bundle1);
-                    }
-                });
-            }
-            for (Bundle b : bundlesSortedByStartLvl) {
-                LOGGER.debug("Starting bundle: {}", b.getSymbolicName());
-                startBundle(state, b);
-            }
-            // Clean up for batch
-            if (!options.contains(Option.NoCleanIfFailure)) {
-                failure.installed.removeAll(state.bundles);
-                if (failure.installed.size()>0) {
-                    bundleManager.uninstall(failure.installed);
-                }
-            }
-            for (Feature f : features) {
-                callListeners(new FeatureEvent(f, FeatureEvent.EventType.FeatureInstalled, false));
-            }
-            for (Map.Entry<Feature, Set<Long>> e : state.features.entrySet()) {
-                installed.put(e.getKey(), e.getValue());
-            }
-            saveState();
-        } catch (Exception e) {
-            boolean noCleanIfFailure = options.contains(Option.NoCleanIfFailure);
-            cleanUpOnFailure(state, failure, noCleanIfFailure);
-            throw e;
-        }
-    }
-
-    /**
-     * Start a bundle.
-     *
-     * @param state the current bundle installation state.
-     * @param bundle the bundle to start.
-     * @throws Exception in case of start failure.
-     */
-	private void startBundle(InstallationState state, Bundle bundle) throws Exception {
-		if (!isFragment(bundle)) {
-		    // do not start bundles that are persistently stopped
-		    if (state.installed.contains(bundle)
-		            || (bundle.getState() != Bundle.STARTING && bundle.getState() != Bundle.ACTIVE
-		                    && bundle.adapt(BundleStartLevel.class).isPersistentlyStarted())) {
-		    	// do no start bundles when user request it
-		    	Long bundleId = bundle.getBundleId();
-		    	BundleInfo bundleInfo = state.bundleInfos.get(bundleId);
-		        if (bundleInfo == null || bundleInfo.isStart()) {
-		            try {
-		                bundle.start();
-		            } catch (BundleException be) {
-		                String msg = format("Could not start bundle %s in feature(s) %s: %s", bundle.getLocation(), getFeaturesContainingBundleList(bundle), be.getMessage());
-		                throw new Exception(msg, be);
-		            }
-		    	}
-		    }
-		}
-	}
-	
-	private boolean isFragment(Bundle b) {
-	    @SuppressWarnings("rawtypes")
-        Dictionary d = b.getHeaders();
-        String fragmentHostHeader = (String) d.get(Constants.FRAGMENT_HOST);
-        return fragmentHostHeader != null && fragmentHostHeader.trim().length() > 0;
-	}
-
-	private void cleanUpOnFailure(InstallationState state, InstallationState failure, boolean noCleanIfFailure) {
-		// cleanup on error
-		if (!noCleanIfFailure) {
-		    HashSet<Bundle> uninstall = new HashSet<Bundle>();
-		    uninstall.addAll(state.installed);
-		    uninstall.addAll(failure.installed);
-		    if (uninstall.size() > 0) {
-		        bundleManager.uninstall(uninstall);
-		    }
-		} else {
-		    // Force start of bundles so that they are flagged as persistently started
-		    for (Bundle b : state.installed) {
-		        try {
-		            b.start();
-		        } catch (Exception e2) {
-		            // Ignore
-		        }
-		    }
-		}
-	}
-
-    protected void doInstallFeature(InstallationState state, Feature feature, boolean verbose) throws Exception {
-        String msg = "Installing feature " + feature.getName() + " " + feature.getVersion();
-        LOGGER.info(msg);
-        if (verbose) {
-            System.out.println(msg);
-        }
-        for (Dependency dependency : feature.getDependencies()) {
-            installFeatureDependency(dependency, state, verbose);
-        }
-        if (configManager != null) {
-            configManager.installFeatureConfigs(feature, verbose);
-        }
-        Set<Long> bundles = new TreeSet<Long>();
-        
-        for (BundleInfo bInfo : Overrides.override(resolve(feature), this.overrides)) {
-            int startLevel = getBundleStartLevel(bInfo.getStartLevel(),feature.getStartLevel());
-            BundleInstallerResult result = bundleManager.installBundleIfNeeded(bInfo.getLocation(), startLevel, feature.getRegion());
-            state.bundles.add(result.bundle);
-            state.bundleStartLevels.put(result.bundle, getBundleStartLevelForOrdering(startLevel));
-            if (result.isNew) {
-                state.installed.add(result.bundle);
-            }
-            String msg2 = (result.isNew) ? "Found installed bundle: " + result.bundle : "Installing bundle " + bInfo.getLocation();
-            LOGGER.debug(msg2);
-            if (verbose) {
-                System.out.println(msg2);
-            }
-
-            bundles.add(result.bundle.getBundleId());
-            state.bundleInfos.put(result.bundle.getBundleId(), bInfo);
-
-        }
-        state.features.put(feature, bundles);
-    }
-
-    private int getBundleStartLevel(int bundleStartLevel, int featureStartLevel) {
-        return (bundleStartLevel > 0) ? bundleStartLevel : featureStartLevel;
-    }
-
-    private int getBundleStartLevelForOrdering(int startLevel){
-        return startLevel == 0 ? KARAF_BUNDLE_START_LEVEL : startLevel;
-    }
-
-    protected void doInstallFeatureConditionals(InstallationState state, Feature feature,  boolean verbose) throws Exception {
-        //Check conditions of the current feature.
-        feature = getFeature(feature.getName(), feature.getVersion());
-        if (feature != null) {
-            for (Conditional conditional : feature.getConditional()) {
-
-                if (dependenciesSatisfied(conditional.getCondition(), state)) {
-                    InstallationState s = new InstallationState();
-                    doInstallFeature(s, conditional.asFeature(feature.getName(), feature.getVersion()), verbose);
-                    state.bundleInfos.putAll(s.bundleInfos);
-                    state.bundles.addAll(s.bundles);
-                    state.features.putAll(s.features);
-                    state.installed.addAll(s.installed);
-                    state.bundleStartLevels.putAll(s.bundleStartLevels);
-                }
-            }
-        }
-    }
-
-    private void installFeatureDependency(Dependency dependency, InstallationState state, boolean verbose)
-            throws Exception {
-        Feature fi = getFeatureForDependency(dependency);
-        if (fi == null) {
-            throw new Exception("No feature named '" + dependency.getName()
-                    + "' with version '" + dependency.getVersion() + "' available");
-        }
-        if (state.features.containsKey(fi)) {
-            LOGGER.debug("Feature {} with version {} is already being installed", fi.getName(), fi.getVersion());
-        } else {
-            doInstallFeature(state, fi, verbose);
-        }
-    }
-    
-    protected List<BundleInfo> resolve(Feature feature) throws Exception {
-        String resolver = feature.getResolver();
-        // If no resolver is specified, we expect a list of uris
-        if (resolver == null || resolver.length() == 0) {
-        	return feature.getBundles();
-        }
-        boolean optional = false;
-        if (resolver.startsWith("(") && resolver.endsWith(")")) {
-            resolver = resolver.substring(1, resolver.length() - 1);
-            optional = true;
-        }
-        
-
-        @SuppressWarnings("unchecked")
-        ServiceTracker<Resolver, Resolver> tracker = bundleManager.createServiceTrackerForResolverName(resolver);
-        if (tracker == null) {
-            return feature.getBundles();
-        }
-        tracker.open();
-        try {
-            if (optional) {
-                Resolver r = (Resolver) tracker.getService();
-                if (r != null) {
-                    return r.resolve(feature);
-                } else {
-                    LOGGER.debug("Optional resolver '" + resolver + "' not found, using the default resolver");
-                    return feature.getBundles();
-                }
-            } else {
-                Resolver r = (Resolver) tracker.waitForService(resolverTimeout);
-                if (r == null) {
-                    throw new Exception("Unable to find required resolver '" + resolver + "'");
-                }
-                return r.resolve(feature);
-            }
-        } finally {
-            tracker.close();
-        }
-    }
-
-    public void uninstallFeature(String name) throws Exception {
-        uninstallFeature(name, EnumSet.noneOf(Option.class));
-    }
-
-    public void uninstallFeature(String name, EnumSet<Option> options) throws Exception {
-        List<String> versions = new ArrayList<String>();
-        for (Feature f : installed.keySet()) {
-            if (name.equals(f.getName())) {
-                versions.add(f.getVersion());
-            }
-        }
-        if (versions.size() == 0) {
-            throw new Exception("Feature named '" + name + "' is not installed");
-        } else if (versions.size() > 1) {
-            StringBuilder sb = new StringBuilder();
-            sb.append("Feature named '").append(name).append("' has multiple versions installed (");
-            for (int i = 0; i < versions.size(); i++) {
-                if (i > 0) {
-                    sb.append(", ");
-                }
-                sb.append(versions.get(i));
-            }
-            sb.append("). Please specify the version to uninstall.");
-            throw new Exception(sb.toString());
-        }
-        uninstallFeature(name, versions.get(0), options);
-    }
-
-    public void uninstallFeature(String name, String version) throws Exception {
-        uninstallFeature(name, version, EnumSet.noneOf(Option.class));
-    }
-
-    public void uninstallFeature(String name, String version, EnumSet<Option> options) throws Exception {
-    	Feature feature = getFeature(name, version);
-        if (feature == null || !installed.containsKey(feature)) {
-            throw new Exception("Feature named '" + name + "' with version '" + version + "' is not installed");
-        }
-        boolean verbose = options != null && options.contains(Option.Verbose);
-        boolean refresh = options == null || !options.contains(Option.NoAutoRefreshBundles);
-        String msg = "Uninstalling feature " + feature.getName() + " " + feature.getVersion();
-        LOGGER.info(msg);
-        if (verbose) {
-            System.out.println(msg);
-        }
-        // Grab all the bundles installed by this feature
-        // and remove all those who will still be in use.
-        // This gives this list of bundles to uninstall.
-        Set<Long> bundles = installed.remove(feature);
-
-        //Also remove bundles installed as conditionals
-        for (Conditional conditional : feature.getConditional()) {
-            Feature conditionalFeature = conditional.asFeature(feature.getName(),feature.getVersion());
-            if (installed.containsKey(conditionalFeature)) {
-                msg = "Uninstalling feature " + conditionalFeature.getName() + " " + conditionalFeature.getVersion();
-                LOGGER.info(msg);
-                if (verbose) {
-                    System.out.println(msg);
-                }
-            	bundles.addAll(installed.remove(conditionalFeature));
-            } else {
-            	LOGGER.info("Conditional feature {}, hasn't been installed!");
-            }
-        }
-        for (Feature f : new HashSet<Feature>(installed.keySet())) {
-            f = getFeature(f.getName(), f.getVersion());
-            if (f != null) {
-                for (Conditional conditional : f.getConditional()) {
-                    boolean satisfied = true;
-                    for (Dependency dep : conditional.getCondition()) {
-                        Feature df = getFeatureForDependency(dep);
-                        satisfied &= installed.containsKey(df);
-                    }
-                    if (!satisfied) {
-                        Feature conditionalFeature = conditional.asFeature(f.getName(), f.getVersion());
-                        if (installed.containsKey(conditionalFeature)) {
-                            msg = "Uninstalling feature " + conditionalFeature.getName() + " " + conditionalFeature.getVersion();
-                            LOGGER.info(msg);
-                            if (verbose) {
-                                System.out.println(msg);
-                            }
-                            bundles.addAll(installed.remove(conditionalFeature));
-                        }
-                    }
-                }
-            }
-        }
-
-        for (Set<Long> b : installed.values()) {
-            bundles.removeAll(b);
-        }
-   
-        List<Bundle> bundlesDescendSortedByStartLvl = new ArrayList<Bundle>();
-        for (long bundleId : bundles) {
-            Bundle b = bundleManager.getBundleContext().getBundle(bundleId);
-            if (b != null) {
-                bundlesDescendSortedByStartLvl.add(b);
-            }
-        }
-        
-        if (isRespectStartLvlDuringFeatureUninstall()) {
-            Collections.sort(bundlesDescendSortedByStartLvl, new Comparator<Bundle>() {
-                @Override
-                public int compare(Bundle bundle, Bundle bundle1) {
-                    return bundle1.adapt(BundleStartLevel.class).getStartLevel() -
-                        bundle.adapt(BundleStartLevel.class).getStartLevel();
-                }
-            });
-        }
-        bundleManager.uninstall(bundlesDescendSortedByStartLvl, refresh);
-        callListeners(new FeatureEvent(feature, FeatureEvent.EventType.FeatureUninstalled, false));
-        saveState();
-    }
-
-    public Feature[] listFeatures() throws Exception {
-        Set<Feature> features = new HashSet<Feature>();
-        for (Map<String, Feature> featureWithDifferentVersion : getFeatures().values()) {
-			for (Feature f : featureWithDifferentVersion.values()) {
-                features.add(f);
-            }
-        }
-        return features.toArray(new Feature[features.size()]);
-    }
-
-    public Feature[] listInstalledFeatures() throws Exception {
-        Set<Feature> features = new HashSet<Feature>();
-        for (Map<String, Feature> featureWithDifferentVersion : getFeatures().values()) {
-            for (Feature f : featureWithDifferentVersion.values()) {
-                if (installed.containsKey(f)) {
-                    features.add(f);
-                }
-            }
-        }
-        return features.toArray(new Feature[features.size()]);
-    }
-
-    public boolean isInstalled(Feature f) {
-        return installed.containsKey(f);
-    }
-
-    public Feature getFeature(String name) throws Exception {
-        return getFeature(name, org.apache.karaf.features.internal.model.Feature.DEFAULT_VERSION);
-    }
-
-    public Feature getFeature(String name, String version) throws Exception {
-        if (version != null) {
-            version = version.trim();
-        }
-        Map<String, Feature> versions = getFeatures().get(name);
-        if (versions == null || versions.isEmpty()) {
-            return null;
-        } else {
-            Feature feature = versions.get(version);
-            if (feature == null) {
-                if (org.apache.karaf.features.internal.model.Feature.DEFAULT_VERSION.equals(version)) {
-                    Version latest = new Version(cleanupVersion(version));
-                    for (String available : versions.keySet()) {
-                        Version availableVersion = new Version(cleanupVersion(available));
-                        if (availableVersion.compareTo(latest) > 0) {
-                            feature = versions.get(available);
-                            latest = availableVersion;
-                        }
-                    }
-                } else {
-                    Version latest = new Version(cleanupVersion(org.apache.karaf.features.internal.model.Feature.DEFAULT_VERSION));
-                    VersionRange versionRange = new VersionRange(version, true, true);
-                    for (String available : versions.keySet()) {
-                        Version availableVersion = new Version(cleanupVersion(available));
-                        if (availableVersion.compareTo(latest) > 0 && versionRange.contains(availableVersion)) {
-                            feature = versions.get(available);
-                            latest = availableVersion;
-                        }
-                    }
-                }
-            }
-            return feature;
-        }
-    }
-
-    @Override
-    public URI getRepositoryUriFor(String name, String version) {
-        return featureFinder.getUriFor(name, version);
-    }
-
-    @Override
-    public String[] getRepositoryNames() {
-        return featureFinder.getNames();
-    }
-
-    protected Map<String, Map<String, Feature>> getFeatures() throws Exception {
-        if (features == null) {
-        	//the outer map's key is feature name, the inner map's key is feature version       
-            Map<String, Map<String, Feature>> map = new HashMap<String, Map<String, Feature>>();
-            // Two phase load:
-            // * first load dependent repositories
-            for (;;) {
-                boolean newRepo = false;
-                for (Repository repo : listRepositories()) {
-                    for (URI uri : repo.getRepositories()) {
-                        if (!repositories.containsKey(uri)) {
-                            internalAddRepository(uri);
-                            newRepo = true;
-                        }
-                    }
-                }
-                if (!newRepo) {
-                    break;
-                }
-            }
-            // * then load all features
-            for (Repository repo : repositories.values()) {
-                for (Feature f : repo.getFeatures()) {
-                	if (map.get(f.getName()) == null) {
-                		Map<String, Feature> versionMap = new HashMap<String, Feature>();
-                		versionMap.put(f.getVersion(), f);
-                		map.put(f.getName(), versionMap);
-                	} else {
-                		map.get(f.getName()).put(f.getVersion(), f);
-                	}
-                }
-            }
-            features = map;
-        }
-        return features;
-    }
-
-	private void initState() {
-        if (!loadState()) {
-            if (uris != null) {
-                for (URI uri : uris) {
-                    try {
-                    	internalAddRepository(uri);
-                    } catch (Exception e) {
-                        LOGGER.warn(format("Unable to add features repository %s at startup", uri), e);    
-                    }
-                }
-            }
-            saveState();
-        }
-	}
-    
-    public void start() {
-        this.eventAdminListener = bundleManager.createAndRegisterEventAdminListener();
-        initState();
-    }
-
-    public void stop() {
-        stopped.set(true);
-        uris = new HashSet<URI>(repositories.keySet());
-        while (!repositories.isEmpty()) {
-            internalRemoveRepository(repositories.keySet().iterator().next());
-        }
-    }
-
-    protected void saveState() {
-        // Never save the state after the service has been stopped
-        if (stopped.get()) {
-            return;
-        }
-        OutputStream os = null;
-        try {
-            File file = bundleManager.getDataFile("FeaturesServiceState.properties");
-            Properties props = new Properties();
-            saveSet(props, "repositories.", repositories.keySet());
-            saveMap(props, "features.", installed);
-            os = new FileOutputStream(file);
-            props.store(new FileOutputStream(file), "FeaturesService State");
-        } catch (Exception e) {
-            LOGGER.error("Error persisting FeaturesService state", e);
-        } finally {
-            close(os);
-        }
-    }
-
-    protected boolean loadState() {
-        try {
-            File file = bundleManager.getDataFile("FeaturesServiceState.properties");
-            if (!file.exists()) {
-                return false;
-            }
-            Properties props = new Properties();
-            InputStream is = new FileInputStream(file);
-            try {
-                props.load(is);
-            } finally {
-                close(is);
-            }
-            Set<URI> repositories = loadSet(props, "repositories.");
-            for (URI repo : repositories) {
-            	try {
-            		internalAddRepository(repo);
-            	} catch (Exception e) {
-            		LOGGER.warn(format("Unable to add features repository %s at startup", repo), e);
-            	}
-            }
-            installed = loadMap(props, "features.");
-            for (Feature f : installed.keySet()) {
-                callListeners(new FeatureEvent(f, FeatureEvent.EventType.FeatureInstalled, true));
-            }
-            return true;
-        } catch (Exception e) {
-            LOGGER.error("Error loading FeaturesService state", e);
-        }
-        return false;
-    }
-
-    private void close(Closeable closeable) {
-        if (closeable != null) {
-            try {
-                closeable.close();
-            } catch (IOException e) {
-                // Ignore
-            }
-        }
-    }
-
-    protected void saveSet(Properties props, String prefix, Set<URI> set) {
-        List<URI> l = new ArrayList<URI>(set);
-        props.clear();
-        props.put(prefix + "count", Integer.toString(l.size()));
-        for (int i = 0; i < l.size(); i++) {
-            props.put(prefix + "item." + i, l.get(i).toString());
-        }
-    }
-
-    protected Set<URI> loadSet(Properties props, String prefix) {
-        Set<URI> l = new HashSet<URI>();
-        String countStr = (String) props.get(prefix + "count");
-        if (countStr != null) {
-            int count = Integer.parseInt(countStr);
-            for (int i = 0; i < count; i++) {
-                l.add(URI.create((String) props.get(prefix + "item." + i)));
-            }
-        }
-        return l;
-    }
-
-    protected void saveMap(Properties props, String prefix, Map<Feature, Set<Long>> map) {
-        for (Map.Entry<Feature, Set<Long>> entry : map.entrySet()) {
-            Feature key = entry.getKey();
-            String val = createValue(entry.getValue());
-            props.put(prefix + key.toString(), val);
-        }
-    }
-
-    protected Map<Feature, Set<Long>> loadMap(Properties props, String prefix) {
-        Map<Feature, Set<Long>> map = new HashMap<Feature, Set<Long>>();
-        for (@SuppressWarnings("rawtypes") Enumeration e = props.propertyNames(); e.hasMoreElements();) {
-            String key = (String) e.nextElement();
-            if (key.startsWith(prefix)) {
-                String val = (String) props.get(key);
-                Set<Long> set = readValue(val);
-                map.put(org.apache.karaf.features.internal.model.Feature.valueOf(key.substring(prefix.length())), set);
-            }
-        }
-        return map;
-    }
-
-    protected String createValue(Set<Long> set) {
-        StringBuilder sb = new StringBuilder();
-        for (long i : set) {
-            if (sb.length() > 0) {
-                sb.append(",");
-            }
-            sb.append(i);
-        }
-        return sb.toString();
-    }
-
-    protected Set<Long> readValue(String val) {
-        Set<Long> set = new HashSet<Long>();
-        if (val != null && val.length() != 0) {
-        	for (String str : val.split(",")) {
-        		set.add(Long.parseLong(str));
-        	}
-        }
-        return set;
-    }
-
-    protected void callListeners(FeatureEvent event) {
-        if (eventAdminListener != null) {
-            eventAdminListener.featureEvent(event);
-        }
-        for (FeaturesListener listener : listeners) {
-            listener.featureEvent(event);
-        }
-    }
-
-    protected void callListeners(RepositoryEvent event) {
-        if (eventAdminListener != null) {
-            eventAdminListener.repositoryEvent(event);
-        }
-        for (FeaturesListener listener : listeners) {
-            listener.repositoryEvent(event);
-        }
-    }
-
-    static Pattern fuzzyVersion  = Pattern.compile("(\\d+)(\\.(\\d+)(\\.(\\d+))?)?([^a-zA-Z0-9](.*))?",
-                                                   Pattern.DOTALL);
-    static Pattern fuzzyModifier = Pattern.compile("(\\d+[.-])*(.*)",
-                                                   Pattern.DOTALL);
-
-    /**
-     * Clean up version parameters. Other builders use more fuzzy definitions of
-     * the version syntax. This method cleans up such a version to match an OSGi
-     * version.
-     *
-     * @param version possibly bundles-non-compliant version
-     * @return osgi compliant version
-     */
-    static public String cleanupVersion(String version) {
-        Matcher m = fuzzyVersion.matcher(version);
-        if (m.matches()) {
-            StringBuffer result = new StringBuffer();
-            String d1 = m.group(1);
-            String d2 = m.group(3);
-            String d3 = m.group(5);
-            String qualifier = m.group(7);
-
-            if (d1 != null) {
-                result.append(d1);
-                if (d2 != null) {
-                    result.append(".");
-                    result.append(d2);
-                    if (d3 != null) {
-                        result.append(".");
-                        result.append(d3);
-                        if (qualifier != null) {
-                            result.append(".");
-                            cleanupModifier(result, qualifier);
-                        }
-                    } else if (qualifier != null) {
-                        result.append(".0.");
-                        cleanupModifier(result, qualifier);
-                    }
-                } else if (qualifier != null) {
-                    result.append(".0.0.");
-                    cleanupModifier(result, qualifier);
-                }
-                return result.toString();
-            }
-        }
-        return version;
-    }
-
-    static void cleanupModifier(StringBuffer result, String modifier) {
-        Matcher m = fuzzyModifier.matcher(modifier);
-        if (m.matches())
-            modifier = m.group(2);
-
-        for (int i = 0; i < modifier.length(); i++) {
-            char c = modifier.charAt(i);
-            if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z')
-                    || (c >= 'A' && c <= 'Z') || c == '_' || c == '-')
-                result.append(c);
-        }
-    }
-
-    public Set<Feature> getFeaturesContainingBundle (Bundle bundle) throws Exception {
-        Set<Feature> features = new HashSet<Feature>();
-        for (Map<String, Feature> featureMap : this.getFeatures().values()) {
-            for (Feature f : featureMap.values()) {
-                for (BundleInfo bi : f.getBundles()) {
-                    if (bi.getLocation().equals(bundle.getLocation())) {
-                        features.add(f);
-                        break;
-                    }
-                }
-            }
-        }
-        return features;
-    }
-
-    private String getFeaturesContainingBundleList(Bundle bundle) throws Exception {
-        Set<Feature> features = getFeaturesContainingBundle(bundle);
-        StringBuilder buffer = new StringBuilder();
-        Iterator<Feature> iter = features.iterator();
-        while (iter.hasNext()) {
-            Feature feature= iter.next();
-            buffer.append(feature.getId());
-            if (iter.hasNext()) {
-                buffer.append(", ");
-            }
-        }
-        return buffer.toString();
-    }
-
-    /**
-     * Returns the {@link Feature} that matches the {@link Dependency}.
-     * @param dependency
-     * @return
-     * @throws Exception
-     */
-    private Feature getFeatureForDependency(Dependency dependency) throws Exception {
-        VersionRange range = org.apache.karaf.features.internal.model.Feature.DEFAULT_VERSION.equals(dependency.getVersion())
-                ? VersionRange.ANY_VERSION : new VersionRange(dependency.getVersion(), true, true);
-        Feature fi = null;
-        for (Feature f : installed.keySet()) {
-            if (f.getName().equals(dependency.getName())) {
-                Version v = VersionTable.getVersion(f.getVersion());
-                if (range.contains(v)) {
-                    if (fi == null || VersionTable.getVersion(fi.getVersion()).compareTo(v) < 0) {
-                        fi = f;
-                    }
-                }
-            }
-        }
-        if (fi == null) {
-            Map<String, Feature> avail = getFeatures().get(dependency.getName());
-            if (avail != null) {
-                for (Feature f : avail.values()) {
-                    Version v = VersionTable.getVersion(f.getVersion());
-                    if (range.contains(v)) {
-                        if (fi == null || VersionTable.getVersion(fi.getVersion()).compareTo(v) < 0) {
-                            fi = f;
-                        }
-                    }
-                }
-            }
-        }
-        return fi;
-    }
-
-    /**
-     * Estimates if the {@link List} of {@link Dependency} is satisfied.
-     * The method will look into {@link Feature}s that are already installed or now being installed (if {@link InstallationState} is provided (not null)).
-     * @param dependencies
-     * @param state
-     * @return
-     */
-    private boolean dependenciesSatisfied(List<? extends Dependency> dependencies, InstallationState state) throws Exception {
-       boolean satisfied = true;
-       for (Dependency dep : dependencies) {
-           Feature f = getFeatureForDependency(dep);
-           if (f != null && !isInstalled(f) && (state != null && !state.features.keySet().contains(f))) {
-               satisfied = false;
-           }
-       }
-       return satisfied;
-    }
-
-    public boolean isRespectStartLvlDuringFeatureUninstall() {
-        return respectStartLvlDuringFeatureUninstall;
-    }
-
-    public void setRespectStartLvlDuringFeatureUninstall(boolean respectStartLvlDuringFeatureUninstall) {
-        this.respectStartLvlDuringFeatureUninstall = respectStartLvlDuringFeatureUninstall;
-    }
-}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/internal/InstallationState.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/InstallationState.java b/features/core/src/main/java/org/apache/karaf/features/internal/InstallationState.java
deleted file mode 100644
index 0594497..0000000
--- a/features/core/src/main/java/org/apache/karaf/features/internal/InstallationState.java
+++ /dev/null
@@ -1,35 +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;
-
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeSet;
-
-import org.apache.karaf.features.BundleInfo;
-import org.apache.karaf.features.Feature;
-import org.osgi.framework.Bundle;
-
-public class InstallationState {
-    final Set<Bundle> installed = new HashSet<Bundle>();
-    final Set<Bundle> bundles = new TreeSet<Bundle>();
-    final Map<Long, BundleInfo> bundleInfos = new HashMap<Long, BundleInfo>();
-    final Map<Bundle,Integer> bundleStartLevels = new HashMap<Bundle, Integer>();
-    final Map<Feature, Set<Long>> features = new HashMap<Feature, Set<Long>>();
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/internal/Overrides.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/Overrides.java b/features/core/src/main/java/org/apache/karaf/features/internal/Overrides.java
deleted file mode 100644
index dab988a..0000000
--- a/features/core/src/main/java/org/apache/karaf/features/internal/Overrides.java
+++ /dev/null
@@ -1,230 +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;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.jar.Manifest;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipInputStream;
-
-import org.apache.felix.utils.manifest.Clause;
-import org.apache.felix.utils.manifest.Parser;
-import org.apache.felix.utils.version.VersionRange;
-import org.apache.felix.utils.version.VersionTable;
-import org.apache.karaf.features.BundleInfo;
-import org.apache.karaf.features.internal.model.Bundle;
-import org.osgi.framework.Constants;
-import org.osgi.framework.Version;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Helper class to deal with overriden bundles at feature installation time.
- \*/
-public class Overrides {
-
-    private static final Logger LOGGER = LoggerFactory.getLogger(Overrides.class);
-
-    protected static final String OVERRIDE_RANGE = "range";
-    private static final String VENDOR_WARNING = "Bundle Vendor has changed, please check if this is intentional. Bundle: ";
-
-    /**
-     * Compute a list of bundles to install, taking into account overrides.
-     *
-     * The file containing the overrides will be loaded from the given url.
-     * Blank lines and lines starting with a '#' will be ignored, all other lines
-     * are considered as urls to override bundles.
-     *
-     * The list of bundles to install will be scanned and for each bundle,
-     * if a bundle override matches that bundle, it will be used instead.
-     *
-     * Matching is done on bundle symbolic name (they have to be the same)
-     * and version (the bundle override version needs to be greater than the
-     * bundle to be installed, and less than the next minor version.  A range
-     * directive can be added to the override url in which case, the matching
-     * will succeed if the bundle to be installed is within the given range.
-     *
-     * @param infos the list of bundles to install
-     * @param overridesUrl url pointing to the file containing the list of override bundles
-     * @return a new list of bundles to install
-     */
-    public static List<BundleInfo> override(List<BundleInfo> infos, String overridesUrl) {
-        List<Clause> overrides = loadOverrides(overridesUrl);
-        if (overrides.isEmpty()) {
-            return infos;
-        }
-        try {
-            Map<String, Manifest> manifests = new HashMap<String, Manifest>();
-            for (Clause override : overrides) {
-                Manifest manifest = getManifest(override.getName());
-                manifests.put(override.getName(), manifest);
-            }
-            List<BundleInfo> newInfos = new ArrayList<BundleInfo>();
-            for (BundleInfo info : infos) {
-                Manifest manifest = getManifest(info.getLocation());
-                if (manifest != null) {
-                    String bsn = getBundleSymbolicName(manifest);
-                    Version ver = getBundleVersion(manifest);
-                    String ven = getBundleVendor(manifest);
-                    String url = info.getLocation();
-                    for (Clause override : overrides) {
-                        Manifest overMan = manifests.get(override.getName());
-                        if (overMan == null) {
-                            continue;
-                        }
-                        String oBsn = getBundleSymbolicName(overMan);
-                        if (!bsn.equals(oBsn)) {
-                            continue;
-                        }
-
-                        Version oVer = getBundleVersion(overMan);
-                        VersionRange range;
-                        String vr = extractVersionRange(override);
-                        if (vr == null) {
-                            // default to micro version compatibility
-                            Version v2 = new Version(oVer.getMajor(), oVer.getMinor(), 0);
-                            if (v2.equals(oVer)) {
-                                continue;
-                            }
-                            range = new VersionRange(false, v2, oVer, true);
-                        } else {
-                            range = VersionRange.parseVersionRange(vr);
-                        }
-
-                        String vendor = getBundleVendor(overMan);
-
-                        // Before we do a replace, lets check if vendors change
-                        if (ven == null) {
-                             if (vendor != null) {
-                                 LOGGER.warn(VENDOR_WARNING + bsn);
-                             }
-                        } else {
-                             if (vendor == null) {
-                                 LOGGER.warn(VENDOR_WARNING + bsn);
-                             } else {
-                                  if (!vendor.equals(ven)) {
-                                      LOGGER.warn(VENDOR_WARNING + bsn);
-                                  } 
-                             }
-                        }
-                        // The resource matches, so replace it with the overridden resource
-                        // if the override is actually a newer version than what we currently have
-                        if (range.contains(ver) && ver.compareTo(oVer) < 0) {
-                            LOGGER.warn("Overriding original bundle " + url + " to " + override.getName());
-                            ver = oVer;
-                            url = override.getName();
-                        }
-                    }
-                    if (!info.getLocation().equals(url)) {
-                        Bundle b = new Bundle();
-                        b.setLocation(url);
-                        b.setStartLevel(info.getStartLevel());
-                        b.setStart(info.isStart());
-                        b.setDependency(info.isDependency());
-                        newInfos.add(b);
-                    } else {
-                        newInfos.add(info);
-                    }
-                } else {
-                    newInfos.add(info);
-                }
-            }
-            return newInfos;
-        } catch (Exception e) {
-            LOGGER.info("Unable to process bundle overrides", e);
-            return infos;
-        }
-    }
-
-    public static List<Clause> loadOverrides(String overridesUrl) {
-        List<Clause> overrides = new ArrayList<Clause>();
-        try {
-            if (overridesUrl != null) {
-                InputStream is = new URL(overridesUrl).openStream();
-                try {
-                    BufferedReader reader = new BufferedReader(new InputStreamReader(is));
-                    String line;
-                    while ((line = reader.readLine()) != null) {
-                        line = line.trim();
-                        if (!line.isEmpty() && !line.startsWith("#")) {
-                            Clause[] cs = Parser.parseClauses(new String[]{line});
-                            Collections.addAll(overrides, cs);
-                        }
-                    }
-                } finally {
-                    is.close();
-                }
-            }
-        } catch (Exception e) {
-            LOGGER.debug("Unable to load overrides bundles list", e);
-        }
-        return overrides;
-    }
-
-    private static Version getBundleVersion(Manifest manifest) {
-        String ver = manifest.getMainAttributes().getValue(Constants.BUNDLE_VERSION);
-        return VersionTable.getVersion(ver);
-    }
-
-    private static String getBundleSymbolicName(Manifest manifest) {
-        String bsn = manifest.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME);
-        bsn = stripSymbolicName(bsn);
-        return bsn;
-    }
-
-    private static String getBundleVendor(Manifest manifest) {
-        String ven = manifest.getMainAttributes().getValue(Constants.BUNDLE_VENDOR);
-        return ven;
-    }
-
-    private static Manifest getManifest(String url) throws IOException {
-        InputStream is = new URL(url).openStream();
-        try {
-            ZipInputStream zis = new ZipInputStream(is);
-            ZipEntry entry = null;
-            while ( (entry = zis.getNextEntry()) != null ) {
-                if ("META-INF/MANIFEST.MF".equals(entry.getName())) {
-                    return new Manifest(zis);
-                }
-            }
-            return null;
-        } finally {
-            is.close();
-        }
-    }
-
-    private static String extractVersionRange(Clause override) {
-        return override.getAttribute(OVERRIDE_RANGE);
-    }
-
-    private static String stripSymbolicName(String symbolicName) {
-        Clause[] cs = Parser.parseHeader(symbolicName);
-        if (cs == null || cs.length != 1) {
-            throw new IllegalArgumentException("Bad Bundle-SymbolicName header: " + symbolicName);
-        }
-        return cs[0].getName();
-    }
-}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/internal/RepositoryImpl.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/RepositoryImpl.java b/features/core/src/main/java/org/apache/karaf/features/internal/RepositoryImpl.java
deleted file mode 100644
index 435ba36..0000000
--- a/features/core/src/main/java/org/apache/karaf/features/internal/RepositoryImpl.java
+++ /dev/null
@@ -1,99 +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;
-
-import java.io.BufferedInputStream;
-import java.io.FilterInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InterruptedIOException;
-import java.net.URI;
-import org.apache.karaf.features.Repository;
-import org.apache.karaf.features.internal.model.Features;
-import org.apache.karaf.features.internal.model.JaxbUtil;
-
-/**
- * The repository implementation.
- */
-public class RepositoryImpl implements Repository {
-
-    private URI uri;
-    private boolean valid;
-    private Features features;
-
-    public RepositoryImpl(URI uri) {
-        this.uri = uri;
-    }
-
-    public String getName() {
-        return features.getName();
-    }
-
-    public URI getURI() {
-        return uri;
-    }
-
-    public URI[] getRepositories() throws Exception {
-        load();
-        URI[] result = new URI[features.getRepository().size()];
-        for (int i = 0; i < features.getRepository().size(); i++) {
-            String uri = features.getRepository().get(i);
-            uri = uri.trim();
-            result[i] = URI.create(uri);
-        }
-        return result;
-    }
-
-    public org.apache.karaf.features.Feature[] getFeatures() throws Exception {
-        load();
-        return features.getFeature().toArray(new org.apache.karaf.features.Feature[0]);
-    }
-
-
-    public void load() throws IOException {
-        if (features == null) {
-            try {
-                InputStream inputStream = uri.toURL().openStream();
-                inputStream = new FilterInputStream(inputStream) {
-    				@Override
-    				public int read(byte b[], int off, int len) throws IOException {
-    					if (Thread.currentThread().isInterrupted()) {
-    						throw new InterruptedIOException();
-    					}
-    					return super.read(b, off, len);
-    				}
-    			};
-                try {
-                    features = JaxbUtil.unmarshal(inputStream, false);
-                } finally {
-                    inputStream.close();
-                }
-                valid = true;
-            } catch (IllegalArgumentException e) {
-                throw (IOException) new IOException(e.getMessage() + " : " + uri).initCause(e);
-            } catch (Exception e) {
-                throw (IOException) new IOException(e.getMessage() + " : " + uri).initCause(e);
-            }
-        }
-    }
-
-    public boolean isValid() {
-        return this.valid;
-    }
-
-}
-

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/internal/deployment/DeploymentBuilder.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/deployment/DeploymentBuilder.java b/features/core/src/main/java/org/apache/karaf/features/internal/deployment/DeploymentBuilder.java
new file mode 100644
index 0000000..7226471
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/deployment/DeploymentBuilder.java
@@ -0,0 +1,330 @@
+/*
+ * 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.deployment;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+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.ResolverImpl;
+import org.apache.felix.utils.version.VersionRange;
+import org.apache.felix.utils.version.VersionTable;
+import org.apache.karaf.features.BundleInfo;
+import org.apache.karaf.features.Conditional;
+import org.apache.karaf.features.Dependency;
+import org.apache.karaf.features.Feature;
+import org.apache.karaf.features.Repository;
+import org.apache.karaf.features.internal.repository.AggregateRepository;
+import org.apache.karaf.features.internal.repository.StaticRepository;
+import org.apache.karaf.features.internal.resolver.FeatureNamespace;
+import org.apache.karaf.features.internal.resolver.FeatureResource;
+import org.apache.karaf.features.internal.resolver.RequirementImpl;
+import org.apache.karaf.features.internal.resolver.ResolveContextImpl;
+import org.apache.karaf.features.internal.resolver.ResourceBuilder;
+import org.apache.karaf.features.internal.resolver.ResourceImpl;
+import org.apache.karaf.features.internal.resolver.Slf4jResolverLog;
+import org.apache.karaf.features.internal.service.Overrides;
+import org.apache.karaf.features.internal.util.Macro;
+import org.apache.karaf.features.internal.util.MultiException;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.Version;
+import org.osgi.framework.namespace.IdentityNamespace;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Requirement;
+import org.osgi.resource.Resource;
+import org.osgi.resource.Wire;
+import org.osgi.service.resolver.ResolutionException;
+import org.osgi.service.resolver.ResolveContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ */
+public class DeploymentBuilder {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(DeploymentBuilder.class);
+
+    public static final String REQ_PROTOCOL = "req:";
+
+    private final Collection<Repository> repositories;
+
+    private final List<org.osgi.service.repository.Repository> resourceRepos;
+
+    String featureRange = "${range;[====,====]}";
+
+    Downloader downloader;
+    ResourceImpl requirements;
+    Map<String, Resource> resources;
+    Map<String, StreamProvider> providers;
+
+    Set<Feature> featuresToRegister = new HashSet<Feature>();
+
+    public DeploymentBuilder(Downloader downloader,
+                             Collection<Repository> repositories) {
+        this.downloader = downloader;
+        this.repositories = repositories;
+        this.resourceRepos = new ArrayList<org.osgi.service.repository.Repository>();
+    }
+
+    public void addResourceRepository(org.osgi.service.repository.Repository repository) {
+        resourceRepos.add(repository);
+    }
+
+    public Map<String, StreamProvider> getProviders() {
+        return providers;
+    }
+
+    public void setFeatureRange(String featureRange) {
+        this.featureRange = featureRange;
+    }
+
+    public Map<String, Resource> download(
+                         Set<String> features,
+                         Set<String> bundles,
+                         Set<String> reqs,
+                         Set<String> overrides,
+                         Set<String> optionals)
+                throws IOException, MultiException, InterruptedException, ResolutionException, BundleException {
+        this.resources = new ConcurrentHashMap<String, Resource>();
+        this.providers = new ConcurrentHashMap<String, StreamProvider>();
+        this.requirements = new ResourceImpl("dummy", "dummy", Version.emptyVersion);
+        // First, gather all bundle resources
+        for (String feature : features) {
+            registerMatchingFeatures(feature);
+        }
+        for (String bundle : bundles) {
+            downloadAndBuildResource(bundle);
+        }
+        for (String req : reqs) {
+            buildRequirement(req);
+        }
+        for (String override : overrides) {
+            // TODO: ignore download failures for overrides
+            downloadAndBuildResource(Overrides.extractUrl(override));
+        }
+        for (String optional : optionals) {
+            downloadAndBuildResource(optional);
+        }
+        // Wait for all resources to be created
+        downloader.await();
+        // Do override replacement
+        Overrides.override(resources, overrides);
+        // Build features resources
+        for (Feature feature : featuresToRegister) {
+            Resource resource = FeatureResource.build(feature, featureRange, resources);
+            resources.put("feature:" + feature.getName() + "/" + feature.getVersion(), resource);
+        }
+        // Build requirements
+        for (String feature : features) {
+            requireFeature(feature, requirements);
+        }
+        for (String bundle : bundles) {
+            requireResource(bundle);
+        }
+        for (String req : reqs) {
+            requireResource(REQ_PROTOCOL + req);
+        }
+        return resources;
+    }
+
+    public Collection<Resource> resolve(List<Resource> systemBundles,
+                                        boolean resolveOptionalImports) throws ResolutionException {
+        // Resolve
+        for (int i = 0; i < systemBundles.size(); i++) {
+            resources.put("system-bundle-" + i, systemBundles.get(i));
+        }
+
+        List<org.osgi.service.repository.Repository> repos = new ArrayList<org.osgi.service.repository.Repository>();
+        repos.add(new StaticRepository(resources.values()));
+        repos.addAll(resourceRepos);
+
+        ResolverImpl resolver = new ResolverImpl(new Slf4jResolverLog(LOGGER));
+        ResolveContext context = new ResolveContextImpl(
+                Collections.<Resource>singleton(requirements),
+                Collections.<Resource>emptySet(),
+                new AggregateRepository(repos),
+                resolveOptionalImports);
+
+        Map<Resource, List<Wire>> resolution = resolver.resolve(context);
+        return resolution.keySet();
+    }
+
+    public void requireFeature(String feature, ResourceImpl resource) throws IOException {
+        // Find name and version range
+        String[] split = feature.split("/");
+        String name = split[0].trim();
+        String version = (split.length > 1) ? split[1].trim() : null;
+        if (version != null && !version.equals("0.0.0") && !version.startsWith("[") && !version.startsWith("(")) {
+            version = Macro.transform(featureRange, version);
+        }
+        VersionRange range = version != null ? new VersionRange(version) : VersionRange.ANY_VERSION;
+        // Add requirement
+        Map<String, Object> attrs = new HashMap<String, Object>();
+        attrs.put(IdentityNamespace.IDENTITY_NAMESPACE, name);
+        attrs.put(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE, FeatureNamespace.TYPE_FEATURE);
+        attrs.put(IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE, range);
+        resource.addRequirement(
+                new RequirementImpl(resource, IdentityNamespace.IDENTITY_NAMESPACE,
+                        Collections.<String, String>emptyMap(), attrs)
+        );
+    }
+
+    public void requireResource(String location) {
+        Resource res = resources.get(location);
+        if (res == null) {
+            throw new IllegalStateException("Could not find resource for " + location);
+        }
+        List<Capability> caps = res.getCapabilities(IdentityNamespace.IDENTITY_NAMESPACE);
+        if (caps.size() != 1) {
+            throw new IllegalStateException("Resource does not have a single " + IdentityNamespace.IDENTITY_NAMESPACE + " capability");
+        }
+        Capability cap = caps.get(0);
+        // Add requirement
+        Map<String, Object> attrs = new HashMap<String, Object>();
+        attrs.put(IdentityNamespace.IDENTITY_NAMESPACE, cap.getAttributes().get(IdentityNamespace.IDENTITY_NAMESPACE));
+        attrs.put(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE, cap.getAttributes().get(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE));
+        attrs.put(IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE, new VersionRange((Version) cap.getAttributes().get(IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE), true));
+        requirements.addRequirement(
+                new RequirementImpl(requirements, IdentityNamespace.IDENTITY_NAMESPACE,
+                        Collections.<String, String>emptyMap(), attrs));
+
+    }
+
+    public void registerMatchingFeatures(String feature) throws IOException {
+        // Find name and version range
+        String[] split = feature.split("/");
+        String name = split[0].trim();
+        String version = (split.length > 1)
+                ? split[1].trim() : Version.emptyVersion.toString();
+        // Register matching features
+        registerMatchingFeatures(name, new VersionRange(version));
+    }
+
+    public void registerMatchingFeatures(String name, String version) throws IOException {
+        if (version != null && !version.equals("0.0.0") && !version.startsWith("[") && !version.startsWith("(")) {
+            version = Macro.transform(featureRange, version);
+        }
+        registerMatchingFeatures(name, version != null ? new VersionRange(version) : VersionRange.ANY_VERSION);
+    }
+
+    public void registerMatchingFeatures(String name, VersionRange range) throws IOException {
+        for (Repository repo : repositories) {
+            Feature[] features;
+            try {
+                features = repo.getFeatures();
+            } catch (Exception e) {
+                // This should not happen as the repository has been loaded already
+                throw new IllegalStateException(e);
+            }
+            for (Feature f : features) {
+                if (name.equals(f.getName())) {
+                    Version v = VersionTable.getVersion(f.getVersion());
+                    if (range.contains(v)) {
+                        featuresToRegister.add(f);
+                        for (Dependency dep : f.getDependencies()) {
+                            registerMatchingFeatures(dep.getName(), dep.getVersion());
+                        }
+                        for (BundleInfo bundle : f.getBundles()) {
+                            downloadAndBuildResource(bundle.getLocation());
+                        }
+                        for (Conditional cond : f.getConditional()) {
+                            Feature c = cond.asFeature(f.getName(), f.getVersion());
+                            featuresToRegister.add(c);
+                            for (BundleInfo bundle : c.getBundles()) {
+                                downloadAndBuildResource(bundle.getLocation());
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    public void buildRequirement(String requirement) {
+        try {
+            String location = REQ_PROTOCOL + requirement;
+            ResourceImpl resource = new ResourceImpl(location, "dummy", Version.emptyVersion);
+            for (Requirement req : ResourceBuilder.parseRequirement(resource, requirement)) {
+                resource.addRequirement(req);
+            }
+            resources.put(location, resource);
+        } catch (BundleException e) {
+            throw new IllegalArgumentException("Error parsing requirement: " + requirement, e);
+        }
+    }
+
+    public void downloadAndBuildResource(final String location) throws IOException {
+        if (!resources.containsKey(location)) {
+            downloader.download(location, new Downloader.DownloadCallback() {
+                @Override
+                public void downloaded(StreamProvider provider) throws Exception {
+                    manageResource(location, provider);
+                }
+            });
+        }
+    }
+
+    private void manageResource(String location, StreamProvider provider) throws Exception {
+        if (!resources.containsKey(location)) {
+            Attributes attributes = getAttributes(location, provider);
+            Resource resource = createResource(location, attributes);
+            resources.put(location, resource);
+            providers.put(location, provider);
+        }
+    }
+
+    private Resource createResource(String uri, Attributes attributes) throws Exception {
+        Map<String, String> headers = new HashMap<String, String>();
+        for (Map.Entry attr : attributes.entrySet()) {
+            headers.put(attr.getKey().toString(), attr.getValue().toString());
+        }
+        try {
+            return ResourceBuilder.build(uri, headers);
+        } catch (BundleException e) {
+            throw new Exception("Unable to create resource for bundle " + uri, e);
+        }
+    }
+
+    protected Attributes getAttributes(String uri, StreamProvider provider) throws Exception {
+        InputStream is = provider.open();
+        try {
+            ZipInputStream zis = new ZipInputStream(is);
+            ZipEntry entry;
+            while ( (entry = zis.getNextEntry()) != null ) {
+                if ("META-INF/MANIFEST.MF".equals(entry.getName())) {
+                    return new Manifest(zis).getMainAttributes();
+                }
+            }
+        } finally {
+            is.close();
+        }
+        throw new IllegalArgumentException("Resource " + uri + " does not contain a manifest");
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/internal/deployment/Downloader.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/deployment/Downloader.java b/features/core/src/main/java/org/apache/karaf/features/internal/deployment/Downloader.java
new file mode 100644
index 0000000..2d5dd98
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/deployment/Downloader.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.deployment;
+
+import java.net.MalformedURLException;
+
+import org.apache.karaf.features.internal.util.MultiException;
+
+public interface Downloader {
+
+    void await() throws InterruptedException, MultiException;
+
+    void download(String location, DownloadCallback downloadCallback) throws MalformedURLException;
+
+    interface DownloadCallback {
+
+        void downloaded(StreamProvider provider) throws Exception;
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/38502e41/features/core/src/main/java/org/apache/karaf/features/internal/deployment/StreamProvider.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/deployment/StreamProvider.java b/features/core/src/main/java/org/apache/karaf/features/internal/deployment/StreamProvider.java
new file mode 100644
index 0000000..60a3dfc
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/deployment/StreamProvider.java
@@ -0,0 +1,26 @@
+/*
+ * 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.deployment;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public interface StreamProvider {
+
+    InputStream open() throws IOException;
+
+}