karaf-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From gno...@apache.org
Subject [11/13] [KARAF-2888] New FeaturesService based on the real OSGi resolver
Date Mon, 07 Apr 2014 08:53:02 GMT
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);
+    }
+
+}


Mime
View raw message