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/23 15:49:16 UTC

[1/4] git commit: [KARAF-2923] Remove unused configuration file for regions

Repository: karaf
Updated Branches:
  refs/heads/master 56025161e -> 96a0920c6


[KARAF-2923] Remove unused configuration file for regions

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

Branch: refs/heads/master
Commit: 7456445c876666cceca1771ca4c465e39610aa41
Parents: 5602516
Author: Guillaume Nodet <gn...@gmail.com>
Authored: Tue Apr 22 17:12:53 2014 +0200
Committer: Guillaume Nodet <gn...@gmail.com>
Committed: Wed Apr 23 15:48:07 2014 +0200

----------------------------------------------------------------------
 .../resources/etc/regions-config.xml            | 37 --------------------
 .../core/internal/InstanceServiceImpl.java      |  1 -
 2 files changed, 38 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/karaf/blob/7456445c/assemblies/features/framework/src/main/filtered-resources/resources/etc/regions-config.xml
----------------------------------------------------------------------
diff --git a/assemblies/features/framework/src/main/filtered-resources/resources/etc/regions-config.xml b/assemblies/features/framework/src/main/filtered-resources/resources/etc/regions-config.xml
deleted file mode 100644
index 9aca3a8..0000000
--- a/assemblies/features/framework/src/main/filtered-resources/resources/etc/regions-config.xml
+++ /dev/null
@@ -1,37 +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.
--->
-<regions xmlns="http://karaf.apache.org/xmlns/region/v1.0.0">
-    <region name="org.apache.karaf.region.application"></region>
-
-    <filter to="org.eclipse.equinox.region.kernel" from="org.apache.karaf.region.application">
-        <package name="org.slf4j"/>
-        <package name="org.apache.aries.util"/>
-        <package name="org.apache.aries.util.io"/>
-        <package name="org.apache.aries.util.nls"/>
-        <package name="org.apache.aries.util.tracker"/>
-        <package name="org.apache.aries.util.service.registry"/>
-        <package name="org.apache.aries.proxy.weaving"/>
-        <package name="org.apache.aries.proxy"/>
-        <package name="org.apache.aries.blueprint"/>
-        <package name="org.apache.aries.blueprint.mutable"/>
-        <package name="org.osgi.service.blueprint"/>
-        <package name="org.osgi.service.blueprint.container"/>
-        <package name="org.osgi.service.blueprint.reflect"/>
-    </filter>
-</regions>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/karaf/blob/7456445c/instance/src/main/java/org/apache/karaf/instance/core/internal/InstanceServiceImpl.java
----------------------------------------------------------------------
diff --git a/instance/src/main/java/org/apache/karaf/instance/core/internal/InstanceServiceImpl.java b/instance/src/main/java/org/apache/karaf/instance/core/internal/InstanceServiceImpl.java
index 535c2a0..01a4fa1 100644
--- a/instance/src/main/java/org/apache/karaf/instance/core/internal/InstanceServiceImpl.java
+++ b/instance/src/main/java/org/apache/karaf/instance/core/internal/InstanceServiceImpl.java
@@ -305,7 +305,6 @@ public class InstanceServiceImpl implements InstanceService {
                 copyResourceToDir(karafBase, "etc/org.apache.karaf.log.cfg", printOutput);
                 copyResourceToDir(karafBase, "etc/org.ops4j.pax.logging.cfg", printOutput);
                 copyResourceToDir(karafBase, "etc/org.ops4j.pax.url.mvn.cfg", printOutput);
-                copyResourceToDir(karafBase, "etc/regions-config.xml", printOutput);
                 copyResourceToDir(karafBase, "etc/shell.init.script", printOutput);
                 copyResourceToDir(karafBase, "etc/users.properties", printOutput);
 


[4/4] git commit: [KARAF-2888] Make sure the FeaturesService configuration uses property substitution

Posted by gn...@apache.org.
[KARAF-2888] Make sure the FeaturesService configuration uses property substitution


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

Branch: refs/heads/master
Commit: 96a0920c6c3bb0cbf7fe778b92b4d33ae4fb29bc
Parents: bc05096
Author: Guillaume Nodet <gn...@gmail.com>
Authored: Wed Apr 23 15:29:57 2014 +0200
Committer: Guillaume Nodet <gn...@gmail.com>
Committed: Wed Apr 23 15:48:57 2014 +0200

----------------------------------------------------------------------
 features/core/pom.xml                                       | 1 +
 .../org/apache/karaf/features/internal/osgi/Activator.java  | 9 +++++++--
 2 files changed, 8 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/karaf/blob/96a0920c/features/core/pom.xml
----------------------------------------------------------------------
diff --git a/features/core/pom.xml b/features/core/pom.xml
index 52500d8..a6f9d1b 100644
--- a/features/core/pom.xml
+++ b/features/core/pom.xml
@@ -132,6 +132,7 @@
                             org.apache.karaf.features.internal.*,
                             org.apache.felix.resolver,
                             org.apache.felix.utils.version,
+                            org.apache.felix.utils.properties,
                             org.apache.felix.utils.manifest,
                             org.apache.karaf.util.collections,
                             org.apache.karaf.util.json,

http://git-wip-us.apache.org/repos/asf/karaf/blob/96a0920c/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 de6b44d..95665fa 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
@@ -27,9 +27,10 @@ import java.util.ArrayList;
 import java.util.Dictionary;
 import java.util.Hashtable;
 import java.util.List;
-import java.util.Properties;
+import java.util.Map;
 
 import org.apache.felix.resolver.ResolverImpl;
+import org.apache.felix.utils.properties.Properties;
 import org.apache.karaf.features.FeaturesListener;
 import org.apache.karaf.features.FeaturesService;
 import org.apache.karaf.features.internal.repository.AggregateRepository;
@@ -93,7 +94,11 @@ public class Activator extends BaseActivator {
                 logger.warn("Error reading configuration file " + configFile.toString(), e);
             }
         }
-        updated((Dictionary) configuration);
+        Dictionary<String, String> props = new Hashtable<String, String>();
+        for (Map.Entry<String, String> entry : configuration.entrySet()) {
+            props.put(entry.getKey(), entry.getValue());
+        }
+        updated(props);
     }
 
     protected void doStart() throws Exception {


[3/4] git commit: [KARAF-2930] Support for user defined repositories during resolution

Posted by gn...@apache.org.
[KARAF-2930] Support for user defined repositories during resolution


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

Branch: refs/heads/master
Commit: bc05096b5e214d4a51c6c3663efdb9d08aa7275b
Parents: 3574b44
Author: Guillaume Nodet <gn...@gmail.com>
Authored: Wed Apr 23 15:28:23 2014 +0200
Committer: Guillaume Nodet <gn...@gmail.com>
Committed: Wed Apr 23 15:48:54 2014 +0200

----------------------------------------------------------------------
 .../download/simple/SimpleDownloader.java       |  4 +-
 .../karaf/features/internal/osgi/Activator.java | 26 ++++++-
 .../region/SubsystemResolveContext.java         | 82 +++++++++++++++++++-
 .../internal/region/SubsystemResolver.java      |  8 +-
 .../internal/service/FeaturesServiceImpl.java   | 18 +++--
 .../karaf/features/FeaturesServiceTest.java     |  8 +-
 .../features/internal/region/SubsystemTest.java |  9 ++-
 .../service/FeaturesServiceImplTest.java        |  8 +-
 8 files changed, 138 insertions(+), 25 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/karaf/blob/bc05096b/features/core/src/main/java/org/apache/karaf/features/internal/download/simple/SimpleDownloader.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/download/simple/SimpleDownloader.java b/features/core/src/main/java/org/apache/karaf/features/internal/download/simple/SimpleDownloader.java
index 9c35199..1255fb6 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/download/simple/SimpleDownloader.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/download/simple/SimpleDownloader.java
@@ -59,7 +59,9 @@ public class SimpleDownloader implements DownloadManager, Downloader {
             providers.putIfAbsent(location, createProvider(location));
         }
         try {
-            downloadCallback.downloaded(providers.get(location));
+            if (downloadCallback != null) {
+                downloadCallback.downloaded(providers.get(location));
+            }
         } catch (Exception e) {
             exception.addException(e);
         }

http://git-wip-us.apache.org/repos/asf/karaf/blob/bc05096b/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 b2cdd6e..de6b44d 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
@@ -23,13 +23,18 @@ import java.io.FileReader;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.ArrayList;
 import java.util.Dictionary;
 import java.util.Hashtable;
+import java.util.List;
 import java.util.Properties;
 
 import org.apache.felix.resolver.ResolverImpl;
 import org.apache.karaf.features.FeaturesListener;
 import org.apache.karaf.features.FeaturesService;
+import org.apache.karaf.features.internal.repository.AggregateRepository;
+import org.apache.karaf.features.internal.repository.JsonRepository;
+import org.apache.karaf.features.internal.repository.XmlRepository;
 import org.apache.karaf.features.internal.resolver.Slf4jResolverLog;
 import org.apache.karaf.features.internal.service.EventAdminListener;
 import org.apache.karaf.features.internal.service.FeatureConfigInstaller;
@@ -39,7 +44,6 @@ 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.util.tracker.BaseActivator;
-import org.apache.karaf.util.tracker.SingleServiceTracker;
 import org.eclipse.equinox.internal.region.DigraphHelper;
 import org.eclipse.equinox.internal.region.StandardRegionDigraph;
 import org.eclipse.equinox.internal.region.management.StandardManageableRegionDigraph;
@@ -50,6 +54,7 @@ import org.osgi.framework.hooks.bundle.CollisionHook;
 import org.osgi.framework.hooks.resolver.ResolverHookFactory;
 import org.osgi.service.cm.ConfigurationAdmin;
 import org.osgi.service.cm.ManagedService;
+import org.osgi.service.repository.Repository;
 import org.osgi.service.resolver.Resolver;
 import org.osgi.service.url.URLStreamHandlerService;
 import org.osgi.util.tracker.ServiceTracker;
@@ -120,7 +125,21 @@ public class Activator extends BaseActivator {
         props.put(Constants.SERVICE_PID, FEATURES_REPOS_PID);
         register(ManagedService.class, featureFinder, props);
 
-       FeatureConfigInstaller configInstaller = new FeatureConfigInstaller(configurationAdmin);
+        List<Repository> repositories = new ArrayList<Repository>();
+        String[] resourceRepositories = getString("resourceRepositories", "").split(",");
+        for (String url : resourceRepositories) {
+            url = url.trim();
+            if (url.startsWith("json:")) {
+                repositories.add(new JsonRepository(url.substring("json:".length())));
+            } else if (url.startsWith("xml:")) {
+                repositories.add(new XmlRepository(url.substring("xml:".length())));
+            } else {
+                logger.warn("Unrecognized resource repository: " + url);
+            }
+        }
+        Repository globalRepository = repositories.isEmpty() ? null : new AggregateRepository(repositories);
+
+        FeatureConfigInstaller configInstaller = new FeatureConfigInstaller(configurationAdmin);
         String overrides = getString("overrides", new File(System.getProperty("karaf.etc"), "overrides.properties").toURI().toString());
         String featureResolutionRange = getString("featureResolutionRange", FeaturesServiceImpl.DEFAULT_FEATURE_RESOLUTION_RANGE);
         String bundleUpdateRange = getString("bundleUpdateRange", FeaturesServiceImpl.DEFAULT_BUNDLE_UPDATE_RANGE);
@@ -159,7 +178,8 @@ public class Activator extends BaseActivator {
                                 overrides,
                                 featureResolutionRange,
                                 bundleUpdateRange,
-                                updateSnapshots);
+                                updateSnapshots,
+                                globalRepository);
         register(FeaturesService.class, featuresService);
 
         featuresListenerTracker = new ServiceTracker<FeaturesListener, FeaturesListener>(

http://git-wip-us.apache.org/repos/asf/karaf/blob/bc05096b/features/core/src/main/java/org/apache/karaf/features/internal/region/SubsystemResolveContext.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/region/SubsystemResolveContext.java b/features/core/src/main/java/org/apache/karaf/features/internal/region/SubsystemResolveContext.java
index 949e37a..ef9c420 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/region/SubsystemResolveContext.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/region/SubsystemResolveContext.java
@@ -16,6 +16,7 @@
  */
 package org.apache.karaf.features.internal.region;
 
+import java.net.MalformedURLException;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -25,7 +26,11 @@ import java.util.List;
 import java.util.Map;
 
 import org.apache.felix.resolver.Util;
+import org.apache.karaf.features.internal.download.Downloader;
 import org.apache.karaf.features.internal.repository.BaseRepository;
+import org.apache.karaf.features.internal.resolver.CapabilityImpl;
+import org.apache.karaf.features.internal.resolver.RequirementImpl;
+import org.apache.karaf.features.internal.resolver.ResourceImpl;
 import org.eclipse.equinox.region.Region;
 import org.eclipse.equinox.region.RegionDigraph;
 import org.eclipse.equinox.region.RegionFilter;
@@ -38,6 +43,8 @@ import org.osgi.service.repository.Repository;
 import org.osgi.service.resolver.HostedCapability;
 import org.osgi.service.resolver.ResolveContext;
 
+import static org.apache.karaf.features.internal.resolver.ResourceUtils.addIdentityRequirement;
+import static org.apache.karaf.features.internal.resolver.ResourceUtils.getUri;
 import static org.eclipse.equinox.region.RegionFilter.VISIBLE_BUNDLE_NAMESPACE;
 import static org.osgi.framework.Constants.BUNDLE_SYMBOLICNAME_ATTRIBUTE;
 import static org.osgi.framework.Constants.BUNDLE_VERSION_ATTRIBUTE;
@@ -54,11 +61,14 @@ public class SubsystemResolveContext extends ResolveContext {
 
     private final Map<Resource, Subsystem> resToSub = new HashMap<Resource, Subsystem>();
     private final Repository repository;
+    private final Repository globalRepository;
+    private final Downloader downloader;
 
-
-    public SubsystemResolveContext(Subsystem root, RegionDigraph digraph) throws BundleException {
+    public SubsystemResolveContext(Subsystem root, RegionDigraph digraph, Repository globalRepository, Downloader downloader) throws BundleException {
         this.root = root;
         this.digraph = digraph;
+        this.globalRepository = globalRepository != null ? new SubsystemRepository(globalRepository) : null;
+        this.downloader = downloader;
 
         prepare(root);
         repository = new BaseRepository(resToSub.keySet());
@@ -87,9 +97,16 @@ public class SubsystemResolveContext extends ResolveContext {
             Map<Requirement, Collection<Capability>> resMap =
                     repository.findProviders(Collections.singleton(requirement));
             Collection<Capability> res = resMap != null ? resMap.get(requirement) : null;
-            if (res != null) {
+            if (res != null && !res.isEmpty()) {
                 caps.addAll(res);
+            } else if (globalRepository != null) {
+                resMap = globalRepository.findProviders(Collections.singleton(requirement));
+                res = resMap != null ? resMap.get(requirement) : null;
+                if (res != null && !res.isEmpty()) {
+                    caps.addAll(res);
+                }
             }
+
             // Use the digraph to prune non visible capabilities
             Visitor visitor = new Visitor(caps);
             requirerRegion.visitSubgraph(visitor);
@@ -191,4 +208,63 @@ public class SubsystemResolveContext extends ResolveContext {
 
     }
 
+    class SubsystemRepository implements Repository {
+
+        private final Repository repository;
+        private final Map<Subsystem, Map<Capability, Capability>> mapping = new HashMap<Subsystem, Map<Capability, Capability>>();
+
+        public SubsystemRepository(Repository repository) {
+            this.repository = repository;
+        }
+
+        @Override
+        public Map<Requirement, Collection<Capability>> findProviders(Collection<? extends Requirement> requirements) {
+            Map<Requirement, Collection<Capability>> base = repository.findProviders(requirements);
+            Map<Requirement, Collection<Capability>> result = new HashMap<Requirement, Collection<Capability>>();
+            for (Map.Entry<Requirement, Collection<Capability>> entry : base.entrySet()) {
+                List<Capability> caps = new ArrayList<Capability>();
+                Subsystem ss = getSubsystem(entry.getKey().getResource());
+                while (!ss.isAcceptDependencies()) {
+                    ss = ss.getParent();
+                }
+                Map<Capability, Capability> map = mapping.get(ss);
+                if (map == null) {
+                    map = new HashMap<Capability, Capability>();
+                    mapping.put(ss, map);
+                }
+                for (Capability cap : entry.getValue()) {
+                    Capability wrapped = map.get(cap);
+                    if (wrapped == null) {
+                        wrap(map, ss, cap.getResource());
+                        wrapped = map.get(cap);
+                    }
+                    caps.add(wrapped);
+                }
+                result.put(entry.getKey(), caps);
+            }
+            return result;
+        }
+
+        private void wrap(Map<Capability, Capability> map, Subsystem subsystem, Resource resource) {
+            ResourceImpl wrapped = new ResourceImpl();
+            for (Capability cap : resource.getCapabilities(null)) {
+                CapabilityImpl wCap = new CapabilityImpl(wrapped, cap.getNamespace(), cap.getDirectives(), cap.getAttributes());
+                map.put(cap, wCap);
+                wrapped.addCapability(wCap);
+            }
+            for (Requirement req : resource.getRequirements(null)) {
+                RequirementImpl wReq = new RequirementImpl(wrapped, req.getNamespace(), req.getDirectives(), req.getAttributes());
+                wrapped.addRequirement(wReq);
+            }
+            addIdentityRequirement(wrapped, subsystem, false);
+            resToSub.put(wrapped, subsystem);
+            // TODO: use RepositoryContent ?
+            try {
+                downloader.download(getUri(wrapped), null);
+            } catch (MalformedURLException e) {
+                throw new IllegalStateException("Unable to download resource: " + getUri(wrapped));
+            }
+        }
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/karaf/blob/bc05096b/features/core/src/main/java/org/apache/karaf/features/internal/region/SubsystemResolver.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/region/SubsystemResolver.java b/features/core/src/main/java/org/apache/karaf/features/internal/region/SubsystemResolver.java
index 190a67d..8f42dda 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/region/SubsystemResolver.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/region/SubsystemResolver.java
@@ -30,6 +30,7 @@ import org.apache.felix.resolver.Util;
 import org.apache.karaf.features.Feature;
 import org.apache.karaf.features.Repository;
 import org.apache.karaf.features.internal.download.DownloadManager;
+import org.apache.karaf.features.internal.download.Downloader;
 import org.apache.karaf.features.internal.download.StreamProvider;
 import org.apache.karaf.features.internal.download.simple.SimpleDownloader;
 import org.apache.karaf.features.internal.resolver.CapabilitySet;
@@ -80,7 +81,8 @@ public class SubsystemResolver {
             Map<String, Set<String>> features,
             Map<String, Set<BundleRevision>> system,
             Set<String> overrides,
-            String featureResolutionRange
+            String featureResolutionRange,
+            org.osgi.service.repository.Repository globalRepository
     ) throws Exception {
         // Build subsystems on the fly
         for (Map.Entry<String, Set<String>> entry : features.entrySet()) {
@@ -141,7 +143,9 @@ public class SubsystemResolver {
         populateDigraph(digraph, root);
 
         Resolver resolver = new ResolverImpl(new Slf4jResolverLog(LOGGER));
-        wiring = resolver.resolve(new SubsystemResolveContext(root, digraph));
+        Downloader downloader = manager.createDownloader();
+        wiring = resolver.resolve(new SubsystemResolveContext(root, digraph, globalRepository, downloader));
+        downloader.await();
 
         // Fragments are always wired to their host only, so create fake wiring to
         // the subsystem the host is wired to

http://git-wip-us.apache.org/repos/asf/karaf/blob/bc05096b/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java
index bb3c00f..7e42df9 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java
@@ -152,6 +152,11 @@ public class FeaturesServiceImpl implements FeaturesService {
      */
     private final String updateSnaphots;
 
+    /**
+     * Optional global repository
+     */
+    private final org.osgi.service.repository.Repository globalRepository;
+
     private final List<FeaturesListener> listeners = new CopyOnWriteArrayIdentityList<FeaturesListener>();
 
     // Synchronized on lock
@@ -171,7 +176,8 @@ public class FeaturesServiceImpl implements FeaturesService {
                                String overrides,
                                String featureResolutionRange,
                                String bundleUpdateRange,
-                               String updateSnaphots) {
+                               String updateSnaphots,
+                               org.osgi.service.repository.Repository globalRepository) {
         this.bundle = bundle;
         this.systemBundleContext = systemBundleContext;
         this.storage = storage;
@@ -183,6 +189,7 @@ public class FeaturesServiceImpl implements FeaturesService {
         this.featureResolutionRange = featureResolutionRange;
         this.bundleUpdateRange = bundleUpdateRange;
         this.updateSnaphots = updateSnaphots;
+        this.globalRepository = globalRepository;
         loadState();
     }
 
@@ -700,7 +707,7 @@ public class FeaturesServiceImpl implements FeaturesService {
         Set<String> fl = required.get(region);
         if (fl == null) {
             fl = new HashSet<String>();
-            required.put(region,fl);
+            required.put(region, fl);
         }
         List<String> featuresToRemove = new ArrayList<String>();
         for (String feature : new HashSet<String>(features)) {
@@ -749,7 +756,7 @@ public class FeaturesServiceImpl implements FeaturesService {
         print(sb.toString(), options.contains(Option.Verbose));
         fl.removeAll(featuresToRemove);
         if (fl.isEmpty()) {
-            required.remove(fl);
+            required.remove(region);
         }
         doInstallFeaturesInThread(required, state, options);
     }
@@ -872,7 +879,8 @@ public class FeaturesServiceImpl implements FeaturesService {
                 features,
                 unmanagedBundles,
                 Overrides.loadOverrides(this.overrides),
-                featureResolutionRange);
+                featureResolutionRange,
+                globalRepository);
         Collection<Resource> allResources = resolution.keySet();
         Map<String, StreamProvider> providers = resolver.getProviders();
 
@@ -957,7 +965,7 @@ public class FeaturesServiceImpl implements FeaturesService {
                     List<Wire> newWires = resolution.get(wiring.getRevision());
                     if (newWires != null) {
                         for (Wire wire : newWires) {
-                            Bundle b = null;
+                            Bundle b;
                             if (wire.getProvider() instanceof BundleRevision) {
                                 b = ((BundleRevision) wire.getProvider()).getBundle();
                             } else {

http://git-wip-us.apache.org/repos/asf/karaf/blob/bc05096b/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 d94d9b7..2609c8d 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
@@ -346,7 +346,7 @@ public class FeaturesServiceTest extends TestBase {
                 + "  <feature name='f2' version='0.2'><bundle>bundle2</bundle></feature>"
                 + "</features>");
 
-        FeaturesServiceImpl svc = new FeaturesServiceImpl(null, null, new Storage(), null, null, null, null, null, null, null, null);
+        FeaturesServiceImpl svc = new FeaturesServiceImpl(null, null, new Storage(), null, null, null, null, null, null, null, null, null);
         svc.addRepository(uri);
 
         assertEquals(feature("f2", "0.2"), svc.getFeature("f2", "[0.1,0.3)"));
@@ -366,7 +366,7 @@ public class FeaturesServiceTest extends TestBase {
         expect(bundleContext.getBundles()).andReturn(new Bundle[0]);
         replay(bundleContext);
 
-        FeaturesServiceImpl svc = new FeaturesServiceImpl(null, bundleContext, new Storage(), null, null, null, null, null, null, null, null);
+        FeaturesServiceImpl svc = new FeaturesServiceImpl(null, bundleContext, new Storage(), null, null, null, null, null, null, null, null, null);
         svc.addRepository(uri);
         try {
             List<String> features = new ArrayList<String>();
@@ -391,7 +391,7 @@ 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>");
 
-        FeaturesServiceImpl svc = new FeaturesServiceImpl(null, null, new Storage(), null, null, null, null, null, null, null, null);
+        FeaturesServiceImpl svc = new FeaturesServiceImpl(null, null, new Storage(), null, null, null, null, null, null, null, null, null);
         try {
             svc.addRepository(uri);
             fail("exception expected");
@@ -409,7 +409,7 @@ public class FeaturesServiceTest extends TestBase {
                 + "  <feature name='f1'><bundle>file:bundle1</bundle><bundle>file:bundle2</bundle></feature>"
                 + "</features>");
 
-        FeaturesServiceImpl svc = new FeaturesServiceImpl(null, null, new Storage(), null, null, null, null, null, null, null, null);
+        FeaturesServiceImpl svc = new FeaturesServiceImpl(null, null, new Storage(), null, null, null, null, null, null, null, null, null);
         svc.addRepository(uri);
         Feature feature = svc.getFeature("f1");
         Assert.assertNotNull("No feature named fi found", feature);        

http://git-wip-us.apache.org/repos/asf/karaf/blob/bc05096b/features/core/src/test/java/org/apache/karaf/features/internal/region/SubsystemTest.java
----------------------------------------------------------------------
diff --git a/features/core/src/test/java/org/apache/karaf/features/internal/region/SubsystemTest.java b/features/core/src/test/java/org/apache/karaf/features/internal/region/SubsystemTest.java
index d64bb65..a027290 100644
--- a/features/core/src/test/java/org/apache/karaf/features/internal/region/SubsystemTest.java
+++ b/features/core/src/test/java/org/apache/karaf/features/internal/region/SubsystemTest.java
@@ -66,7 +66,8 @@ public class SubsystemTest {
                          features,
                          Collections.<String, Set<BundleRevision>>emptyMap(),
                          Collections.<String>emptySet(),
-                         FeaturesServiceImpl.DEFAULT_FEATURE_RESOLUTION_RANGE);
+                         FeaturesServiceImpl.DEFAULT_FEATURE_RESOLUTION_RANGE,
+                         null);
 
         verify(resolver, expected);
     }
@@ -96,7 +97,8 @@ public class SubsystemTest {
                          features,
                          Collections.<String, Set<BundleRevision>>emptyMap(),
                          Collections.<String>emptySet(),
-                         FeaturesServiceImpl.DEFAULT_FEATURE_RESOLUTION_RANGE);
+                         FeaturesServiceImpl.DEFAULT_FEATURE_RESOLUTION_RANGE,
+                         null);
 
         verify(resolver, expected);
     }
@@ -116,7 +118,8 @@ public class SubsystemTest {
                 features,
                 Collections.<String, Set<BundleRevision>>emptyMap(),
                 Collections.singleton("b"),
-                FeaturesServiceImpl.DEFAULT_FEATURE_RESOLUTION_RANGE);
+                FeaturesServiceImpl.DEFAULT_FEATURE_RESOLUTION_RANGE,
+                null);
 
         verify(resolver, expected);
     }

http://git-wip-us.apache.org/repos/asf/karaf/blob/bc05096b/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
index 3627c0f..7f1f358 100644
--- 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
@@ -49,7 +49,7 @@ public class FeaturesServiceImplTest extends TestBase {
     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, null, "", null, null, null) {
+        final FeaturesServiceImpl impl = new FeaturesServiceImpl(null, null, new Storage(), null, null, null, null, "", null, null, null, null) {
             protected Map<String,Map<String,Feature>> getFeatures() throws Exception {
                 return features;
             }
@@ -60,7 +60,7 @@ public class FeaturesServiceImplTest extends TestBase {
     
     @Test
     public void testGetFeatureStripVersion() throws Exception {
-        final FeaturesServiceImpl impl = new FeaturesServiceImpl(null, null, new Storage(), null, null, null, null, "", null, null, null) {
+        final FeaturesServiceImpl impl = new FeaturesServiceImpl(null, null, new Storage(), null, null, null, null, "", null, null, null, null) {
             protected Map<String,Map<String,Feature>> getFeatures() throws Exception {
                 return features(feature("transaction", "1.0.0"));
             }
@@ -72,7 +72,7 @@ public class FeaturesServiceImplTest extends TestBase {
     
     @Test
     public void testGetFeatureNotAvailable() throws Exception {
-        final FeaturesServiceImpl impl = new FeaturesServiceImpl(null, null, new Storage(), null, null, null, null, "", null, null, null) {
+        final FeaturesServiceImpl impl = new FeaturesServiceImpl(null, null, new Storage(), null, null, null, null, "", null, null, null, null) {
             protected Map<String,Map<String,Feature>> getFeatures() throws Exception {
                 return features(feature("transaction", "1.0.0"));
             }
@@ -86,7 +86,7 @@ public class FeaturesServiceImplTest extends TestBase {
                 feature("transaction", "1.0.0"),
                 feature("transaction", "2.0.0")
         );
-        final FeaturesServiceImpl impl = new FeaturesServiceImpl(null, null, new Storage(), null, null, null, null, "", null, null, null) {
+        final FeaturesServiceImpl impl = new FeaturesServiceImpl(null, null, new Storage(), null, null, null, null, "", null, null, null, null) {
             protected Map<String,Map<String,Feature>> getFeatures() throws Exception {
                 return features;
             }


[2/4] git commit: [KARAF-2930] Refactory repository support and fully support for the xml format defined by the OSGi Repository specification

Posted by gn...@apache.org.
[KARAF-2930] Refactory repository support and fully support for the xml format defined by the OSGi Repository specification


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

Branch: refs/heads/master
Commit: 3574b440f45093802507e5fdd427289a5b4e9984
Parents: 7456445
Author: Guillaume Nodet <gn...@gmail.com>
Authored: Wed Apr 23 11:30:45 2014 +0200
Committer: Guillaume Nodet <gn...@gmail.com>
Committed: Wed Apr 23 15:48:42 2014 +0200

----------------------------------------------------------------------
 .../region/SubsystemResolveContext.java         |   4 +-
 .../internal/repository/BaseRepository.java     |   7 +
 .../internal/repository/CacheRepository.java    |  59 ----
 .../repository/HttpMetadataProvider.java        |  88 ------
 .../internal/repository/JsonRepository.java     | 117 ++++++++
 .../internal/repository/MetadataProvider.java   |  29 --
 .../internal/repository/MetadataRepository.java |  43 ---
 .../internal/repository/StaticRepository.java   |  33 ---
 .../internal/repository/StaxParser.java         | 289 +++++++++++++++++++
 .../features/internal/repository/UrlLoader.java |  88 ++++++
 .../internal/repository/XmlRepository.java      | 163 +++++++++++
 .../internal/resolver/ResourceImpl.java         |   9 +
 .../internal/repository/RepositoryTest.java     |  58 ++++
 .../features/internal/repository/repo.json      |   9 +
 .../karaf/features/internal/repository/repo.xml |  53 ++++
 pom.xml                                         |   2 +
 16 files changed, 797 insertions(+), 254 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/karaf/blob/3574b440/features/core/src/main/java/org/apache/karaf/features/internal/region/SubsystemResolveContext.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/region/SubsystemResolveContext.java b/features/core/src/main/java/org/apache/karaf/features/internal/region/SubsystemResolveContext.java
index d5a3113..949e37a 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/region/SubsystemResolveContext.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/region/SubsystemResolveContext.java
@@ -25,7 +25,7 @@ import java.util.List;
 import java.util.Map;
 
 import org.apache.felix.resolver.Util;
-import org.apache.karaf.features.internal.repository.StaticRepository;
+import org.apache.karaf.features.internal.repository.BaseRepository;
 import org.eclipse.equinox.region.Region;
 import org.eclipse.equinox.region.RegionDigraph;
 import org.eclipse.equinox.region.RegionFilter;
@@ -61,7 +61,7 @@ public class SubsystemResolveContext extends ResolveContext {
         this.digraph = digraph;
 
         prepare(root);
-        repository = new StaticRepository(resToSub.keySet());
+        repository = new BaseRepository(resToSub.keySet());
     }
 
     void prepare(Subsystem subsystem) {

http://git-wip-us.apache.org/repos/asf/karaf/blob/3574b440/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
index c4c0d16..e4befcb 100644
--- 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
@@ -44,6 +44,13 @@ public class BaseRepository implements Repository {
         this.capSets = new HashMap<String, CapabilitySet>();
     }
 
+    public BaseRepository(Collection<Resource> resources) {
+        this();
+        for (Resource resource : resources) {
+            addResource(resource);
+        }
+    }
+
     protected void addResource(Resource resource) {
         for (Capability cap : resource.getCapabilities(null)) {
             String ns = cap.getNamespace();

http://git-wip-us.apache.org/repos/asf/karaf/blob/3574b440/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
deleted file mode 100644
index 7916821..0000000
--- a/features/core/src/main/java/org/apache/karaf/features/internal/repository/CacheRepository.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.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/3574b440/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
deleted file mode 100644
index 1aecef1..0000000
--- a/features/core/src/main/java/org/apache/karaf/features/internal/repository/HttpMetadataProvider.java
+++ /dev/null
@@ -1,88 +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.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/3574b440/features/core/src/main/java/org/apache/karaf/features/internal/repository/JsonRepository.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/repository/JsonRepository.java b/features/core/src/main/java/org/apache/karaf/features/internal/repository/JsonRepository.java
new file mode 100644
index 0000000..f1f3c0b
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/repository/JsonRepository.java
@@ -0,0 +1,117 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.features.internal.repository;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import org.apache.karaf.features.internal.resolver.ResourceBuilder;
+import org.apache.karaf.features.internal.util.JsonReader;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Requirement;
+import org.osgi.resource.Resource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Repository using a JSON representation of resource metadata.
+ * The json should be a map: the key is the resource uri and the
+ * value is a map of resource headers.
+ * The content of the URL can be gzipped.
+ */
+public class JsonRepository extends BaseRepository {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(JsonRepository.class);
+
+    private final UrlLoader loader;
+    private final ReadWriteLock lock = new ReentrantReadWriteLock();
+
+    public JsonRepository(String url) {
+        loader = new UrlLoader(url) {
+            @Override
+            protected boolean doRead(InputStream is) throws IOException {
+                return JsonRepository.this.doRead(is);
+            }
+        };
+    }
+
+    @Override
+    public List<Resource> getResources() {
+        checkAndLoadCache();
+        lock.readLock().lock();
+        try {
+            return super.getResources();
+        } finally {
+            lock.readLock().unlock();
+        }
+    }
+
+    @Override
+    public Map<Requirement, Collection<Capability>> findProviders(Collection<? extends Requirement> requirements) {
+        checkAndLoadCache();
+        lock.readLock().lock();
+        try {
+            return super.findProviders(requirements);
+        } finally {
+            lock.readLock().unlock();
+        }
+    }
+
+    private void checkAndLoadCache() {
+        loader.checkAndLoadCache();
+    }
+
+    protected boolean doRead(InputStream is) throws IOException {
+        Map<String, Map<String, String>> metadatas = verify(JsonReader.read(is));
+        lock.writeLock().lock();
+        try {
+            resources.clear();
+            capSets.clear();
+            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);
+                }
+            }
+            return true;
+        } finally {
+            lock.writeLock().unlock();
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    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/3574b440/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
deleted file mode 100644
index 9ac54a1..0000000
--- a/features/core/src/main/java/org/apache/karaf/features/internal/repository/MetadataProvider.java
+++ /dev/null
@@ -1,29 +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.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/3574b440/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
deleted file mode 100644
index 2d4fbba..0000000
--- a/features/core/src/main/java/org/apache/karaf/features/internal/repository/MetadataRepository.java
+++ /dev/null
@@ -1,43 +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.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/3574b440/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
deleted file mode 100644
index f289c8d..0000000
--- a/features/core/src/main/java/org/apache/karaf/features/internal/repository/StaticRepository.java
+++ /dev/null
@@ -1,33 +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.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/3574b440/features/core/src/main/java/org/apache/karaf/features/internal/repository/StaxParser.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/repository/StaxParser.java b/features/core/src/main/java/org/apache/karaf/features/internal/repository/StaxParser.java
new file mode 100644
index 0000000..9e13e8e
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/repository/StaxParser.java
@@ -0,0 +1,289 @@
+/*
+ * 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.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.stream.Location;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamReader;
+
+import org.apache.karaf.features.internal.resolver.CapabilityImpl;
+import org.apache.karaf.features.internal.resolver.RequirementImpl;
+import org.apache.karaf.features.internal.resolver.ResourceImpl;
+import org.osgi.framework.Version;
+import org.osgi.resource.Resource;
+
+import static javax.xml.stream.XMLStreamConstants.CHARACTERS;
+import static javax.xml.stream.XMLStreamConstants.END_ELEMENT;
+import static javax.xml.stream.XMLStreamConstants.START_ELEMENT;
+
+/**
+ * Repository XML xml based on StaX
+ */
+public class StaxParser {
+
+    public static final String REPOSITORY = "repository";
+    public static final String REPO_NAME = "name";
+    public static final String INCREMENT = "increment";
+    public static final String REFERRAL = "referral";
+    public static final String DEPTH = "depth";
+    public static final String URL = "url";
+    public static final String RESOURCE = "resource";
+    public static final String CAPABILITY = "capability";
+    public static final String REQUIREMENT = "requirement";
+    public static final String NAMESPACE = "namespace";
+    public static final String ATTRIBUTE = "attribute";
+    public static final String DIRECTIVE = "directive";
+    public static final String NAME = "name";
+    public static final String VALUE = "value";
+    public static final String TYPE = "type";
+
+    public static class Referral {
+        String url;
+        int depth = Integer.MAX_VALUE;
+    }
+
+    public static class XmlRepository {
+        String name;
+        long increment;
+        List<Referral> referrals = new ArrayList<Referral>();
+        List<Resource> resources = new ArrayList<Resource>();
+    }
+
+    public static XmlRepository parse(InputStream is) throws XMLStreamException {
+        return parse(is, null);
+    }
+
+    public static XmlRepository parse(InputStream is, XmlRepository previous) throws XMLStreamException {
+        XMLStreamReader reader = getFactory().createXMLStreamReader(is);
+        int event = reader.nextTag();
+        if (event != START_ELEMENT || !REPOSITORY.equals(reader.getLocalName())) {
+            throw new IllegalStateException("Expected element 'repository' at the root of the document");
+        }
+        XmlRepository repo = new XmlRepository();
+        for (int i = 0, nb = reader.getAttributeCount(); i < nb; i++) {
+            String attrName = reader.getAttributeLocalName(i);
+            String attrValue = reader.getAttributeValue(i);
+            if (REPO_NAME.equals(attrName)) {
+                repo.name = attrName;
+            } else if (INCREMENT.equals(attrName)) {
+                repo.increment = Integer.parseInt(attrValue);
+            } else {
+                throw new IllegalStateException("Unexpected attribute '" + attrName + "'");
+            }
+        }
+        if (previous != null && repo.increment == previous.increment) {
+            return previous;
+        }
+        while ((event = reader.nextTag()) == START_ELEMENT) {
+            String element = reader.getLocalName();
+            if (REFERRAL.equals(element)) {
+                Referral referral = new Referral();
+                for (int i = 0, nb = reader.getAttributeCount(); i < nb; i++) {
+                    String attrName = reader.getAttributeLocalName(i);
+                    String attrValue = reader.getAttributeValue(i);
+                    if (DEPTH.equals(attrName)) {
+                        referral.depth = Integer.parseInt(attrValue);
+                    } else if (URL.equals(attrName)) {
+                        referral.url = attrValue;
+                    } else {
+                        throw new IllegalStateException("Unexpected attribute '" + attrName + "'");
+                    }
+                }
+                if (referral.url == null) {
+                    throw new IllegalStateException("Expected attribute '" + URL + "'");
+                }
+                repo.referrals.add(referral);
+                sanityCheckEndElement(reader, reader.nextTag(), REFERRAL);
+            } else if (RESOURCE.equals(element)) {
+                repo.resources.add(parseResource(reader));
+            } else {
+                throw new IllegalStateException("Unsupported element '" + element + "'. Expected 'referral' or 'resource'");
+            }
+        }
+        // Sanity check
+        sanityCheckEndElement(reader, event, REPOSITORY);
+        return repo;
+    }
+
+    private static void sanityCheckEndElement(XMLStreamReader reader, int event, String element) {
+        if (event != END_ELEMENT || !element.equals(reader.getLocalName())) {
+            throw new IllegalStateException("Unexpected state while finishing element " + element);
+        }
+    }
+
+    private static ResourceImpl parseResource(XMLStreamReader reader) {
+        try {
+            if (reader.getAttributeCount() > 0) {
+                throw new IllegalStateException("Unexpected attribute '" + reader.getAttributeLocalName(0) + "'");
+            }
+            ResourceImpl resource = new ResourceImpl();
+            int event;
+            while ((event = reader.nextTag()) == START_ELEMENT) {
+                String element = reader.getLocalName();
+                if (CAPABILITY.equals(element)) {
+                    resource.addCapability(parseCapability(reader, resource));
+                } else if (REQUIREMENT.equals(element)) {
+                    resource.addRequirement(parseRequirement(reader, resource));
+                } else {
+                    while ((event = reader.next()) != END_ELEMENT) {
+                        switch (event) {
+                            case START_ELEMENT:
+                                throw new IllegalStateException("Unexpected element '" + reader.getLocalName() + "' inside 'resource' element");
+                            case CHARACTERS:
+                                throw new IllegalStateException("Unexpected text inside 'resource' element");
+                        }
+                    }
+                }
+            }
+            // Sanity check
+            sanityCheckEndElement(reader, event, RESOURCE);
+            return resource;
+        } catch (Exception e) {
+            Location loc = reader.getLocation();
+            if (loc != null) {
+                throw new IllegalStateException("Error while parsing resource at line " + loc.getLineNumber() + " and column " + loc.getColumnNumber(), e);
+            } else {
+                throw new IllegalStateException("Error while parsing resource", e);
+            }
+        }
+    }
+
+    private static CapabilityImpl parseCapability(XMLStreamReader reader, ResourceImpl resource) throws XMLStreamException {
+        String[] namespace = new String[1];
+        Map<String, String> directives = new HashMap<String, String>();
+        Map<String, Object> attributes = new HashMap<String, Object>();
+        parseClause(reader, namespace, directives, attributes);
+        sanityCheckEndElement(reader, reader.getEventType(), CAPABILITY);
+        return new CapabilityImpl(resource, namespace[0], directives, attributes);
+    }
+
+    private static RequirementImpl parseRequirement(XMLStreamReader reader, ResourceImpl resource) throws XMLStreamException {
+        String[] namespace = new String[1];
+        Map<String, String> directives = new HashMap<String, String>();
+        Map<String, Object> attributes = new HashMap<String, Object>();
+        parseClause(reader, namespace, directives, attributes);
+        sanityCheckEndElement(reader, reader.getEventType(), REQUIREMENT);
+        return new RequirementImpl(resource, namespace[0], directives, attributes);
+    }
+
+    private static void parseClause(XMLStreamReader reader, String[] namespace, Map<String, String> directives, Map<String, Object> attributes) throws XMLStreamException {
+        namespace[0] = null;
+        for (int i = 0, nb = reader.getAttributeCount(); i < nb; i++) {
+            String name = reader.getAttributeLocalName(i);
+            String value = reader.getAttributeValue(i);
+            if (NAMESPACE.equals(name)) {
+                namespace[0] = value;
+            } else {
+                throw new IllegalStateException("Unexpected attribute: '" + name + "'. Expected 'namespace'");
+            }
+        }
+        if (namespace[0] == null) {
+            throw new IllegalStateException("Expected attribute 'namespace'");
+        }
+        while (reader.nextTag() == START_ELEMENT) {
+            String element = reader.getLocalName();
+            if (DIRECTIVE.equals(element)) {
+                String name = null;
+                String value = null;
+                for (int i = 0, nb = reader.getAttributeCount(); i < nb; i++) {
+                    String attName = reader.getAttributeLocalName(i);
+                    String attValue = reader.getAttributeValue(i);
+                    if (NAME.equals(attName)) {
+                        name = attValue;
+                    } else if (VALUE.equals(attName)) {
+                        value = attValue;
+                    } else {
+                        throw new IllegalStateException("Unexpected attribute: '" + attName + "'. Expected 'name', or 'value'.");
+                    }
+                }
+                if (name == null || value == null) {
+                    throw new IllegalStateException("Expected attribute 'name' and 'value'");
+                }
+                directives.put(name, value);
+                sanityCheckEndElement(reader, reader.nextTag(), DIRECTIVE);
+            } else if (ATTRIBUTE.equals(element)) {
+                String name = null;
+                String value = null;
+                String type = "String";
+                for (int i = 0, nb = reader.getAttributeCount(); i < nb; i++) {
+                    String attName = reader.getAttributeLocalName(i);
+                    String attValue = reader.getAttributeValue(i);
+                    if (NAME.equals(attName)) {
+                        name = attValue;
+                    } else if (VALUE.equals(attName)) {
+                        value = attValue;
+                    } else if (TYPE.equals(attName)) {
+                        type = attValue;
+                    } else {
+                        throw new IllegalStateException("Unexpected attribute: '" + attName + "'. Expected 'name', 'value' or 'type'.");
+                    }
+                }
+                if (name == null || value == null) {
+                    throw new IllegalStateException("Expected attribute 'name' and 'value'");
+                }
+                attributes.put(name, parseAttribute(value, type));
+                sanityCheckEndElement(reader, reader.nextTag(), ATTRIBUTE);
+            } else {
+                throw new IllegalStateException("Unexpected element: '" + element + ". Expected 'directive' or 'attribute'");
+            }
+        }
+    }
+
+    private static Object parseAttribute(String value, String type) {
+        if ("String".equals(type)) {
+            return value;
+        } else if ("Version".equals(type)) {
+            return Version.parseVersion(value);
+        } else if ("Long".equals(type)) {
+            return Long.parseLong(value.trim());
+        } else if ("Double".equals(type)) {
+            return Double.parseDouble(value.trim());
+        } else if (type.startsWith("List<") && type.endsWith(">")) {
+            type = type.substring("List<".length(), type.length() - 1);
+            List<Object> list = new ArrayList<Object>();
+            for (String s : value.split(",")) {
+                list.add(parseAttribute(s.trim(), type));
+            }
+            return list;
+        } else {
+            throw new IllegalStateException("Unexpected type: '" + type + "'");
+        }
+    }
+
+    static XMLInputFactory factory;
+
+    private static synchronized XMLInputFactory getFactory() {
+        if (StaxParser.factory == null) {
+            XMLInputFactory factory = XMLInputFactory.newInstance();
+            factory.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, true);
+            StaxParser.factory = factory;
+        }
+        return StaxParser.factory;
+    }
+
+    private StaxParser() {
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/karaf/blob/3574b440/features/core/src/main/java/org/apache/karaf/features/internal/repository/UrlLoader.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/repository/UrlLoader.java b/features/core/src/main/java/org/apache/karaf/features/internal/repository/UrlLoader.java
new file mode 100644
index 0000000..8bed863
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/repository/UrlLoader.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.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URLConnection;
+import java.util.zip.GZIPInputStream;
+
+import static java.net.HttpURLConnection.HTTP_NOT_MODIFIED;
+import static java.net.HttpURLConnection.HTTP_OK;
+
+/**
+ */
+public abstract class UrlLoader {
+
+    public static final String HEADER_ACCEPT_ENCODING = "Accept-Encoding";
+    public static final String GZIP = "gzip";
+
+    private final String url;
+    private long lastModified;
+
+    public UrlLoader(String url) {
+        this.url = url;
+    }
+
+    protected boolean checkAndLoadCache() {
+        try {
+            URLConnection connection = new java.net.URL(url).openConnection();
+            if (connection instanceof HttpURLConnection) {
+                HttpURLConnection con = (HttpURLConnection) connection;
+                if (lastModified > 0) {
+                    con.setIfModifiedSince(lastModified);
+                }
+                con.setRequestProperty(HEADER_ACCEPT_ENCODING, GZIP);
+                int rc = con.getResponseCode();
+                if (rc == HTTP_NOT_MODIFIED) {
+                    return false;
+                }
+                if (rc != HTTP_OK) {
+                    throw new IOException("Unexpected http response: " + rc + " " + con.getResponseMessage());
+                }
+            }
+            long lm = connection.getLastModified();
+            if (lm <= lastModified) {
+                return false;
+            }
+            BufferedInputStream bis = new BufferedInputStream(connection.getInputStream());
+            try {
+                // Auto-detect gzipped streams
+                InputStream is = bis;
+                bis.mark(512);
+                int b0 = bis.read();
+                int b1 = bis.read();
+                bis.reset();
+                if (b0 == 0x1f && b1 == 0x8b) {
+                    is = new GZIPInputStream(bis);
+                }
+                boolean r = doRead(is);
+                lastModified = lm;
+                return r;
+            } finally {
+                bis.close();
+            }
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    protected abstract boolean doRead(InputStream is) throws IOException;
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/3574b440/features/core/src/main/java/org/apache/karaf/features/internal/repository/XmlRepository.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/repository/XmlRepository.java b/features/core/src/main/java/org/apache/karaf/features/internal/repository/XmlRepository.java
new file mode 100644
index 0000000..6661fb1
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/repository/XmlRepository.java
@@ -0,0 +1,163 @@
+/*
+ * 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.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import javax.xml.stream.XMLStreamException;
+
+import org.apache.karaf.features.internal.resolver.CapabilitySet;
+import org.apache.karaf.features.internal.resolver.SimpleFilter;
+import org.osgi.framework.Version;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Requirement;
+import org.osgi.resource.Resource;
+
+import static org.osgi.framework.namespace.IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE;
+import static org.osgi.framework.namespace.IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE;
+import static org.osgi.framework.namespace.IdentityNamespace.IDENTITY_NAMESPACE;
+
+/**
+ * Repository conforming to the OSGi Repository specification.
+ * The content of the URL can be gzipped.
+ */
+public class XmlRepository extends BaseRepository {
+
+    private final String url;
+    private final Map<String, XmlLoader> loaders = new HashMap<String, XmlLoader>();
+    private final ReadWriteLock lock = new ReentrantReadWriteLock();
+
+    public XmlRepository(String url) {
+        this.url = url;
+    }
+
+    @Override
+    public List<Resource> getResources() {
+        checkAndLoadCache();
+        return super.getResources();
+    }
+
+    @Override
+    public Map<Requirement, Collection<Capability>> findProviders(Collection<? extends Requirement> requirements) {
+        checkAndLoadCache();
+        return super.findProviders(requirements);
+    }
+
+    @Override
+    protected void addResource(Resource resource) {
+        List<Capability> identities = resource.getCapabilities(IDENTITY_NAMESPACE);
+        if (identities.isEmpty()) {
+            throw new IllegalStateException("Invalid resource: a capability with 'osgi.identity' namespace is required");
+        } else if (identities.size() > 1) {
+            throw new IllegalStateException("Invalid resource: multiple 'osgi.identity' capabilities found");
+        }
+        Capability identity = identities.get(0);
+        Object name = identity.getAttributes().get(IDENTITY_NAMESPACE);
+        Object type = identity.getAttributes().get(CAPABILITY_TYPE_ATTRIBUTE);
+        Object vers = identity.getAttributes().get(CAPABILITY_VERSION_ATTRIBUTE);
+        if (!String.class.isInstance(name)
+                || !String.class.isInstance(type)
+                || !Version.class.isInstance(vers)) {
+            throw new IllegalStateException("Invalid osgi.identity capability: " + identity);
+        }
+        if (!hasResource((String) type, (String) name, (Version) vers)) {
+            super.addResource(resource);
+        }
+    }
+
+    private boolean hasResource(String type, String name, Version version) {
+        CapabilitySet set = capSets.get(IDENTITY_NAMESPACE);
+        if (set != null) {
+            Map<String, Object> attrs = new HashMap<String, Object>();
+            attrs.put(CAPABILITY_TYPE_ATTRIBUTE, type);
+            attrs.put(IDENTITY_NAMESPACE, name);
+            attrs.put(CAPABILITY_VERSION_ATTRIBUTE, version);
+            SimpleFilter sf = SimpleFilter.convert(attrs);
+            return !set.match(sf, true).isEmpty();
+        } else {
+            return false;
+        }
+    }
+
+    private void checkAndLoadCache() {
+        if (checkAndLoadReferrals(url, Integer.MAX_VALUE)) {
+            lock.writeLock().lock();
+            try {
+                resources.clear();
+                capSets.clear();
+                populate(loaders.get(url).xml, Integer.MAX_VALUE);
+            } finally {
+                lock.writeLock().unlock();
+            }
+        }
+    }
+
+    private void populate(StaxParser.XmlRepository xml, int hopCount) {
+        if (hopCount > 0) {
+            for (Resource resource : xml.resources) {
+                addResource(resource);
+            }
+            for (StaxParser.Referral referral : xml.referrals) {
+                populate(loaders.get(referral.url).xml, Math.min(referral.depth, hopCount - 1));
+            }
+        }
+    }
+
+    private boolean checkAndLoadReferrals(String url, int hopCount) {
+        boolean modified = false;
+        if (hopCount > 0) {
+            XmlLoader loader = loaders.get(url);
+            if (loader == null) {
+                loader = new XmlLoader(url);
+                loaders.put(url, loader);
+            }
+            modified = loader.checkAndLoadCache();
+            for (StaxParser.Referral referral : loader.xml.referrals) {
+                modified |= checkAndLoadReferrals(referral.url, Math.min(referral.depth, hopCount - 1));
+            }
+        }
+        return modified;
+    }
+
+    static class XmlLoader extends UrlLoader {
+
+        StaxParser.XmlRepository xml;
+
+        XmlLoader(String url) {
+            super(url);
+        }
+
+        @Override
+        protected boolean doRead(InputStream is) throws IOException {
+            try {
+                StaxParser.XmlRepository oldXml = xml;
+                xml = StaxParser.parse(is, oldXml);
+                return oldXml != xml;
+            } catch (XMLStreamException e) {
+                throw new IOException("Unable to read xml repository", e);
+            }
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/3574b440/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
index 873720e..cd48ca2 100644
--- 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
@@ -34,6 +34,15 @@ public class ResourceImpl implements Resource {
     private final List<Capability> m_caps;
     private final List<Requirement> m_reqs;
 
+    /**
+     * CAUTION: This constructor does not ensure that the resource
+     * has the required identity capability
+     */
+    public ResourceImpl() {
+        m_caps = new ArrayList<Capability>();
+        m_reqs = new ArrayList<Requirement>();
+    }
+
     public ResourceImpl(String name, Version version) {
         this(name, IdentityNamespace.TYPE_BUNDLE, version);
     }

http://git-wip-us.apache.org/repos/asf/karaf/blob/3574b440/features/core/src/test/java/org/apache/karaf/features/internal/repository/RepositoryTest.java
----------------------------------------------------------------------
diff --git a/features/core/src/test/java/org/apache/karaf/features/internal/repository/RepositoryTest.java b/features/core/src/test/java/org/apache/karaf/features/internal/repository/RepositoryTest.java
new file mode 100644
index 0000000..c05eea0
--- /dev/null
+++ b/features/core/src/test/java/org/apache/karaf/features/internal/repository/RepositoryTest.java
@@ -0,0 +1,58 @@
+/*
+ * 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.net.URL;
+
+import org.junit.Test;
+import org.osgi.resource.Resource;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.osgi.framework.namespace.BundleNamespace.BUNDLE_NAMESPACE;
+import static org.osgi.framework.namespace.IdentityNamespace.IDENTITY_NAMESPACE;
+import static org.osgi.framework.namespace.PackageNamespace.PACKAGE_NAMESPACE;
+
+public class RepositoryTest {
+
+    @Test
+    public void testXml() throws Exception {
+        URL url = getClass().getResource("repo.xml");
+        XmlRepository repo = new XmlRepository(url.toExternalForm());
+        verify(repo);
+    }
+
+    @Test
+    public void testJson() throws Exception {
+        URL url = getClass().getResource("repo.json");
+        JsonRepository repo = new JsonRepository(url.toExternalForm());
+        verify(repo);
+    }
+
+    private void verify(BaseRepository repo) {
+        assertNotNull(repo.getResources());
+        assertEquals(1, repo.getResources().size());
+        Resource resource = repo.getResources().get(0);
+        assertNotNull(resource);
+        assertEquals(1, resource.getCapabilities(IDENTITY_NAMESPACE).size());
+        assertEquals(1, resource.getCapabilities(BUNDLE_NAMESPACE).size());
+        assertEquals(1, resource.getCapabilities(PACKAGE_NAMESPACE).size());
+        assertEquals(1, resource.getRequirements(PACKAGE_NAMESPACE).size());
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/3574b440/features/core/src/test/resources/org/apache/karaf/features/internal/repository/repo.json
----------------------------------------------------------------------
diff --git a/features/core/src/test/resources/org/apache/karaf/features/internal/repository/repo.json b/features/core/src/test/resources/org/apache/karaf/features/internal/repository/repo.json
new file mode 100644
index 0000000..7249594
--- /dev/null
+++ b/features/core/src/test/resources/org/apache/karaf/features/internal/repository/repo.json
@@ -0,0 +1,9 @@
+ {
+	"http://www.acme.com/repository/org/acme/pool/org.acme.pool-1.5.6.jar": {
+		"Bundle-ManifestVersion": "2",
+	 	"Bundle-SymbolicName": "org.acme.pool",
+	 	"Bundle-Version": "1.5.6",
+	 	"Export-Package": "org.acme.pool; version=1.1.2; uses=\"org.acme.pool,org.acme.util\"",
+	 	"Import-Package": "org.apache.commons.pool; version=1.5.6"
+	}
+ }

http://git-wip-us.apache.org/repos/asf/karaf/blob/3574b440/features/core/src/test/resources/org/apache/karaf/features/internal/repository/repo.xml
----------------------------------------------------------------------
diff --git a/features/core/src/test/resources/org/apache/karaf/features/internal/repository/repo.xml b/features/core/src/test/resources/org/apache/karaf/features/internal/repository/repo.xml
new file mode 100644
index 0000000..dc1318b
--- /dev/null
+++ b/features/core/src/test/resources/org/apache/karaf/features/internal/repository/repo.xml
@@ -0,0 +1,53 @@
+<?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.
+-->
+<repository name='OSGi Repository' increment='13582741'
+            xmlns='http://www.osgi.org/xmlns/repository/v1.0.0'>
+    <resource>
+        <capability namespace='osgi.identity'>
+            <attribute name='osgi.identity' value='org.acme.pool'/>
+            <attribute name='version' type='Version' value='1.5.6'> </attribute>
+            <attribute name='type' value='osgi.bundle'/>
+        </capability>
+        <capability namespace='osgi.content'>
+            <attribute name='osgi.content' value='e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'/>
+            <attribute name='url' value='http://www.acme.com/repository/org/acme/pool/org.acme.pool-1.5.6.jar'/>
+            <attribute name='size' type='Long' value='4405'/>
+            <attribute name='mime' value='application/vnd.osgi.bundle'/>
+        </capability>
+        <capability namespace='osgi.wiring.bundle'>
+            <attribute name='osgi.wiring.bundle' value='org.acme.pool'/>
+            <attribute name='bundle-version' type='Version' value='1.5.6'/>
+        </capability>
+        <capability namespace='osgi.wiring.package'>
+            <attribute name='osgi.wiring.package' value='org.acme.pool'/>
+            <attribute name='version' type='Version' value='1.1.2'/>
+            <attribute name='bundle-version' type='Version' value='1.5.6'/>
+            <attribute name='bundle-symbolic-name' value='org.acme.pool'/>
+            <directive name='uses' value='org.acme.pool,org.acme.util'/>
+        </capability>
+        <requirement namespace='osgi.wiring.package'>
+            <directive name='filter' value='(&amp;(osgi.wiring.package=org.apache.commons.pool)(version&gt;=1.5.6))'/>
+        </requirement>
+        <requirement namespace='osgi.identity'>
+            <directive name='effective' value='meta'/>
+            <directive name='resolution' value='optional'/>
+            <directive name='filter' value='(&amp;(version=1.5.6)(osgi.identity=org.acme.pool-src))'/>
+            <directive name='classifier' value='sources'/>
+        </requirement>
+    </resource>
+</repository>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/karaf/blob/3574b440/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 82427e0..51a1eb3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -2250,6 +2250,8 @@
                                 <exclude>manual/**/*.conf</exclude>
                                 <!-- test manifests -->
                                 <exclude>**/*.mf</exclude>
+                                <!-- test json files -->
+                                <exclude>**/*.json</exclude>
                             </excludes>
                         </configuration>
                     </plugin>