You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@karaf.apache.org by gn...@apache.org on 2015/06/11 08:31:39 UTC

[1/2] karaf git commit: [KARAF-3764] NoClassDefFoundError when the features-core bundle is refreshed by itself

Repository: karaf
Updated Branches:
  refs/heads/master 947d2eb7c -> 8723b8fd8


[KARAF-3764] NoClassDefFoundError when the features-core bundle is refreshed by itself

Also fix problems with eventadmin and packageadmin causing refreshes of pax-logging-api and most of bundles at the start phase


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

Branch: refs/heads/master
Commit: 8723b8fd843040e7eed3fd9f887efd53727c0c3a
Parents: 9770185
Author: Guillaume Nodet <gn...@gmail.com>
Authored: Wed Jun 10 21:08:25 2015 +0200
Committer: Guillaume Nodet <gn...@gmail.com>
Committed: Wed Jun 10 21:13:35 2015 +0200

----------------------------------------------------------------------
 assemblies/apache-karaf/pom.xml                 |   5 +-
 .../standard/src/main/feature/feature.xml       |   2 +-
 config/pom.xml                                  |   5 +-
 .../karaf/config/command/MetaCommand.java       | 125 ++++++++++---------
 .../features/internal/service/Deployer.java     |  24 ++--
 .../internal/service/EventAdminListener.java    |  93 ++++++++------
 .../karaf/itests/ConditionalFeaturesTest.java   |   3 +-
 .../org/apache/karaf/itests/FeatureTest.java    |   2 +-
 8 files changed, 147 insertions(+), 112 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/karaf/blob/8723b8fd/assemblies/apache-karaf/pom.xml
----------------------------------------------------------------------
diff --git a/assemblies/apache-karaf/pom.xml b/assemblies/apache-karaf/pom.xml
index f2b7e7b..ba12881 100644
--- a/assemblies/apache-karaf/pom.xml
+++ b/assemblies/apache-karaf/pom.xml
@@ -48,7 +48,6 @@
             <artifactId>standard</artifactId>
             <classifier>features</classifier>
             <type>xml</type>
-            <scope>runtime</scope>
         </dependency>
         <dependency>
             <groupId>org.apache.karaf.features</groupId>
@@ -159,8 +158,10 @@
                     <installedFeatures>
                         <feature>wrapper</feature>
                     </installedFeatures>
-                    <bootFeatures>
+                    <startupFeatures>
                         <feature>eventadmin</feature>
+                    </startupFeatures>
+                    <bootFeatures>
                         <feature>wrap</feature>
                         <feature>aries-blueprint</feature>
                         <feature>shell</feature>

http://git-wip-us.apache.org/repos/asf/karaf/blob/8723b8fd/assemblies/features/standard/src/main/feature/feature.xml
----------------------------------------------------------------------
diff --git a/assemblies/features/standard/src/main/feature/feature.xml b/assemblies/features/standard/src/main/feature/feature.xml
index 7c66305..c45c828 100644
--- a/assemblies/features/standard/src/main/feature/feature.xml
+++ b/assemblies/features/standard/src/main/feature/feature.xml
@@ -499,7 +499,7 @@
             org.apache.felix.eventadmin.AddTimestamp=true
             org.apache.felix.eventadmin.AddSubject=true
         </config>
-        <bundle start-level="30">mvn:org.apache.karaf.services/org.apache.karaf.services.eventadmin/${project.version}</bundle>
+        <bundle start-level="5">mvn:org.apache.karaf.services/org.apache.karaf.services.eventadmin/${project.version}</bundle>
         <conditional>
             <condition>webconsole</condition>
             <bundle start-level="30">mvn:org.apache.felix/org.apache.felix.webconsole.plugins.event/${felix.eventadmin.webconsole.plugin.version}</bundle>

http://git-wip-us.apache.org/repos/asf/karaf/blob/8723b8fd/config/pom.xml
----------------------------------------------------------------------
diff --git a/config/pom.xml b/config/pom.xml
index a460394..16abc8c 100644
--- a/config/pom.xml
+++ b/config/pom.xml
@@ -102,11 +102,10 @@
                         <Export-Package>
                             org.apache.karaf.config.command*,
                             org.apache.karaf.config.core,
-                            org.osgi.service.metatype
                         </Export-Package>
                         <Import-Package>
-                            *,
-                            org.osgi.service.metatype
+                            org.osgi.service.metatype;resolution:=optional,
+                            *
                         </Import-Package>
                         <Private-Package>
                             org.apache.karaf.config.core.impl,

http://git-wip-us.apache.org/repos/asf/karaf/blob/8723b8fd/config/src/main/java/org/apache/karaf/config/command/MetaCommand.java
----------------------------------------------------------------------
diff --git a/config/src/main/java/org/apache/karaf/config/command/MetaCommand.java b/config/src/main/java/org/apache/karaf/config/command/MetaCommand.java
index 83a1e7d..b7554ae 100644
--- a/config/src/main/java/org/apache/karaf/config/command/MetaCommand.java
+++ b/config/src/main/java/org/apache/karaf/config/command/MetaCommand.java
@@ -61,74 +61,87 @@ public class MetaCommand extends ConfigCommandSupport {
     }
 
     @Override
-    protected Object doExecute() throws Exception {
-
-        ServiceReference<MetaTypeService> ref = context.getServiceReference(MetaTypeService.class);
-        if (ref == null) {
+    public Object doExecute() throws Exception {
+        try {
+            new InnerCommand().doExecute();
+        } catch (NoClassDefFoundError e) {
             System.out
-                .println("No MetaTypeService present. You need to install an implementation to use this command.");
-        }
-        MetaTypeService metaTypeService = context.getService(ref);
-        ObjectClassDefinition def = getMetatype(metaTypeService, pid);
-        context.ungetService(ref);
-
-        if (def == null) {
-            System.out.println("No meta type definition found for pid: " + pid);
-            return null;
+                    .println("No MetaTypeService present. You need to install an implementation to use this command.");
         }
-        System.out.println("Meta type informations for pid: " + pid);
-        ShellTable table = new ShellTable();
-        table.column("key");
-        table.column("name");
-        table.column("type");
-        table.column("default");
-        table.column("description");
-        AttributeDefinition[] attrs = def.getAttributeDefinitions(ObjectClassDefinition.ALL);
-        if (attrs != null) {
-            for (AttributeDefinition attr : attrs) {
-                table.addRow().addContent(attr.getID(), attr.getName(), getType(attr.getType()),
-                                          getDefaultValueStr(attr.getDefaultValue()), attr.getDescription());
-            }
-        }
-        table.print(System.out);
         return null;
     }
 
-    private String getType(int type) {
-        return typeMap.get(type);
-    }
+    class InnerCommand {
 
-    private String getDefaultValueStr(String[] defaultValues) {
-        if (defaultValues == null) {
-            return "";
-        }
-        StringBuilder result = new StringBuilder();
-        boolean first = true;
-        for (String defaultValue : defaultValues) {
-            if (first) {
-                first = false;
-            } else {
-                result.append(",");
+        protected Object doExecute() throws Exception {
+
+            ServiceReference<MetaTypeService> ref = context.getServiceReference(MetaTypeService.class);
+            if (ref == null) {
+                System.out
+                        .println("No MetaTypeService present. You need to install an implementation to use this command.");
+            }
+            MetaTypeService metaTypeService = context.getService(ref);
+            ObjectClassDefinition def = getMetatype(metaTypeService, pid);
+            context.ungetService(ref);
+
+            if (def == null) {
+                System.out.println("No meta type definition found for pid: " + pid);
+                return null;
+            }
+            System.out.println("Meta type informations for pid: " + pid);
+            ShellTable table = new ShellTable();
+            table.column("key");
+            table.column("name");
+            table.column("type");
+            table.column("default");
+            table.column("description");
+            AttributeDefinition[] attrs = def.getAttributeDefinitions(ObjectClassDefinition.ALL);
+            if (attrs != null) {
+                for (AttributeDefinition attr : attrs) {
+                    table.addRow().addContent(attr.getID(), attr.getName(), getType(attr.getType()),
+                            getDefaultValueStr(attr.getDefaultValue()), attr.getDescription());
+                }
             }
-            result.append(defaultValue);
+            table.print(System.out);
+            return null;
+        }
+
+        private String getType(int type) {
+            return typeMap.get(type);
         }
-        return result.toString();
-    }
 
-    public ObjectClassDefinition getMetatype(MetaTypeService metaTypeService, String pid) {
-        for (Bundle bundle : context.getBundles()) {
-            MetaTypeInformation info = metaTypeService.getMetaTypeInformation(bundle);
-            if (info == null) {
-                continue;
+        private String getDefaultValueStr(String[] defaultValues) {
+            if (defaultValues == null) {
+                return "";
             }
-            String[] pids = info.getPids();
-            for (String cPid : pids) {
-                if (cPid.equals(pid)) {
-                    return info.getObjectClassDefinition(cPid, null);
+            StringBuilder result = new StringBuilder();
+            boolean first = true;
+            for (String defaultValue : defaultValues) {
+                if (first) {
+                    first = false;
+                } else {
+                    result.append(",");
                 }
+                result.append(defaultValue);
             }
+            return result.toString();
         }
-        return null;
-    }
 
+        public ObjectClassDefinition getMetatype(MetaTypeService metaTypeService, String pid) {
+            for (Bundle bundle : context.getBundles()) {
+                MetaTypeInformation info = metaTypeService.getMetaTypeInformation(bundle);
+                if (info == null) {
+                    continue;
+                }
+                String[] pids = info.getPids();
+                for (String cPid : pids) {
+                    if (cPid.equals(pid)) {
+                        return info.getObjectClassDefinition(cPid, null);
+                    }
+                }
+            }
+            return null;
+        }
+
+    }
 }

http://git-wip-us.apache.org/repos/asf/karaf/blob/8723b8fd/features/core/src/main/java/org/apache/karaf/features/internal/service/Deployer.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/Deployer.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/Deployer.java
index e6bc258..f3f767b 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/service/Deployer.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/Deployer.java
@@ -775,10 +775,6 @@ public class Deployer {
             }
         }
 
-        // TODO: remove this hack, but it avoids loading the class after the bundle is refreshed
-        new CopyOnWriteArrayIdentityList().iterator();
-        RequirementSort.sort(Collections.<Resource>emptyList());
-
         if (!noRefresh) {
             toStop = new HashSet<>();
             toStop.addAll(toRefresh.keySet());
@@ -802,9 +798,12 @@ public class Deployer {
                     Bundle bundle = entry.getKey();
                     print("    " + bundle.getSymbolicName() + " / " + bundle.getVersion() + " (" + entry.getValue() + ")", verbose);
                 }
-                if (!toRefresh.isEmpty()) {
-                    callback.refreshPackages(toRefresh.keySet());
+                // Ensure all classes are loaded in case the bundle will be refreshed
+                if (dstate.serviceBundle != null && toRefresh.containsKey(dstate.serviceBundle)) {
+                    ensureAllClassesLoaded(dstate.serviceBundle);
                 }
+                callback.refreshPackages(toRefresh.keySet());
+
             }
         }
 
@@ -935,7 +934,7 @@ public class Deployer {
                 Resource resource = bndToRes.get(bundle);
                 // This bundle is not managed
                 if (resource == null) {
-                    continue;
+                    resource = bundle.adapt(BundleRevision.class);
                 }
                 // Continue if we already know about this bundle
                 if (toRefresh.containsKey(bundle)) {
@@ -1370,4 +1369,15 @@ public class Deployer {
         return provider.open();
     }
 
+    public static void ensureAllClassesLoaded(Bundle bundle) throws ClassNotFoundException {
+        BundleWiring wiring = bundle.adapt(BundleWiring.class);
+        if (wiring != null) {
+            for (String path : wiring.listResources("/", "*.class", BundleWiring.LISTRESOURCES_RECURSE)) {
+                String className = path.substring(0, path.length() - ".class".length());
+                className = className.replace('/', '.');
+                bundle.loadClass(className);
+            }
+        }
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/karaf/blob/8723b8fd/features/core/src/main/java/org/apache/karaf/features/internal/service/EventAdminListener.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/EventAdminListener.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/EventAdminListener.java
index c16613d..bd5c0d5 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/service/EventAdminListener.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/EventAdminListener.java
@@ -27,12 +27,15 @@ import org.osgi.framework.BundleContext;
 import org.osgi.service.event.Event;
 import org.osgi.service.event.EventAdmin;
 import org.osgi.util.tracker.ServiceTracker;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * A listener to publish events to EventAdmin
  */
 public class EventAdminListener implements FeaturesListener {
 
+    private static final Logger LOGGER = LoggerFactory.getLogger(EventAdminListener.class);
     private final ServiceTracker<EventAdmin, EventAdmin> tracker;
 
     public EventAdminListener(BundleContext context) {
@@ -41,51 +44,59 @@ public class EventAdminListener implements FeaturesListener {
     }
 
     public void featureEvent(FeatureEvent event) {
-        EventAdmin eventAdmin = tracker.getService();
-        if (eventAdmin == null) {
-            return;
+        try {
+            EventAdmin eventAdmin = tracker.getService();
+            if (eventAdmin == null) {
+                return;
+            }
+            Dictionary<String, Object> props = new Hashtable<String, Object>();
+            props.put(EventConstants.TYPE, event.getType());
+            props.put(EventConstants.EVENT, event);
+            props.put(EventConstants.TIMESTAMP, System.currentTimeMillis());
+            props.put(EventConstants.FEATURE_NAME, event.getFeature().getName());
+            props.put(EventConstants.FEATURE_VERSION, event.getFeature().getVersion());
+            String topic;
+            switch (event.getType()) {
+            case FeatureInstalled:
+                topic = EventConstants.TOPIC_FEATURES_INSTALLED;
+                break;
+            case FeatureUninstalled:
+                topic = EventConstants.TOPIC_FEATURES_UNINSTALLED;
+                break;
+            default:
+                throw new IllegalStateException("Unknown features event type: " + event.getType());
+            }
+            eventAdmin.postEvent(new Event(topic, props));
+        } catch (IllegalStateException e) {
+            LOGGER.warn("Unable to post event to EventAdmin", e);
         }
-        Dictionary<String, Object> props = new Hashtable<String, Object>();
-        props.put(EventConstants.TYPE, event.getType());
-        props.put(EventConstants.EVENT, event);
-        props.put(EventConstants.TIMESTAMP, System.currentTimeMillis());
-        props.put(EventConstants.FEATURE_NAME, event.getFeature().getName());
-        props.put(EventConstants.FEATURE_VERSION, event.getFeature().getVersion());
-        String topic;
-        switch (event.getType()) {
-        case FeatureInstalled:
-            topic = EventConstants.TOPIC_FEATURES_INSTALLED;
-            break;
-        case FeatureUninstalled:
-            topic = EventConstants.TOPIC_FEATURES_UNINSTALLED;
-            break;
-        default:
-            throw new IllegalStateException("Unknown features event type: " + event.getType());
-        }
-        eventAdmin.postEvent(new Event(topic, props));
     }
 
     public void repositoryEvent(RepositoryEvent event) {
-        EventAdmin eventAdmin = tracker.getService();
-        if (eventAdmin == null) {
-            return;
-        }
-        Dictionary<String, Object> props = new Hashtable<String, Object>();
-        props.put(EventConstants.TYPE, event.getType());
-        props.put(EventConstants.EVENT, event);
-        props.put(EventConstants.TIMESTAMP, System.currentTimeMillis());
-        props.put(EventConstants.REPOSITORY_URI, event.getRepository().getURI().toString());
-        String topic;
-        switch (event.getType()) {
-        case RepositoryAdded:
-            topic = EventConstants.TOPIC_REPOSITORY_ADDED;
-            break;
-        case RepositoryRemoved:
-            topic = EventConstants.TOPIC_REPOSITORY_REMOVED;
-            break;
-        default:
-            throw new IllegalStateException("Unknown repository event type: " + event.getType());
+        try {
+            EventAdmin eventAdmin = tracker.getService();
+            if (eventAdmin == null) {
+                return;
+            }
+            Dictionary<String, Object> props = new Hashtable<String, Object>();
+            props.put(EventConstants.TYPE, event.getType());
+            props.put(EventConstants.EVENT, event);
+            props.put(EventConstants.TIMESTAMP, System.currentTimeMillis());
+            props.put(EventConstants.REPOSITORY_URI, event.getRepository().getURI().toString());
+            String topic;
+            switch (event.getType()) {
+            case RepositoryAdded:
+                topic = EventConstants.TOPIC_REPOSITORY_ADDED;
+                break;
+            case RepositoryRemoved:
+                topic = EventConstants.TOPIC_REPOSITORY_REMOVED;
+                break;
+            default:
+                throw new IllegalStateException("Unknown repository event type: " + event.getType());
+            }
+            eventAdmin.postEvent(new Event(topic, props));
+        } catch (IllegalStateException e) {
+            LOGGER.warn("Unable to post event to EventAdmin", e);
         }
-        eventAdmin.postEvent(new Event(topic, props));
     }
 }

http://git-wip-us.apache.org/repos/asf/karaf/blob/8723b8fd/itests/src/test/java/org/apache/karaf/itests/ConditionalFeaturesTest.java
----------------------------------------------------------------------
diff --git a/itests/src/test/java/org/apache/karaf/itests/ConditionalFeaturesTest.java b/itests/src/test/java/org/apache/karaf/itests/ConditionalFeaturesTest.java
index f64dc02..18ef2d0 100644
--- a/itests/src/test/java/org/apache/karaf/itests/ConditionalFeaturesTest.java
+++ b/itests/src/test/java/org/apache/karaf/itests/ConditionalFeaturesTest.java
@@ -76,6 +76,7 @@ public class ConditionalFeaturesTest extends KarafTestSupport {
             featureService.uninstallFeature("scr", EnumSet.of(FeaturesService.Option.NoAutoRefreshBundles));
         } catch (Exception e) {
         }
+        featureService.installFeature("eventadmin", EnumSet.of(FeaturesService.Option.NoAutoRefreshBundles));
         featureService.installFeature("webconsole", EnumSet.of(FeaturesService.Option.NoAutoRefreshBundles));
 
         assertBundleInstalled("org.apache.karaf.webconsole.features");
@@ -84,7 +85,7 @@ public class ConditionalFeaturesTest extends KarafTestSupport {
         assertBundleInstalled("org.apache.karaf.webconsole.http");
         assertBundleInstalled("org.apache.felix.webconsole.plugins.event");
 
-        // remove eventadmin
+        // add eventadmin
         featureService.uninstallFeature("eventadmin", EnumSet.of(FeaturesService.Option.NoAutoRefreshBundles));
         assertBundleNotInstalled("org.apache.felix.webconsole.plugins.event");
     }

http://git-wip-us.apache.org/repos/asf/karaf/blob/8723b8fd/itests/src/test/java/org/apache/karaf/itests/FeatureTest.java
----------------------------------------------------------------------
diff --git a/itests/src/test/java/org/apache/karaf/itests/FeatureTest.java b/itests/src/test/java/org/apache/karaf/itests/FeatureTest.java
index 9aa4112..e87b111 100644
--- a/itests/src/test/java/org/apache/karaf/itests/FeatureTest.java
+++ b/itests/src/test/java/org/apache/karaf/itests/FeatureTest.java
@@ -34,7 +34,7 @@ public class FeatureTest extends KarafTestSupport {
 
     @Test
     public void bootFeatures() throws Exception {
-        assertFeaturesInstalled("eventadmin","jaas", "ssh", "management", "bundle", "config", "deployer", "diagnostic",
+        assertFeaturesInstalled("jaas", "ssh", "management", "bundle", "config", "deployer", "diagnostic",
                                 "instance", "kar", "log", "package", "service", "system");
     }
 


[2/2] karaf git commit: [KARAF-3759] Provide tooling to store a resolution attempt that failed so that it can be replayed offline for analysis

Posted by gn...@apache.org.
[KARAF-3759] Provide tooling to store a resolution attempt that failed so that it can be replayed offline for analysis


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

Branch: refs/heads/master
Commit: 9770185c85d0d38d78e3e9d0854582ef8529dd3b
Parents: 947d2eb
Author: Guillaume Nodet <gn...@gmail.com>
Authored: Mon Jun 8 10:25:52 2015 +0200
Committer: Guillaume Nodet <gn...@gmail.com>
Committed: Wed Jun 10 21:13:35 2015 +0200

----------------------------------------------------------------------
 .../features/command/InstallFeatureCommand.java |   4 +
 features/core/pom.xml                           |   6 +
 .../apache/karaf/features/FeaturesService.java  |   2 +
 .../internal/region/OfflineResolver.java        | 167 +++++++++++++++++++
 .../region/SubsystemResolveContext.java         |   8 +
 .../internal/region/SubsystemResolver.java      |  72 +++++++-
 .../internal/resolver/ResourceBuilder.java      |  83 ++++++++-
 .../features/internal/service/Deployer.java     |   4 +-
 .../internal/service/FeaturesServiceImpl.java   |  25 ++-
 .../features/internal/util/JsonWriter.java      |  15 +-
 .../features/internal/region/SubsystemTest.java |  16 +-
 11 files changed, 380 insertions(+), 22 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/karaf/blob/9770185c/features/command/src/main/java/org/apache/karaf/features/command/InstallFeatureCommand.java
----------------------------------------------------------------------
diff --git a/features/command/src/main/java/org/apache/karaf/features/command/InstallFeatureCommand.java b/features/command/src/main/java/org/apache/karaf/features/command/InstallFeatureCommand.java
index 600310b..571a713 100644
--- a/features/command/src/main/java/org/apache/karaf/features/command/InstallFeatureCommand.java
+++ b/features/command/src/main/java/org/apache/karaf/features/command/InstallFeatureCommand.java
@@ -52,6 +52,9 @@ public class InstallFeatureCommand extends FeaturesCommandSupport {
     @Option(name = "-t", aliases = "--simulate", description = "Perform a simulation only", required = false, multiValued = false)
     boolean simulate;
 
+    @Option(name = "--store", description = "Store the resolution into the given file and result for offline analysis")
+    String outputFile;
+
     @Option(name = "-g", aliases = "--region", description = "Region to install to")
     String region;
 
@@ -61,6 +64,7 @@ public class InstallFeatureCommand extends FeaturesCommandSupport {
         addOption(FeaturesService.Option.NoAutoRefreshBundles, noRefresh);
         addOption(FeaturesService.Option.NoAutoManageBundles, noManage);
         addOption(FeaturesService.Option.Verbose, verbose);
+        admin.setResolutionOutputFile(outputFile);
         admin.installFeatures(new HashSet<String>(features), region, options);
     }
 }

http://git-wip-us.apache.org/repos/asf/karaf/blob/9770185c/features/core/pom.xml
----------------------------------------------------------------------
diff --git a/features/core/pom.xml b/features/core/pom.xml
index d45feb5..767fe12 100644
--- a/features/core/pom.xml
+++ b/features/core/pom.xml
@@ -52,6 +52,11 @@
 
         <dependency>
             <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.resolver</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
             <artifactId>org.apache.felix.utils</artifactId>
             <scope>provided</scope>
         </dependency>
@@ -135,6 +140,7 @@
                             org.eclipse.equinox.region.*
                         </Export-Package>
                         <Import-Package>
+                            !org.apache.felix.resolver,
                             !org.eclipse.osgi.service.resolver,
                             *
                         </Import-Package>

http://git-wip-us.apache.org/repos/asf/karaf/blob/9770185c/features/core/src/main/java/org/apache/karaf/features/FeaturesService.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/FeaturesService.java b/features/core/src/main/java/org/apache/karaf/features/FeaturesService.java
index 82d246d..57e7014 100644
--- a/features/core/src/main/java/org/apache/karaf/features/FeaturesService.java
+++ b/features/core/src/main/java/org/apache/karaf/features/FeaturesService.java
@@ -83,6 +83,8 @@ public interface FeaturesService {
 
     String getRepositoryName(URI uri) throws Exception;
 
+    void setResolutionOutputFile(String outputFile);
+
     void installFeature(String name) throws Exception;
 
     void installFeature(String name, EnumSet<Option> options) throws Exception;

http://git-wip-us.apache.org/repos/asf/karaf/blob/9770185c/features/core/src/main/java/org/apache/karaf/features/internal/region/OfflineResolver.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/region/OfflineResolver.java b/features/core/src/main/java/org/apache/karaf/features/internal/region/OfflineResolver.java
new file mode 100644
index 0000000..1c89dc2
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/region/OfflineResolver.java
@@ -0,0 +1,167 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.features.internal.region;
+
+import java.io.BufferedReader;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.felix.resolver.Logger;
+import org.apache.felix.resolver.ResolverImpl;
+import org.apache.karaf.features.internal.repository.BaseRepository;
+import org.apache.karaf.features.internal.resolver.RequirementImpl;
+import org.apache.karaf.features.internal.resolver.ResourceBuilder;
+import org.apache.karaf.features.internal.resolver.ResourceImpl;
+import org.apache.karaf.features.internal.resolver.SimpleFilter;
+import org.apache.karaf.features.internal.util.JsonReader;
+import org.osgi.framework.BundleException;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Requirement;
+import org.osgi.resource.Resource;
+import org.osgi.resource.Wire;
+import org.osgi.resource.Wiring;
+import org.osgi.service.repository.Repository;
+import org.osgi.service.resolver.HostedCapability;
+import org.osgi.service.resolver.ResolveContext;
+import org.osgi.service.resolver.Resolver;
+
+import static org.osgi.framework.Constants.RESOLUTION_DIRECTIVE;
+import static org.osgi.framework.Constants.RESOLUTION_OPTIONAL;
+import static org.osgi.framework.namespace.IdentityNamespace.IDENTITY_NAMESPACE;
+
+public class OfflineResolver {
+
+    public static void main(String[] args) throws Exception {
+        if (args == null || args.length != 1) {
+            throw new IllegalArgumentException("File path expected");
+        }
+        resolve(args[0]);
+    }
+
+    public static void resolve(String resolutionFile) throws Exception {
+        Map<String, Object> resolution;
+        try (BufferedReader reader = Files.newBufferedReader(Paths.get(resolutionFile), StandardCharsets.UTF_8)) {
+            resolution = (Map<String, Object>) JsonReader.read(reader);
+        }
+
+        final Repository globalRepository;
+        if (resolution.containsKey("globalRepository")) {
+            globalRepository = readRepository(resolution.get("globalRepository"));
+        } else {
+            globalRepository = null;
+        }
+        final Repository repository = readRepository(resolution.get("repository"));
+
+        Resolver resolver = new ResolverImpl(new Logger(Logger.LOG_ERROR));
+        Map<Resource, List<Wire>> wiring = resolver.resolve(new ResolveContext() {
+            private final Set<Resource> mandatory = new HashSet<>();
+            private final CandidateComparator candidateComparator = new CandidateComparator(mandatory);
+
+            @Override
+            public Collection<Resource> getMandatoryResources() {
+                List<Resource> resources = new ArrayList<Resource>();
+                Requirement req = new RequirementImpl(
+                        null,
+                        IDENTITY_NAMESPACE,
+                        Collections.<String, String>emptyMap(),
+                        Collections.<String, Object>emptyMap(),
+                        SimpleFilter.parse("(" + IDENTITY_NAMESPACE + "=root)"));
+                Collection<Capability> identities = repository.findProviders(Collections.singleton(req)).get(req);
+                for (Capability identity : identities) {
+                    resources.add(identity.getResource());
+                }
+                return resources;
+            }
+
+            @Override
+            public List<Capability> findProviders(Requirement requirement) {
+                List<Capability> caps = new ArrayList<>();
+                Map<Requirement, Collection<Capability>> resMap =
+                        repository.findProviders(Collections.singleton(requirement));
+                Collection<Capability> res = resMap != null ? resMap.get(requirement) : null;
+                if (res != null && !res.isEmpty()) {
+                    caps.addAll(res);
+                } else if (globalRepository != null) {
+                    // Only bring in external resources for non optional requirements
+                    if (!RESOLUTION_OPTIONAL.equals(requirement.getDirectives().get(RESOLUTION_DIRECTIVE))) {
+                        resMap = globalRepository.findProviders(Collections.singleton(requirement));
+                        res = resMap != null ? resMap.get(requirement) : null;
+                        if (res != null && !res.isEmpty()) {
+                            caps.addAll(res);
+                        }
+                    }
+                }
+
+                // Sort caps
+                Collections.sort(caps, candidateComparator);
+                return caps;
+            }
+
+            @Override
+            public int insertHostedCapability(List<Capability> capabilities, HostedCapability hostedCapability) {
+                int idx = Collections.binarySearch(capabilities, hostedCapability, candidateComparator);
+                if (idx < 0) {
+                    idx = Math.abs(idx + 1);
+                }
+                capabilities.add(idx, hostedCapability);
+                return idx;
+            }
+
+            @Override
+            public boolean isEffective(Requirement requirement) {
+                return true;
+            }
+
+            @Override
+            public Map<Resource, Wiring> getWirings() {
+                return Collections.emptyMap();
+            }
+        });
+    }
+
+    private static Repository readRepository(Object repository) throws BundleException {
+        List<Resource> resources = new ArrayList<>();
+        Collection<Map<String, List<String>>> metadatas;
+        if (repository instanceof Map) {
+            metadatas = ((Map<String, Map<String, List<String>>>) repository).values();
+        } else {
+            metadatas = (Collection<Map<String, List<String>>>) repository;
+        }
+        for (Map<String, List<String>> metadata : metadatas) {
+            ResourceImpl res = new ResourceImpl();
+            for (String cap : metadata.get("capabilities")) {
+                res.addCapabilities(ResourceBuilder.parseCapability(res, cap));
+            }
+            if (metadata.containsKey("requirements")) {
+                for (String req : metadata.get("requirements")) {
+                    res.addRequirements(ResourceBuilder.parseRequirement(res, req));
+                }
+            }
+            resources.add(res);
+        }
+        return new BaseRepository(resources);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/9770185c/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 fdf9016..de990f4 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
@@ -87,6 +87,14 @@ public class SubsystemResolveContext extends ResolveContext {
         findMandatory(root);
     }
 
+    public Repository getRepository() {
+        return repository;
+    }
+
+    public Repository getGlobalRepository() {
+        return globalRepository;
+    }
+
     void findMandatory(Resource res) {
         if (mandatory.add(res)) {
             for (Requirement req : res.getRequirements(null)) {

http://git-wip-us.apache.org/repos/asf/karaf/blob/9770185c/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 0402c12..d3701c7 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
@@ -16,6 +16,12 @@
  */
 package org.apache.karaf.features.internal.region;
 
+import java.io.Writer;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
@@ -30,12 +36,16 @@ import org.apache.karaf.features.Feature;
 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.resolver.BaseClause;
 import org.apache.karaf.features.internal.resolver.CapabilityImpl;
 import org.apache.karaf.features.internal.resolver.CapabilitySet;
+import org.apache.karaf.features.internal.resolver.RequirementImpl;
 import org.apache.karaf.features.internal.resolver.ResolverUtil;
 import org.apache.karaf.features.internal.resolver.ResourceBuilder;
 import org.apache.karaf.features.internal.resolver.ResourceImpl;
+import org.apache.karaf.features.internal.resolver.ResourceUtils;
 import org.apache.karaf.features.internal.resolver.SimpleFilter;
+import org.apache.karaf.features.internal.util.JsonWriter;
 import org.eclipse.equinox.internal.region.StandardRegionDigraph;
 import org.eclipse.equinox.region.Region;
 import org.eclipse.equinox.region.RegionDigraph;
@@ -49,6 +59,7 @@ import org.osgi.resource.Capability;
 import org.osgi.resource.Requirement;
 import org.osgi.resource.Resource;
 import org.osgi.resource.Wire;
+import org.osgi.service.repository.Repository;
 import org.osgi.service.resolver.Resolver;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -168,8 +179,9 @@ public class SubsystemResolver {
     public Map<Resource, List<Wire>> resolve(
             Set<String> overrides,
             String featureResolutionRange,
-            final org.osgi.service.repository.Repository globalRepository
-    ) throws Exception {
+            final Repository globalRepository,
+            String outputFile) throws Exception {
+
         if (root == null) {
             return Collections.emptyMap();
         }
@@ -182,7 +194,30 @@ public class SubsystemResolver {
         populateDigraph(digraph, root);
 
         Downloader downloader = manager.createDownloader();
-        wiring = resolver.resolve(new SubsystemResolveContext(root, digraph, globalRepository, downloader));
+        SubsystemResolveContext context = new SubsystemResolveContext(root, digraph, globalRepository, downloader);
+        if (outputFile != null) {
+            Map<String, Object> json = new HashMap<>();
+            if (globalRepository != null) {
+                json.put("globalRepository", toJson(globalRepository));
+            }
+            json.put("repository", toJson(context.getRepository()));
+            try {
+                wiring = resolver.resolve(context);
+                json.put("success", "true");
+            } catch (Exception e) {
+                json.put("exception", e.toString());
+                throw e;
+            } finally {
+                try (Writer writer = Files.newBufferedWriter(
+                        Paths.get(outputFile),
+                        StandardCharsets.UTF_8,
+                        StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
+                    JsonWriter.write(writer, json);
+                }
+            }
+        } else {
+            wiring = resolver.resolve(context);
+        }
         downloader.await();
 
         // Remove wiring to the fake environment resource
@@ -203,6 +238,37 @@ public class SubsystemResolver {
         return wiring;
     }
 
+    private Object toJson(Repository repository) {
+        Requirement req = new RequirementImpl(
+                null,
+                IDENTITY_NAMESPACE,
+                Collections.<String, String>emptyMap(),
+                Collections.<String, Object>emptyMap(),
+                new SimpleFilter(null, null, SimpleFilter.MATCH_ALL));
+        Collection<Capability> identities = repository.findProviders(Collections.singleton(req)).get(req);
+        List<Object> resources = new ArrayList<>();
+        for (Capability identity : identities) {
+            String id = BaseClause.toString(null, identity.getNamespace(), identity.getAttributes(), identity.getDirectives());
+            resources.add(toJson(identity.getResource()));
+        }
+        return resources;
+    }
+
+    private Object toJson(Resource resource) {
+        Map<String, Object> obj = new HashMap<>();
+        List<Object> caps = new ArrayList<>();
+        List<Object> reqs = new ArrayList<>();
+        for (Capability cap : resource.getCapabilities(null)) {
+            caps.add(BaseClause.toString(null, cap.getNamespace(), cap.getAttributes(), cap.getDirectives()));
+        }
+        for (Requirement req : resource.getRequirements(null)) {
+            reqs.add(BaseClause.toString(null, req.getNamespace(), req.getAttributes(), req.getDirectives()));
+        }
+        obj.put("capabilities", caps);
+        obj.put("requirements", reqs);
+        return obj;
+    }
+
     public Map<String, Map<String, BundleInfo>> getBundleInfos() {
         if (bundleInfos == null) {
             bundleInfos = new HashMap<>();

http://git-wip-us.apache.org/repos/asf/karaf/blob/9770185c/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceBuilder.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceBuilder.java b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceBuilder.java
index 47a0661..0dbf4e2 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceBuilder.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceBuilder.java
@@ -455,6 +455,87 @@ public final class ResourceBuilder {
     private static List<ParsedHeaderClause> normalizeRequireCapabilityClauses(
             List<ParsedHeaderClause> clauses) throws BundleException {
 
+        // Convert attributes into specified types.
+        for (ParsedHeaderClause clause : clauses) {
+            for (Map.Entry<String, Object> entry : clause.attrs.entrySet()) {
+                if (entry.getKey().equals("version")) {
+                    clause.attrs.put(entry.getKey(), new VersionRange(entry.getValue().toString()));
+                }
+            }
+            for (Map.Entry<String, String> entry : clause.types.entrySet()) {
+                String type = entry.getValue();
+                if (!type.equals("String")) {
+                    if (type.equals("Double")) {
+                        clause.attrs.put(
+                                entry.getKey(),
+                                new Double(clause.attrs.get(entry.getKey()).toString().trim()));
+                    } else if (type.equals("Version")) {
+                        clause.attrs.put(
+                                entry.getKey(),
+                                new Version(clause.attrs.get(entry.getKey()).toString().trim()));
+                    } else if (type.equals("Long")) {
+                        clause.attrs.put(
+                                entry.getKey(),
+                                new Long(clause.attrs.get(entry.getKey()).toString().trim()));
+                    } else if (type.startsWith("List")) {
+                        int startIdx = type.indexOf('<');
+                        int endIdx = type.indexOf('>');
+                        if (((startIdx > 0) && (endIdx <= startIdx))
+                                || ((startIdx < 0) && (endIdx > 0))) {
+                            throw new BundleException(
+                                    "Invalid Provide-Capability attribute list type for '"
+                                            + entry.getKey()
+                                            + "' : "
+                                            + type
+                            );
+                        }
+
+                        String listType = "String";
+                        if (endIdx > startIdx) {
+                            listType = type.substring(startIdx + 1, endIdx).trim();
+                        }
+
+                        List<String> tokens = parseDelimitedString(
+                                clause.attrs.get(entry.getKey()).toString(), ",", false);
+                        List<Object> values = new ArrayList<>(tokens.size());
+                        for (String token : tokens) {
+                            switch (listType) {
+                            case "String":
+                                values.add(token);
+                                break;
+                            case "Double":
+                                values.add(new Double(token.trim()));
+                                break;
+                            case "Version":
+                                values.add(new Version(token.trim()));
+                                break;
+                            case "Long":
+                                values.add(new Long(token.trim()));
+                                break;
+                            default:
+                                throw new BundleException(
+                                        "Unknown Provide-Capability attribute list type for '"
+                                                + entry.getKey()
+                                                + "' : "
+                                                + type
+                                );
+                            }
+                        }
+                        clause.attrs.put(
+                                entry.getKey(),
+                                values);
+                    } else {
+                        throw new BundleException(
+                                "Unknown Provide-Capability attribute type for '"
+                                        + entry.getKey()
+                                        + "' : "
+                                        + type
+                        );
+                    }
+                }
+            }
+        }
+
         return clauses;
     }
 
@@ -550,7 +631,7 @@ public final class ResourceBuilder {
                 String filterStr = clause.dirs.get(Constants.FILTER_DIRECTIVE);
                 SimpleFilter sf = (filterStr != null)
                         ? SimpleFilter.parse(filterStr)
-                        : new SimpleFilter(null, null, SimpleFilter.MATCH_ALL);
+                        : SimpleFilter.convert(clause.attrs);
                 for (String path : clause.paths) {
                     // Create requirement and add to requirement list.
                     reqList.add(new RequirementImpl(

http://git-wip-us.apache.org/repos/asf/karaf/blob/9770185c/features/core/src/main/java/org/apache/karaf/features/internal/service/Deployer.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/Deployer.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/Deployer.java
index e1d4354..e6bc258 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/service/Deployer.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/Deployer.java
@@ -163,6 +163,7 @@ public class Deployer {
         public Map<String, Set<String>> requirements;
         public Map<String, Map<String, FeatureState>> stateChanges;
         public EnumSet<FeaturesService.Option> options;
+        public String outputFile;
     }
 
     static class Deployment {
@@ -261,7 +262,8 @@ public class Deployer {
         resolver.resolve(
                 request.overrides,
                 request.featureResolutionRange,
-                request.globalRepository);
+                request.globalRepository,
+                request.outputFile);
 
         Map<String, StreamProvider> providers = resolver.getProviders();
         Map<String, Set<Resource>> featuresPerRegion = resolver.getFeaturesPerRegions();

http://git-wip-us.apache.org/repos/asf/karaf/blob/9770185c/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 b003331..4dcd267 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
@@ -160,6 +160,8 @@ public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCall
 
     private final String blacklisted;
 
+    private final ThreadLocal<String> outputFile = new ThreadLocal<>();
+
     /**
      * Optional global repository
      */
@@ -799,6 +801,11 @@ public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCall
 
 
     @Override
+    public void setResolutionOutputFile(String outputFile) {
+        this.outputFile.set(outputFile);
+    }
+
+    @Override
     public void installFeatures(Set<String> features, String region, EnumSet<Option> options) throws Exception {
         State state = copyState();
         Map<String, Set<String>> required = copy(state.requirements);
@@ -954,10 +961,12 @@ public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCall
                                     final EnumSet<Option> options) throws Exception {
         ExecutorService executor = Executors.newCachedThreadPool();
         try {
+            final String outputFile = this.outputFile.get();
+            this.outputFile.set(null);
             executor.submit(new Callable<Object>() {
                 @Override
                 public Object call() throws Exception {
-                    doProvision(requirements, stateChanges, state, options);
+                    doProvision(requirements, stateChanges, state, options, outputFile);
                     return null;
                 }
             }).get();
@@ -1026,7 +1035,7 @@ public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCall
         return dstate;
     }
 
-    private Deployer.DeploymentRequest getDeploymentRequest(Map<String, Set<String>> requirements, Map<String, Map<String, FeatureState>> stateChanges, EnumSet<Option> options) {
+    private Deployer.DeploymentRequest getDeploymentRequest(Map<String, Set<String>> requirements, Map<String, Map<String, FeatureState>> stateChanges, EnumSet<Option> options, String outputFile) {
         Deployer.DeploymentRequest request = new Deployer.DeploymentRequest();
         request.bundleUpdateRange = bundleUpdateRange;
         request.featureResolutionRange = featureResolutionRange;
@@ -1036,15 +1045,17 @@ public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCall
         request.requirements = requirements;
         request.stateChanges = stateChanges;
         request.options = options;
+        request.outputFile = outputFile;
         return request;
     }
 
 
 
-    public void doProvision(Map<String, Set<String>> requirements,                 // all requirements
-                            Map<String, Map<String, FeatureState>> stateChanges, // features state changes
-                            State state,                                           // current state
-                            EnumSet<Option> options                                // installation options
+    public void doProvision(Map<String, Set<String>> requirements,                // all requirements
+                            Map<String, Map<String, FeatureState>> stateChanges,  // features state changes
+                            State state,                                          // current state
+                            EnumSet<Option> options,                              // installation options
+                            String outputFile                                     // file to store the resolution or null
     ) throws Exception {
 
         Dictionary<String, String> props = getMavenConfig();
@@ -1057,7 +1068,7 @@ public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCall
             while (true) {
                 try {
                     Deployer.DeploymentState dstate = getDeploymentState(state);
-                    Deployer.DeploymentRequest request = getDeploymentRequest(requirements, stateChanges, options);
+                    Deployer.DeploymentRequest request = getDeploymentRequest(requirements, stateChanges, options, outputFile);
                     new Deployer(manager, this.resolver, this).deploy(dstate, request);
                     break;
                 } catch (Deployer.PartialDeploymentException e) {

http://git-wip-us.apache.org/repos/asf/karaf/blob/9770185c/features/core/src/main/java/org/apache/karaf/features/internal/util/JsonWriter.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/util/JsonWriter.java b/features/core/src/main/java/org/apache/karaf/features/internal/util/JsonWriter.java
index d65bc35..fb27346 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/util/JsonWriter.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/util/JsonWriter.java
@@ -76,14 +76,25 @@ public final class JsonWriter {
             char c = value.charAt(i);
             switch (c) {
             case '\"':
+                writer.append("\\\"");
+                break;
             case '\\':
+                writer.append("\\\\");
+                break;
             case '\b':
+                writer.append("\\b");
+                break;
             case '\f':
+                writer.append("\\f");
+                break;
             case '\n':
+                writer.append("\\n");
+                break;
             case '\r':
+                writer.append("\\r");
+                break;
             case '\t':
-                writer.append('\\');
-                writer.append(c);
+                writer.append("\\t");
                 break;
             default:
                 if (c < ' ' || (c >= '\u0080' && c < '\u00a0') || (c >= '\u2000' && c < '\u2100')) {

http://git-wip-us.apache.org/repos/asf/karaf/blob/9770185c/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 965b5f6..846a2c9 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
@@ -67,7 +67,7 @@ public class SubsystemTest {
                          Collections.<String, Set<BundleRevision>>emptyMap());
         resolver.resolve(Collections.<String>emptySet(),
                          FeaturesService.DEFAULT_FEATURE_RESOLUTION_RANGE,
-                         null);
+                         null, null);
 
         verify(resolver, expected);
     }
@@ -98,7 +98,7 @@ public class SubsystemTest {
                          Collections.<String, Set<BundleRevision>>emptyMap());
         resolver.resolve(Collections.<String>emptySet(),
                          FeaturesService.DEFAULT_FEATURE_RESOLUTION_RANGE,
-                         null);
+                         null, null);
 
         verify(resolver, expected);
     }
@@ -119,7 +119,7 @@ public class SubsystemTest {
                          Collections.<String, Set<BundleRevision>>emptyMap());
         resolver.resolve(Collections.singleton("b"),
                          FeaturesService.DEFAULT_FEATURE_RESOLUTION_RANGE,
-                         null);
+                         null, null);
 
         verify(resolver, expected);
     }
@@ -139,7 +139,7 @@ public class SubsystemTest {
                          Collections.<String, Set<BundleRevision>>emptyMap());
         resolver.resolve(Collections.<String>emptySet(),
                          FeaturesService.DEFAULT_FEATURE_RESOLUTION_RANGE,
-                         null);
+                         null, null);
 
         verify(resolver, expected);
     }
@@ -161,7 +161,7 @@ public class SubsystemTest {
                          Collections.<String, Set<BundleRevision>>emptyMap());
         resolver.resolve(Collections.<String>emptySet(),
                          FeaturesService.DEFAULT_FEATURE_RESOLUTION_RANGE,
-                         null);
+                         null, null);
 
         verify(resolver, expected);
     }
@@ -183,7 +183,7 @@ public class SubsystemTest {
                 Collections.<String, Set<BundleRevision>>emptyMap());
         resolver.resolve(Collections.<String>emptySet(),
                 FeaturesService.DEFAULT_FEATURE_RESOLUTION_RANGE,
-                null);
+                null, null);
 
         verify(resolver, expected);
     }
@@ -204,7 +204,7 @@ public class SubsystemTest {
                 Collections.<String, Set<BundleRevision>>emptyMap());
         resolver.resolve(Collections.<String>emptySet(),
                 FeaturesService.DEFAULT_FEATURE_RESOLUTION_RANGE,
-                null);
+                null, null);
 
         verify(resolver, expected);
     }
@@ -226,7 +226,7 @@ public class SubsystemTest {
                 Collections.<String, Set<BundleRevision>>emptyMap());
         resolver.resolve(Collections.<String>emptySet(),
                 FeaturesService.DEFAULT_FEATURE_RESOLUTION_RANGE,
-                null);
+                null, null);
 
         verify(resolver, expected);
     }