You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@karaf.apache.org by jb...@apache.org on 2020/11/15 15:32:54 UTC

[karaf] branch master updated: [KARAF-6479] Add features json format support

This is an automated email from the ASF dual-hosted git repository.

jbonofre pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/karaf.git


The following commit(s) were added to refs/heads/master by this push:
     new 08e8353  [KARAF-6479] Add features json format support
     new be83639  Merge pull request #1251 from jbonofre/KARAF-6479
08e8353 is described below

commit 08e835321981dc839d47a46ff208588ae8a867a2
Author: jbonofre <jb...@apache.org>
AuthorDate: Fri Nov 13 17:57:07 2020 +0100

    [KARAF-6479] Add features json format support
---
 .../features/FeatureDeploymentListener.java        |  23 ++-
 features/core/pom.xml                              |   8 +-
 .../karaf/features/internal/model/JacksonUtil.java |  81 ++++++++
 .../internal/service/FeaturesServiceImpl.java      |   8 +-
 .../features/internal/service/RepositoryImpl.java  |   7 +-
 .../apache/karaf/features/FeaturesServiceTest.java |  27 +++
 .../karaf/features/internal/model/JsonTest.java    |  61 ++++++
 .../resources/org/apache/karaf/features/repo.json  |  47 +++++
 .../src/main/asciidoc/user-guide/provisioning.adoc | 204 ++++++++++++++++++++-
 .../org/apache/karaf/profile/assembly/Builder.java |  15 +-
 .../java/org/apache/karaf/tooling/KarMojo.java     |   8 +-
 .../java/org/apache/karaf/tooling/VerifyMojo.java  |  13 +-
 .../tooling/features/AbstractFeatureMojo.java      |  22 ++-
 .../features/ExportFeatureMetaDataMojo.java        |  11 +-
 .../tooling/features/GenerateDescriptorMojo.java   |  56 ++++--
 15 files changed, 525 insertions(+), 66 deletions(-)

diff --git a/deployer/features/src/main/java/org/apache/karaf/deployer/features/FeatureDeploymentListener.java b/deployer/features/src/main/java/org/apache/karaf/deployer/features/FeatureDeploymentListener.java
index 58bfc2b..5c4c7ee 100644
--- a/deployer/features/src/main/java/org/apache/karaf/deployer/features/FeatureDeploymentListener.java
+++ b/deployer/features/src/main/java/org/apache/karaf/deployer/features/FeatureDeploymentListener.java
@@ -16,12 +16,7 @@
  */
 package org.apache.karaf.deployer.features;
 
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
+import java.io.*;
 import java.net.URI;
 import java.net.URL;
 import java.util.ArrayList;
@@ -142,14 +137,26 @@ public class FeatureDeploymentListener implements ArtifactUrlTransformer, Bundle
         }
     }
 
+    @Override
     public boolean canHandle(File artifact) {
         try {
+            if (artifact.isFile() && artifact.getName().endsWith(".json")) {
+                try (BufferedReader reader = new BufferedReader(new FileReader(artifact))) {
+                    String line;
+                    while ((line = reader.readLine()) != null) {
+                        if (line.contains("\"feature\"")) {
+                            return true;
+                        }
+                    }
+                }
+                return false;
+            }
             if (artifact.isFile() && artifact.getName().endsWith(".xml")) {
                 QName qname = getRootElementName(artifact);
                 String name = qname.getLocalPart();
                 String uri  = qname.getNamespaceURI();
                 if ("features".equals(name) ) {
-                	if(isKnownFeaturesURI(uri)){
+                	if (isKnownFeaturesURI(uri)){
                         return true;
                 	} else {
                 		logger.error("unknown features uri", new Exception("" + uri));
@@ -208,7 +215,7 @@ public class FeatureDeploymentListener implements ArtifactUrlTransformer, Bundle
             }
             saveProperties();
 
-            // Compute new informations
+            // Compute new information
             List<URI> repsToAdd = new ArrayList<>();
             List<String> reqsToAdd = new ArrayList<>();
             if (bundleEvent.getType() == BundleEvent.RESOLVED) {
diff --git a/features/core/pom.xml b/features/core/pom.xml
index ab27cf3..1b0ad0a 100644
--- a/features/core/pom.xml
+++ b/features/core/pom.xml
@@ -150,6 +150,11 @@
             <groupId>org.glassfish.jaxb</groupId>
             <artifactId>jaxb-runtime</artifactId>
         </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+            <version>2.10.5</version>
+        </dependency>
     </dependencies>
 
     <build>
@@ -213,7 +218,8 @@
                             org.apache.felix.resolver.*,
                             org.ops4j.pax.swissbox.*,
                             org.ops4j.util.*,
-                            org.ops4j.lang.*
+                            org.ops4j.lang.*,
+                            com.fasterxml.jackson*
                         </Private-Package>
                         <Embed-Dependency>
                             org.apache.karaf.util;inline="org/apache/karaf/util/XmlUtils*.class"
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/model/JacksonUtil.java b/features/core/src/main/java/org/apache/karaf/features/internal/model/JacksonUtil.java
new file mode 100644
index 0000000..950cd30
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/model/JacksonUtil.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.features.internal.model;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.*;
+import java.net.URL;
+
+public class JacksonUtil {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(JacksonUtil.class);
+
+    private static final ObjectMapper mapper;
+
+    static {
+        mapper = new ObjectMapper();
+    }
+
+    private JacksonUtil() {
+    }
+
+    public static boolean isJson(String uri) {
+        try {
+            return isJson(new URL(uri).openStream());
+        } catch (Exception e) {
+            return false;
+        }
+    }
+
+    public static boolean isJson(InputStream inputStream) throws IOException {
+        try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
+            String line;
+            while ((line = reader.readLine()) != null) {
+                line = line.trim();
+                if (line.isEmpty()) {
+                    continue;
+                }
+                if (line.startsWith("{")) {
+                    return true;
+                } else {
+                    return false;
+                }
+            }
+        }
+        return false;
+    }
+
+    public static Features unmarshal(String uri) throws IOException {
+        return unmarshal(new URL(uri).openStream());
+    }
+
+    public static Features unmarshal(InputStream inputStream) throws IOException {
+        return mapper.readValue(inputStream, Features.class);
+    }
+
+    public static void marshal(Features features, OutputStream outputStream) throws IOException {
+        mapper.writeValue(outputStream, features);
+    }
+
+    public static void marshal(Features features, Writer writer) throws IOException {
+        mapper.writeValue(writer, features);
+    }
+
+}
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 504d0d1..4b8537e 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
@@ -66,6 +66,7 @@ import org.apache.karaf.features.RepositoryEvent;
 import org.apache.karaf.features.internal.download.DownloadManager;
 import org.apache.karaf.features.internal.download.DownloadManagers;
 import org.apache.karaf.features.internal.model.Features;
+import org.apache.karaf.features.internal.model.JacksonUtil;
 import org.apache.karaf.features.internal.model.JaxbUtil;
 import org.apache.karaf.features.internal.region.DigraphHelper;
 import org.apache.karaf.features.internal.service.BundleInstallSupport.FrameworkInfo;
@@ -355,7 +356,12 @@ public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCall
 
     @Override
     public Feature[] repositoryProvidedFeatures(URI uri) throws Exception {
-        Features features = JaxbUtil.unmarshal(uri.toURL().toExternalForm(), true);
+        Features features;
+        if (JacksonUtil.isJson(uri.toURL().toExternalForm())) {
+            features = JacksonUtil.unmarshal(uri.toURL().toExternalForm());
+        } else {
+            features = JaxbUtil.unmarshal(uri.toURL().toExternalForm(), true);
+        }
         Feature[] array = new Feature[features.getFeature().size()];
         return features.getFeature().toArray(array);
     }
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/RepositoryImpl.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/RepositoryImpl.java
index 713ca07..c8de71d 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/service/RepositoryImpl.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/RepositoryImpl.java
@@ -26,6 +26,7 @@ import java.util.Objects;
 import org.apache.karaf.features.Feature;
 import org.apache.karaf.features.Repository;
 import org.apache.karaf.features.internal.model.Features;
+import org.apache.karaf.features.internal.model.JacksonUtil;
 import org.apache.karaf.features.internal.model.JaxbUtil;
 
 /**
@@ -111,7 +112,11 @@ public class RepositoryImpl implements Repository {
     private void load(boolean validate) {
         if (features == null) {
             try (InputStream inputStream = new InterruptibleInputStream(uri.toURL().openStream())) {
-                features = JaxbUtil.unmarshal(uri.toASCIIString(), inputStream, validate);
+                if (JacksonUtil.isJson(uri.toASCIIString())) {
+                    features = JacksonUtil.unmarshal(uri.toASCIIString());
+                } else {
+                    features = JaxbUtil.unmarshal(uri.toASCIIString(), inputStream, validate);
+                }
             } catch (Exception e) {
                 throw new RuntimeException(e.getMessage() + " : " + uri, e);
             }
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 435a400..208cd14 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
@@ -438,6 +438,33 @@ public class FeaturesServiceTest extends TestBase {
         Assert.assertEquals(2, bundles.size());
     }
 
+    @Test
+    public void testJsonFeatureFile() throws Exception {
+        URI uri = createTempRepo("{" +
+                "\"name\": \"test\"," +
+                "\"feature\": [" +
+                "{ \"name\": \"f1\", " +
+                "\"bundle\": [" +
+                "{ \"location\": \"file:bundle1\" }," +
+                "{ \"location\": \"file:bundle2\" }" +
+                "]" +
+                "}" +
+                "]" +
+                "}");
+
+        BundleInstallSupport installSupport = EasyMock.niceMock(BundleInstallSupport.class);
+        EasyMock.replay(installSupport);
+        FeaturesServiceConfig cfg = new FeaturesServiceConfig();
+        FeaturesServiceImpl svc = new FeaturesServiceImpl(new Storage(), null, null, resolver, installSupport, null, cfg);
+        svc.addRepository(uri);
+        Feature[] features = svc.getFeatures("f1");
+        Assert.assertEquals(1, features.length);
+        Feature feature = features[0];
+        Assert.assertNotNull("No feature named f1 found", feature);
+        List<BundleInfo> bundles = feature.getBundles();
+        Assert.assertEquals(2, bundles.size());
+    }
+
     static class Storage extends StateStorage {
         @Override
         protected InputStream getInputStream() throws IOException {
diff --git a/features/core/src/test/java/org/apache/karaf/features/internal/model/JsonTest.java b/features/core/src/test/java/org/apache/karaf/features/internal/model/JsonTest.java
new file mode 100644
index 0000000..9a0c4f7
--- /dev/null
+++ b/features/core/src/test/java/org/apache/karaf/features/internal/model/JsonTest.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.features.internal.model;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class JsonTest {
+
+    @Test
+    public void testUnmarshall() throws Exception {
+        Assert.assertTrue(JacksonUtil.isJson(this.getClass().getResourceAsStream("/org/apache/karaf/features/repo.json")));
+
+        Features features = JacksonUtil.unmarshal(this.getClass().getResourceAsStream("/org/apache/karaf/features/repo.json"));
+
+        Assert.assertEquals("test", features.getName());
+
+        Assert.assertEquals("mvn:org.apache.karaf/test/1.0-SNAPSHOT", features.getRepository().get(0));
+
+        Assert.assertEquals("test-feature", features.getFeature().get(0).getName());
+        Assert.assertEquals("1.0.0.SNAPSHOT", features.getFeature().get(0).getVersion());
+        Assert.assertEquals("This is test feature", features.getFeature().get(0).getDescription());
+        Assert.assertEquals("obr", features.getFeature().get(0).getResolver());
+        Assert.assertEquals("auto", features.getFeature().get(0).getInstall());
+        Assert.assertEquals(30, features.getFeature().get(0).getStartLevel());
+        Assert.assertEquals(false, features.getFeature().get(0).isHidden());
+
+        Assert.assertEquals("inner-feature", features.getFeature().get(0).getFeature().get(0).getName());
+        Assert.assertEquals("[1,2)", features.getFeature().get(0).getFeature().get(0).getVersion());
+
+        Assert.assertEquals("mvn:org.apache.karaf/test-bundle/1.0-SNAPSHOT", features.getFeature().get(0).getBundle().get(0).getLocation());
+        Assert.assertEquals(40, features.getFeature().get(0).getBundle().get(0).getStartLevel());
+        Assert.assertEquals(true, features.getFeature().get(0).getBundle().get(0).isDependency());
+        Assert.assertEquals(true, features.getFeature().get(0).getBundle().get(0).isStart());
+
+        Assert.assertEquals("test-config", features.getFeature().get(0).getConfig().get(0).getName());
+        Assert.assertEquals(false, features.getFeature().get(0).getConfig().get(0).isAppend());
+        Assert.assertEquals(false, features.getFeature().get(0).getConfig().get(0).isExternal());
+        Assert.assertEquals(false, features.getFeature().get(0).getConfig().get(0).isOverride());
+        Assert.assertEquals("foo=bar", features.getFeature().get(0).getConfig().get(0).getValue());
+
+        Assert.assertEquals("test-configfile", features.getFeature().get(0).getConfigfile().get(0).getFinalname());
+        Assert.assertEquals(false, features.getFeature().get(0).getConfigfile().get(0).isOverride());
+        Assert.assertEquals("mvn:org.apache.karaf/test-configfile/1.0-SNAPSHOT", features.getFeature().get(0).getConfigfile().get(0).getLocation());
+    }
+
+}
diff --git a/features/core/src/test/resources/org/apache/karaf/features/repo.json b/features/core/src/test/resources/org/apache/karaf/features/repo.json
new file mode 100644
index 0000000..8f099f9
--- /dev/null
+++ b/features/core/src/test/resources/org/apache/karaf/features/repo.json
@@ -0,0 +1,47 @@
+{
+  "name": "test",
+  "repository": [
+    "mvn:org.apache.karaf/test/1.0-SNAPSHOT"
+  ],
+  "feature": [
+    {
+      "name": "test-feature",
+      "version": "1.0-SNAPSHOT",
+      "description": "This is test feature",
+      "resolver": "obr",
+      "install": "auto",
+      "startLevel": 30,
+      "hidden": false,
+      "feature": [
+        {
+          "name": "inner-feature",
+          "version": "[1,2)"
+        }
+      ],
+      "bundle": [
+        {
+          "location": "mvn:org.apache.karaf/test-bundle/1.0-SNAPSHOT",
+          "startLevel": 40,
+          "dependency": true,
+          "start": true
+        }
+      ],
+      "config": [
+        {
+          "name": "test-config",
+          "append": false,
+          "external": false,
+          "override": false,
+          "value": "foo=bar"
+        }
+      ],
+      "configfile": [
+        {
+          "finalname": "test-configfile",
+          "override": false,
+          "location": "mvn:org.apache.karaf/test-configfile/1.0-SNAPSHOT"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/manual/src/main/asciidoc/user-guide/provisioning.adoc b/manual/src/main/asciidoc/user-guide/provisioning.adoc
index 217bc14..8008d83 100644
--- a/manual/src/main/asciidoc/user-guide/provisioning.adoc
+++ b/manual/src/main/asciidoc/user-guide/provisioning.adoc
@@ -79,15 +79,15 @@ A feature has a complete lifecycle: install, start, stop, update, uninstall.
 
 ==== Features repositories
 
-The features are described in a features XML descriptor. This XML file contains the description of a set of features.
+The features are described in a features XML or JSON descriptor. This file contains the description of a set of features.
 
-A features XML descriptor is named a "features repository". Before being able to install a feature, you have to register
+A features descriptor is named a "features repository". Before being able to install a feature, you have to register
 the features repository that provides the feature (using `feature:repo-add` command or FeatureMBean as described later).
 
 For instance, the following XML file (or "features repository") describes the `feature1` and `feature2` features:
 
 ----
-<features xmlns="http://karaf.apache.org/xmlns/features/v1.3.0">
+<features name="my-features-repo" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
   <feature name="feature1" version="1.0.0">
     <bundle>...</bundle>
     <bundle>...</bundle>
@@ -99,6 +99,34 @@ For instance, the following XML file (or "features repository") describes the `f
 </features>
 ----
 
+Here's the same descriptor in JSON format:
+
+----
+{
+ "name": "my-features-repo",
+ "feature": [
+    {
+        "name": "feature1",
+        "version": "1.0.0",
+        "bundle": [
+           { "location": "..." },
+           { "location": "..."
+        ]
+    },
+    {
+        "name": "feature2",
+        "version": "1.1.0",
+        "feature": [
+            { "name": "feature1" }
+        ]
+        "bundle": [
+            { "location": "..." }
+        ]
+    }
+ ]
+}
+----
+
 We can note that the features XML has a schema. Take a look on [Features XML Schema section|provisioning-schema] of the user guide
 for details.
 The `feature1` feature is available in version `1.0.0`, and contains two bundles. The `<bundle/>` element contains a URL
@@ -180,6 +208,27 @@ This value can be "overridden" by the `start-level` attribute of the `<bundle/>`
   </feature>
 ----
 
+----
+{
+    "feature": [
+        {
+            "name": "my-project",
+            "version": "1.0.0"
+            "bundle": [
+                {
+                    "startLevel": 80,
+                    "location": "mvn:com.mycompany.myproject/myproject-dao"
+                },
+                {
+                    "startLeve": 85,
+                    "location": "mvn:com.mycompany.myproject/myproject-service"
+                }
+            ]
+        }
+    ]
+}
+----
+
 The start-level attribute insures that the `myproject-dao` bundle is started before the bundles that use it.
 
 Instead of using start-level, a better solution is to simply let the OSGi framework know what your dependencies are by
@@ -201,12 +250,48 @@ To do so, a feature can specify the `start` attribute to false in the `<bundle/>
   </feature>
 ----
 
+----
+{
+    "feature": [
+        {
+            "name": "my-project",
+            "version": "1.0.0"
+            "bundle": [
+                {
+                    "startLevel": 80,
+                    "start": false,
+                    "location": "mvn:com.mycompany.myproject/myproject-dao"
+                },
+                {
+                    "startLeve": 85,
+                    "start": false,
+                    "location": "mvn:com.mycompany.myproject/myproject-service"
+                }
+            ]
+        }
+    ]
+}
+----
+
 ===== Dependency
 
-A bundle can be flagged as being a dependency, using the `dependency` attribute set to true on the `<bundle/>` element.
+A bundle can be flagged as being a dependency, using the `dependency` attribute set to true on the `bundle` element.
 
 This information can be used by resolvers to compute the full list of bundles to be installed.
 
+----
+<bundle dependency="true">...</bundle>
+----
+
+----
+{
+    "dependency": true,
+    "location": "..."
+}
+----
+
+The dependency flag means that the bundle will be installed only if there's not other bundle providing the same capability.
+
 ==== Dependent features
 
 A feature can depend on a set of other features:
@@ -219,6 +304,32 @@ A feature can depend on a set of other features:
   </feature>
 ----
 
+----
+{
+    "feature": [
+        {
+            "name": "my-project",
+            "version": "1.0.0",
+            "feature": [
+                { "name": "other" }
+            ],
+            "bundle": [
+                {
+                    "startLevel": 80,
+                    "start": false,
+                    "location": "mvn:com.mycompany.myproject/myproject-dao"
+                },
+                {
+                    "startLevel": 85,
+                    "start": false,
+                    "location": "mvn:com.mycompany.myproject/myproject-service"
+                }
+            ]
+        }
+    ]
+}
+----
+
 When the `my-project` feature will be installed, the `other` feature will be automatically installed as well.
 
 It's possible to define a version range for a dependent feature:
@@ -230,6 +341,22 @@ It's possible to define a version range for a dependent feature:
 </feature>
 ----
 
+----
+{
+    "feature": [
+        {
+            "name": "spring-dm",
+            "feature": [
+                {
+                    "name": "spring",
+                    "version": "[2.5.6,4)"
+                }
+            ]
+        }
+    ]
+}
+----
+
 The feature with the highest version available in the range will be installed.
 
 If a single version is specified, the range will be considered open-ended.
@@ -242,32 +369,75 @@ To specify an exact version, use a closed range such as `[3.1,3.1]`.
 
 A prerequisite feature is a special kind of dependency. If you add the `prerequisite` attribute to dependant feature tag then it will force installation and also activation of bundles in the dependant feature before the installation of the actual feature. This may be handy in the case that bundles enlisted in a given feature are not using pre installed URLs such as `wrap` or `war`.
 
+----
+<feature name="foo" version="1.0.0">
+    <feature prerequisite="true">bar</feature>
+    ...
+</feature>
+----
+
+----
+{
+    "name": "foo",
+    "version": "1.0.0",
+    "feature": [
+        {
+            "prerequisite": true,
+            "name": "bar"
+        }
+    ]
+}
+----
+
 ==== Feature configurations
 
-The `<config/>` element in a feature XML allows a feature to create and/or populate a configuration (identified by a configuration PID).
+The `<config/>` element in a feature XML (or "config" in feature JSON) allows a feature to create and/or populate a configuration (identified by a configuration PID).
 
 ----
+...
 <config name="com.foo.bar">
   myProperty = myValue
 </config>
+...
 ----
 
-The `name` attribute of the `<config/>` element corresponds to the configuration PID (see the [Configuration section|configuration] for details).
+----
+...
+"config": [
+    {
+        "name": "com.foo.bar",
+        "value": "myProperty=myValue"
+    }
+]
+....
+----
+
+The `name` attribute of the `config` element corresponds to the configuration PID (see the [Configuration section|configuration] for details).
 
 The installation of the feature will have the same effect as dropping a file named `com.foo.bar.cfg` in the `etc` folder.
 
-The content of the `<config/>` element is a set of properties, following the key=value standard.
+The content (value) of the `config` element is a set of properties, following the `key=value` standard.
 
 ==== Feature configuration files
 
-Instead of using the `<config/>` element, a feature can specify `<configfile/>` elements.
+Instead of using the `config` element, a feature can specify `configfile` elements.
 
 ----
 <configfile finalname="/etc/myfile.cfg" override="false">URL</configfile>
 ----
 
-Instead of directly manipulating the Apache Karaf configuration layer (as when using the `<config/>` element), the
-`<configfile/>` element takes directly a file specified by a URL, and copy the file in the location specified by the
+----
+  "configfile": [
+    {
+        "finalname": "/etc/myfile.cfg",
+        "override": false,
+        "location": "URL"
+    }
+  ]
+----
+
+Instead of directly manipulating the Apache Karaf configuration layer (as when using the `config` element), the
+`configfile` element takes directly a file specified by a URL, and copy the file in the location specified by the
 `finalname` attribute.
 
 If not specified, the location is relative from the `KARAF_BASE` variable. It's also possible to use variable like
@@ -279,6 +449,16 @@ For instance:
 <configfile finalname="${karaf.etc}/myfile.cfg" override="false">URL</configfile>
 ----
 
+----
+  "configfile": [
+    {
+        "finalname": "${karaf.etc}/myfile.cfg",
+        "override": false,
+        "location": "URL"
+    }
+  ]
+----
+
 If the file is already present at the desired location it is kept and the deployment of the configuration file is skipped,
 as a already existing file might contain customization. This behaviour can be overriden by `override` set to true.
 
@@ -295,6 +475,10 @@ For instance, a feature can contain:
 <requirement>osgi.ee;filter:=&quot;(&amp;(osgi.ee=JavaSE)(!(version&gt;=1.8)))&quot;</requirement>
 ----
 
+----
+"requirement": "osgi.ee;filter="(&(osgi.ee=JavaSE)(!(version>1.8)))"
+----
+
 The requirement specifies that the feature will work by only if the JDK version is not 1.8 (so basically 1.7).
 
 The features resolver is also able to refresh the bundles when an optional dependency is satisfied, rewiring the optional import.
diff --git a/profile/src/main/java/org/apache/karaf/profile/assembly/Builder.java b/profile/src/main/java/org/apache/karaf/profile/assembly/Builder.java
index 719e211..c567084 100644
--- a/profile/src/main/java/org/apache/karaf/profile/assembly/Builder.java
+++ b/profile/src/main/java/org/apache/karaf/profile/assembly/Builder.java
@@ -74,13 +74,7 @@ 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.impl.DownloadManagerHelper;
-import org.apache.karaf.features.internal.model.Bundle;
-import org.apache.karaf.features.internal.model.Conditional;
-import org.apache.karaf.features.internal.model.ConfigFile;
-import org.apache.karaf.features.internal.model.Dependency;
-import org.apache.karaf.features.internal.model.Feature;
-import org.apache.karaf.features.internal.model.Features;
-import org.apache.karaf.features.internal.model.JaxbUtil;
+import org.apache.karaf.features.internal.model.*;
 import org.apache.karaf.features.internal.model.processing.FeaturesProcessing;
 import org.apache.karaf.features.internal.service.Blacklist;
 import org.apache.karaf.features.internal.service.Deployer;
@@ -2060,7 +2054,12 @@ public class Builder {
                                 }
                             }
                             try (InputStream is = provider.open()) {
-                                Features featuresModel = JaxbUtil.unmarshal(url, is, false);
+                                Features featuresModel;
+                                if (JacksonUtil.isJson(url)) {
+                                    featuresModel = JacksonUtil.unmarshal(url);
+                                } else {
+                                    featuresModel = JaxbUtil.unmarshal(url, is, false);
+                                }
                                 // always process according to processor configuration
                                 featuresModel.setBlacklisted(processor.isRepositoryBlacklisted(url));
                                 processor.process(featuresModel);
diff --git a/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/KarMojo.java b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/KarMojo.java
index d252518..ad29b90 100644
--- a/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/KarMojo.java
+++ b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/KarMojo.java
@@ -32,6 +32,7 @@ import org.apache.karaf.features.Conditional;
 import org.apache.karaf.features.ConfigFileInfo;
 import org.apache.karaf.features.internal.model.Feature;
 import org.apache.karaf.features.internal.model.Features;
+import org.apache.karaf.features.internal.model.JacksonUtil;
 import org.apache.karaf.features.internal.model.JaxbUtil;
 import org.apache.karaf.tooling.utils.MavenUtil;
 import org.apache.karaf.tooling.utils.MojoSupport;
@@ -203,7 +204,12 @@ public class KarMojo extends MojoSupport {
     private List<Artifact> readResources(File featuresFile) throws MojoExecutionException {
         List<Artifact> resources = new ArrayList<>();
         try {
-            Features features = JaxbUtil.unmarshal(featuresFile.toURI().toASCIIString(), false);
+            Features features;
+            if (JacksonUtil.isJson(featuresFile.toURI().toASCIIString())) {
+                features = JacksonUtil.unmarshal(featuresFile.toURI().toASCIIString());
+            } else {
+                features = JaxbUtil.unmarshal(featuresFile.toURI().toASCIIString(), false);
+            }
             for (Feature feature : features.getFeature()) {
                 for (BundleInfo bundle : feature.getBundles()) {
                     if (ignoreDependencyFlag || !bundle.isDependency()) {
diff --git a/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/VerifyMojo.java b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/VerifyMojo.java
index dfbd2fa..8c3d3d8 100644
--- a/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/VerifyMojo.java
+++ b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/VerifyMojo.java
@@ -70,11 +70,7 @@ import org.apache.karaf.features.internal.download.DownloadCallback;
 import org.apache.karaf.features.internal.download.DownloadManager;
 import org.apache.karaf.features.internal.download.Downloader;
 import org.apache.karaf.features.internal.download.StreamProvider;
-import org.apache.karaf.features.internal.model.Conditional;
-import org.apache.karaf.features.internal.model.ConfigFile;
-import org.apache.karaf.features.internal.model.Feature;
-import org.apache.karaf.features.internal.model.Features;
-import org.apache.karaf.features.internal.model.JaxbUtil;
+import org.apache.karaf.features.internal.model.*;
 import org.apache.karaf.features.internal.resolver.ResourceUtils;
 import org.apache.karaf.features.internal.service.Deployer;
 import org.apache.karaf.features.internal.service.FeaturesProcessorImpl;
@@ -615,7 +611,12 @@ public class VerifyMojo extends MojoSupport {
                             }
                         }
                         try (InputStream is = provider.open()) {
-                            Features featuresModel = JaxbUtil.unmarshal(provider.getUrl(), is, false);
+                            Features featuresModel;
+                            if (JacksonUtil.isJson(provider.getUrl())) {
+                                featuresModel = JacksonUtil.unmarshal(provider.getUrl());
+                            } else {
+                                featuresModel = JaxbUtil.unmarshal(provider.getUrl(), is, false);
+                            }
                             processor.process(featuresModel);
                             synchronized (loaded) {
                                 loaded.put(provider.getUrl(), featuresModel);
diff --git a/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/features/AbstractFeatureMojo.java b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/features/AbstractFeatureMojo.java
index 7c29fe3..24cb990 100644
--- a/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/features/AbstractFeatureMojo.java
+++ b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/features/AbstractFeatureMojo.java
@@ -29,12 +29,7 @@ import org.apache.felix.utils.version.VersionRange;
 import org.apache.felix.utils.version.VersionTable;
 import org.apache.karaf.features.BundleInfo;
 import org.apache.karaf.features.Conditional;
-import org.apache.karaf.features.internal.model.Bundle;
-import org.apache.karaf.features.internal.model.ConfigFile;
-import org.apache.karaf.features.internal.model.Dependency;
-import org.apache.karaf.features.internal.model.Feature;
-import org.apache.karaf.features.internal.model.Features;
-import org.apache.karaf.features.internal.model.JaxbUtil;
+import org.apache.karaf.features.internal.model.*;
 import org.apache.karaf.tooling.utils.MojoSupport;
 import org.apache.maven.artifact.Artifact;
 import org.apache.maven.artifact.repository.ArtifactRepository;
@@ -83,6 +78,9 @@ public abstract class AbstractFeatureMojo extends MojoSupport {
     @Parameter
     private int defaultStartLevel = 80;
 
+    @Parameter(defaultValue = "false")
+    protected boolean useJson;
+
     /**
      * Internal counter for garbage collection
      */
@@ -125,7 +123,17 @@ public abstract class AbstractFeatureMojo extends MojoSupport {
         String uriToUse = descriptor != null
                 ? descriptor.getFile().toURI().toString()
                 : translateFromMaven(uri);
-        Features repo = JaxbUtil.unmarshal(uriToUse, true);
+        Features repo = null;
+        if (JacksonUtil.isJson(uriToUse)) {
+            try {
+                repo = JacksonUtil.unmarshal(uriToUse);
+            } catch (Exception e) {
+                getLog().error("Error unmarshalling features json", e);
+                return;
+            }
+        } else {
+            repo = JaxbUtil.unmarshal(uriToUse, true);
+        }
         for (Feature f : repo.getFeature()) {
             featuresMap.put(f.getId(), f);
         }
diff --git a/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/features/ExportFeatureMetaDataMojo.java b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/features/ExportFeatureMetaDataMojo.java
index e421d98..17619d6 100644
--- a/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/features/ExportFeatureMetaDataMojo.java
+++ b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/features/ExportFeatureMetaDataMojo.java
@@ -30,10 +30,7 @@ import java.util.jar.Attributes;
 import java.util.jar.JarInputStream;
 import java.util.jar.Manifest;
 
-import org.apache.karaf.features.internal.model.Bundle;
-import org.apache.karaf.features.internal.model.Feature;
-import org.apache.karaf.features.internal.model.Features;
-import org.apache.karaf.features.internal.model.JaxbUtil;
+import org.apache.karaf.features.internal.model.*;
 import org.apache.maven.artifact.Artifact;
 import org.apache.maven.plugin.MojoExecutionException;
 import org.apache.maven.plugin.MojoFailureException;
@@ -80,7 +77,11 @@ public class ExportFeatureMetaDataMojo extends AbstractFeatureMojo {
             Features features = new Features();
             features.getFeature().addAll(featuresSet);
             try (OutputStream os = new FileOutputStream(metaDataFile)) {
-                JaxbUtil.marshal(features, os);
+                if (useJson) {
+                    JacksonUtil.marshal(features, os);
+                } else {
+                    JaxbUtil.marshal(features, os);
+                }
             }
         } catch (Exception e) {
             throw new RuntimeException("Error writing feature meta data to " + metaDataFile + ": " + e.getMessage(), e);
diff --git a/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/features/GenerateDescriptorMojo.java b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/features/GenerateDescriptorMojo.java
index d40483b..553ce91 100644
--- a/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/features/GenerateDescriptorMojo.java
+++ b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/features/GenerateDescriptorMojo.java
@@ -36,13 +36,7 @@ import javax.xml.bind.JAXBException;
 import javax.xml.parsers.ParserConfigurationException;
 import javax.xml.stream.XMLStreamException;
 
-import org.apache.karaf.features.internal.model.Bundle;
-import org.apache.karaf.features.internal.model.ConfigFile;
-import org.apache.karaf.features.internal.model.Dependency;
-import org.apache.karaf.features.internal.model.Feature;
-import org.apache.karaf.features.internal.model.Features;
-import org.apache.karaf.features.internal.model.JaxbUtil;
-import org.apache.karaf.features.internal.model.ObjectFactory;
+import org.apache.karaf.features.internal.model.*;
 import org.apache.karaf.tooling.utils.DependencyHelper;
 import org.apache.karaf.tooling.utils.DependencyHelperFactory;
 import org.apache.karaf.tooling.utils.LocalDependency;
@@ -293,6 +287,9 @@ public class GenerateDescriptorMojo extends MojoSupport {
     @Parameter
     private List<String> dependencyFeatures = new ArrayList<>();
 
+    @Parameter(defaultValue = "false")
+    private boolean useJson;
+
     // *************************************************
     // READ-ONLY MAVEN PLUGIN PARAMETERS
     // *************************************************
@@ -582,8 +579,15 @@ public class GenerateDescriptorMojo extends MojoSupport {
                 }
             }
         }
-
-        JaxbUtil.marshal(features, out);
+        if (useJson) {
+            try {
+                JacksonUtil.marshal(features, out);
+            } catch (Exception e) {
+                throw new MojoExecutionException("Can't create features json", e);
+            }
+        } else {
+            JaxbUtil.marshal(features, out);
+        }
         try {
             checkChanges(features, objectFactory);
         } catch (Exception e) {
@@ -722,7 +726,11 @@ public class GenerateDescriptorMojo extends MojoSupport {
     }
 
     static Features readFeaturesFile(File featuresFile) throws XMLStreamException, JAXBException, IOException {
-        return JaxbUtil.unmarshal(featuresFile.toURI().toASCIIString(), false);
+        if (JacksonUtil.isJson(featuresFile.toURI().toASCIIString())) {
+            return JacksonUtil.unmarshal(featuresFile.toURI().toASCIIString());
+        } else {
+            return JaxbUtil.unmarshal(featuresFile.toURI().toASCIIString(), false);
+        }
     }
 
     @Override
@@ -876,14 +884,14 @@ public class GenerateDescriptorMojo extends MojoSupport {
                 if (!addedBundles.isEmpty() || !removedBundles.isEmpty() || !addedDependencys.isEmpty() || !removedDependencys.isEmpty()) {
                     saveDependencyChanges(addedBundles, removedBundles, addedDependencys, removedDependencys, objectFactory);
                     if (overwriteChangedDependencies) {
-                        writeDependencies(features, dependencyCache);
+                        writeDependencies(features, dependencyCache, useJson);
                     }
                 } else {
                     getLog().info(saveTreeListing());
                 }
 
             } else {
-                writeDependencies(features, dependencyCache);
+                writeDependencies(features, dependencyCache, useJson);
             }
         }
     }
@@ -892,11 +900,11 @@ public class GenerateDescriptorMojo extends MojoSupport {
             throws Exception {
         File addedFile = new File(filteredDependencyCache.getParentFile(), "dependencies.added.xml");
         Features added = toFeatures(addedBundles, addedDependencys, objectFactory);
-        writeDependencies(added, addedFile);
+        writeDependencies(added, addedFile, useJson);
 
         File removedFile = new File(filteredDependencyCache.getParentFile(), "dependencies.removed.xml");
         Features removed = toFeatures(removedBundles, removedDependencys, objectFactory);
-        writeDependencies(removed, removedFile);
+        writeDependencies(removed, removedFile, useJson);
 
         StringWriter out = new StringWriter();
         out.write(saveTreeListing());
@@ -905,13 +913,21 @@ public class GenerateDescriptorMojo extends MojoSupport {
         if (!addedBundles.isEmpty() || !addedDependencys.isEmpty()) {
             out.write("\tAdded dependencies are saved here: " + addedFile.getAbsolutePath() + "\n");
             if (logDependencyChanges) {
-                JaxbUtil.marshal(added, out);
+                if (useJson) {
+                    JacksonUtil.marshal(added, out);
+                } else {
+                    JaxbUtil.marshal(added, out);
+                }
             }
         }
         if (!removedBundles.isEmpty() || !removedDependencys.isEmpty()) {
             out.write("\tRemoved dependencies are saved here: " + removedFile.getAbsolutePath() + "\n");
             if (logDependencyChanges) {
-                JaxbUtil.marshal(removed, out);
+                if (useJson) {
+                    JacksonUtil.marshal(removed, out);
+                } else {
+                    JaxbUtil.marshal(removed, out);
+                }
             }
         }
         out.write("Delete " + dependencyCache.getAbsolutePath()
@@ -934,13 +950,17 @@ public class GenerateDescriptorMojo extends MojoSupport {
     }
 
 
-    private static void writeDependencies(Features features, File file) throws JAXBException, IOException {
+    private static void writeDependencies(Features features, File file, boolean useJson) throws JAXBException, IOException {
         file.getParentFile().mkdirs();
         if (!file.getParentFile().exists() || !file.getParentFile().isDirectory()) {
             throw new IOException("Cannot create directory at " + file.getParent());
         }
         try (OutputStream out = new FileOutputStream(file)) {
-            JaxbUtil.marshal(features, out);
+            if (useJson) {
+                JacksonUtil.marshal(features, out);
+            } else {
+                JaxbUtil.marshal(features, out);
+            }
         }
     }