You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by da...@apache.org on 2018/03/15 07:44:17 UTC

[sling-whiteboard] 03/04: Refactor feature file generator to use JsonWriter

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

davidb pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-whiteboard.git

commit 6b51e9f49b25848214c1717dfa8ac7f729b7d986
Author: David Bosschaert <da...@gmail.com>
AuthorDate: Mon Mar 12 17:29:14 2018 +0000

    Refactor feature file generator to use JsonWriter
    
    This is to support 'pretty' formatting of output files, rather than a
    single line that contains the whole file.
---
 .../support/json/ApplicationJSONWriter.java        |  36 +++--
 .../support/json/ConfigurationJSONWriter.java      |  23 +++-
 .../feature/support/json/FeatureJSONWriter.java    | 152 +++++++++++----------
 .../sling/feature/support/json/JSONWriterBase.java | 133 +++++++++---------
 .../support/json/FeatureJSONWriterTest.java        |  15 +-
 .../src/test/resources/features/test.json          |   1 +
 6 files changed, 198 insertions(+), 162 deletions(-)

diff --git a/featuremodel/feature-support/src/main/java/org/apache/sling/feature/support/json/ApplicationJSONWriter.java b/featuremodel/feature-support/src/main/java/org/apache/sling/feature/support/json/ApplicationJSONWriter.java
index 9e8b43f..32835ab 100644
--- a/featuremodel/feature-support/src/main/java/org/apache/sling/feature/support/json/ApplicationJSONWriter.java
+++ b/featuremodel/feature-support/src/main/java/org/apache/sling/feature/support/json/ApplicationJSONWriter.java
@@ -21,11 +21,16 @@ import org.apache.sling.feature.ArtifactId;
 import org.apache.sling.feature.Configuration;
 import org.apache.sling.feature.Configurations;
 
-import javax.json.Json;
-import javax.json.stream.JsonGenerator;
 import java.io.IOException;
 import java.io.Writer;
+import java.util.Collections;
 
+import javax.json.Json;
+import javax.json.JsonArrayBuilder;
+import javax.json.JsonObjectBuilder;
+import javax.json.JsonWriter;
+import javax.json.JsonWriterFactory;
+import javax.json.stream.JsonGenerator;
 
 /**
  * Simple JSON writer for an application
@@ -47,25 +52,25 @@ public class ApplicationJSONWriter extends JSONWriterBase {
 
    private void writeApp(final Writer writer, final Application app)
     throws IOException {
-        final JsonGenerator w = Json.createGenerator(writer);
-        w.writeStartObject();
+       JsonObjectBuilder ob = Json.createObjectBuilder();
 
         // framework
         if ( app.getFramework() != null ) {
-            w.write(JSONConstants.APP_FRAMEWORK, app.getFramework().toMvnId());
+            ob.add(JSONConstants.APP_FRAMEWORK, app.getFramework().toMvnId());
         }
 
         // features
         if ( !app.getFeatureIds().isEmpty() ) {
-            w.writeStartArray(JSONConstants.APP_FEATURES);
+            JsonArrayBuilder featuresArr = Json.createArrayBuilder();
+
             for(final ArtifactId id : app.getFeatureIds()) {
-                w.write(id.toMvnId());
+                featuresArr.add(id.toMvnId());
             }
-            w.writeEnd();
+            ob.add(JSONConstants.APP_FEATURES, featuresArr.build());
         }
 
         // bundles
-        writeBundles(w, app.getBundles(), app.getConfigurations());
+        writeBundles(ob, app.getBundles(), app.getConfigurations());
 
         // configurations
         final Configurations cfgs = new Configurations();
@@ -75,15 +80,18 @@ public class ApplicationJSONWriter extends JSONWriterBase {
                 cfgs.add(cfg);
             }
         }
-        writeConfigurations(w, cfgs);
+        writeConfigurations(ob, cfgs);
 
         // framework properties
-        writeFrameworkProperties(w, app.getFrameworkProperties());
+        writeFrameworkProperties(ob, app.getFrameworkProperties());
 
         // extensions
-        writeExtensions(w, app.getExtensions(), app.getConfigurations());
+        writeExtensions(ob, app.getExtensions(), app.getConfigurations());
 
-        w.writeEnd();
-        w.flush();
+        JsonWriterFactory writerFactory = Json.createWriterFactory(
+                Collections.singletonMap(JsonGenerator.PRETTY_PRINTING, true));
+        JsonWriter jw = writerFactory.createWriter(writer);
+        jw.writeObject(ob.build());
+        jw.close();
     }
 }
diff --git a/featuremodel/feature-support/src/main/java/org/apache/sling/feature/support/json/ConfigurationJSONWriter.java b/featuremodel/feature-support/src/main/java/org/apache/sling/feature/support/json/ConfigurationJSONWriter.java
index d72bad0..1024c51 100644
--- a/featuremodel/feature-support/src/main/java/org/apache/sling/feature/support/json/ConfigurationJSONWriter.java
+++ b/featuremodel/feature-support/src/main/java/org/apache/sling/feature/support/json/ConfigurationJSONWriter.java
@@ -18,10 +18,15 @@ package org.apache.sling.feature.support.json;
 
 import org.apache.sling.feature.Configurations;
 
-import javax.json.Json;
-import javax.json.stream.JsonGenerator;
 import java.io.IOException;
 import java.io.Writer;
+import java.util.Collections;
+
+import javax.json.Json;
+import javax.json.JsonObjectBuilder;
+import javax.json.JsonWriter;
+import javax.json.JsonWriterFactory;
+import javax.json.stream.JsonGenerator;
 
 
 /**
@@ -44,12 +49,16 @@ public class ConfigurationJSONWriter extends JSONWriterBase {
 
     private void writeConfigurations(final Writer writer, final Configurations configs)
     throws IOException {
-        final JsonGenerator w = Json.createGenerator(writer);
-        w.writeStartObject();
+        JsonObjectBuilder ob = Json.createObjectBuilder();
 
-        writeConfigurationsMap(w, configs);
+        // TODO is this correct?
+        ob.add(JSONConstants.FEATURE_CONFIGURATIONS,
+                writeConfigurationsMap(configs));
 
-        w.writeEnd();
-        w.flush();
+        JsonWriterFactory writerFactory = Json.createWriterFactory(
+                Collections.singletonMap(JsonGenerator.PRETTY_PRINTING, true));
+        JsonWriter jw = writerFactory.createWriter(writer);
+        jw.writeObject(ob.build());
+        jw.close();
     }
 }
diff --git a/featuremodel/feature-support/src/main/java/org/apache/sling/feature/support/json/FeatureJSONWriter.java b/featuremodel/feature-support/src/main/java/org/apache/sling/feature/support/json/FeatureJSONWriter.java
index 3c64391..6d52400 100644
--- a/featuremodel/feature-support/src/main/java/org/apache/sling/feature/support/json/FeatureJSONWriter.java
+++ b/featuremodel/feature-support/src/main/java/org/apache/sling/feature/support/json/FeatureJSONWriter.java
@@ -16,24 +16,29 @@
  */
 package org.apache.sling.feature.support.json;
 
-import static org.apache.sling.feature.support.util.ManifestUtil.marshalAttribute;
-import static org.apache.sling.feature.support.util.ManifestUtil.marshalDirective;
+import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.Configuration;
+import org.apache.sling.feature.Configurations;
+import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.Include;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Requirement;
 
 import java.io.IOException;
 import java.io.Writer;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
 import javax.json.Json;
+import javax.json.JsonArrayBuilder;
+import javax.json.JsonObjectBuilder;
+import javax.json.JsonWriter;
+import javax.json.JsonWriterFactory;
 import javax.json.stream.JsonGenerator;
 
-import org.apache.sling.feature.ArtifactId;
-import org.apache.sling.feature.Configuration;
-import org.apache.sling.feature.Configurations;
-import org.apache.sling.feature.Feature;
-import org.apache.sling.feature.Include;
-import org.osgi.resource.Capability;
-import org.osgi.resource.Requirement;
+import static org.apache.sling.feature.support.util.ManifestUtil.marshalAttribute;
+import static org.apache.sling.feature.support.util.ManifestUtil.marshalDirective;
 
 /**
  * Simple JSON writer for a feature
@@ -53,130 +58,128 @@ public class FeatureJSONWriter extends JSONWriterBase {
         w.writeFeature(writer, feature);
     }
 
-    private void writeProperty(final JsonGenerator w, final String key, final String value) {
+    private void writeProperty(final JsonObjectBuilder ob, final String key, final String value) {
         if ( value != null ) {
-            w.write(key, value);
+            ob.add(key, value);
         }
     }
 
     private void writeFeature(final Writer writer, final Feature feature)
     throws IOException {
-        final JsonGenerator w = Json.createGenerator(writer);
-        w.writeStartObject();
-
-        w.write(JSONConstants.FEATURE_ID, feature.getId().toMvnId());
+        JsonObjectBuilder ob = Json.createObjectBuilder();
+        ob.add(JSONConstants.FEATURE_ID, feature.getId().toMvnId());
 
         // title, description, vendor, license
-        writeProperty(w, JSONConstants.FEATURE_TITLE, feature.getTitle());
-        writeProperty(w, JSONConstants.FEATURE_DESCRIPTION, feature.getDescription());
-        writeProperty(w, JSONConstants.FEATURE_VENDOR, feature.getVendor());
-        writeProperty(w, JSONConstants.FEATURE_LICENSE, feature.getLicense());
+        writeProperty(ob, JSONConstants.FEATURE_TITLE, feature.getTitle());
+        writeProperty(ob, JSONConstants.FEATURE_DESCRIPTION, feature.getDescription());
+        writeProperty(ob, JSONConstants.FEATURE_VENDOR, feature.getVendor());
+        writeProperty(ob, JSONConstants.FEATURE_LICENSE, feature.getLicense());
 
         // includes
         if ( !feature.getIncludes().isEmpty() ) {
-            w.writeStartArray(JSONConstants.FEATURE_INCLUDES);
-
+            JsonArrayBuilder incArray = Json.createArrayBuilder();
             for(final Include inc : feature.getIncludes()) {
                 if ( inc.getArtifactExtensionRemovals().isEmpty()
                      && inc.getBundleRemovals().isEmpty()
                      && inc.getConfigurationRemovals().isEmpty()
                      && inc.getFrameworkPropertiesRemovals().isEmpty() ) {
-                    w.write(inc.getId().toMvnId());
+                    incArray.add(inc.getId().toMvnId());
                 } else {
-                    w.writeStartObject();
-                    w.write(JSONConstants.ARTIFACT_ID, inc.getId().toMvnId());
-                    w.writeStartObject(JSONConstants.INCLUDE_REMOVALS);
+                    JsonObjectBuilder includeObj = Json.createObjectBuilder();
+                    includeObj.add(JSONConstants.ARTIFACT_ID, inc.getId().toMvnId());
+
+                    JsonObjectBuilder removalsObj = Json.createObjectBuilder();
                     if ( !inc.getArtifactExtensionRemovals().isEmpty()
                          || inc.getExtensionRemovals().isEmpty() ) {
-                        w.writeStartArray(JSONConstants.INCLUDE_EXTENSION_REMOVALS);
+                        JsonArrayBuilder extRemovals = Json.createArrayBuilder();
                         for(final String id : inc.getExtensionRemovals()) {
-                            w.write(id);
+                            extRemovals.add(id);
                         }
                         for(final Map.Entry<String, List<ArtifactId>> entry : inc.getArtifactExtensionRemovals().entrySet()) {
-                            w.writeStartObject(entry.getKey());
-                            w.writeStartArray();
+                            JsonArrayBuilder ab = Json.createArrayBuilder();
                             for(final ArtifactId id : entry.getValue()) {
-                                w.write(id.toMvnId());
+                                ab.add(id.toMvnId());
                             }
-                            w.writeEnd();
-                            w.writeEnd();
+                            extRemovals.add(Json.createObjectBuilder().add(entry.getKey(),
+                                    ab.build()).build());
                         }
-                        w.writeEnd();
+                        removalsObj.add(JSONConstants.INCLUDE_EXTENSION_REMOVALS, extRemovals.build());
                     }
                     if ( !inc.getConfigurationRemovals().isEmpty() ) {
-                        w.writeStartArray(JSONConstants.FEATURE_CONFIGURATIONS);
+                        JsonArrayBuilder cfgRemovals = Json.createArrayBuilder();
                         for(final String val : inc.getConfigurationRemovals()) {
-                            w.write(val);
+                            cfgRemovals.add(val);
                         }
-                        w.writeEnd();
+                        removalsObj.add(JSONConstants.FEATURE_CONFIGURATIONS, cfgRemovals.build());
                     }
                     if ( !inc.getBundleRemovals().isEmpty() ) {
-                        w.writeStartArray(JSONConstants.FEATURE_BUNDLES);
+                        JsonArrayBuilder bundleRemovals = Json.createArrayBuilder();
                         for(final ArtifactId val : inc.getBundleRemovals()) {
-                            w.write(val.toMvnId());
+                            bundleRemovals.add(val.toMvnId());
                         }
-                        w.writeEnd();
+                        removalsObj.add(JSONConstants.FEATURE_BUNDLES, bundleRemovals.build());
                     }
                     if ( !inc.getFrameworkPropertiesRemovals().isEmpty() ) {
-                        w.writeStartArray(JSONConstants.FEATURE_FRAMEWORK_PROPERTIES);
+                        JsonArrayBuilder propRemovals = Json.createArrayBuilder();
                         for(final String val : inc.getFrameworkPropertiesRemovals()) {
-                            w.write(val);
+                            propRemovals.add(val);
                         }
-                        w.writeEnd();
+                        removalsObj.add(JSONConstants.FEATURE_FRAMEWORK_PROPERTIES, propRemovals.build());
                     }
-                    w.writeEnd();
-                    w.writeEnd();
+                    includeObj.add(JSONConstants.INCLUDE_REMOVALS, removalsObj.build());
+
+                    incArray.add(includeObj.build());
                 }
             }
-            w.writeEnd();
+            ob.add(JSONConstants.FEATURE_INCLUDES, incArray.build());
         }
 
         // requirements
         if ( !feature.getRequirements().isEmpty() ) {
-            w.writeStartArray(JSONConstants.FEATURE_REQUIREMENTS);
+            JsonArrayBuilder requirements = Json.createArrayBuilder();
 
             for(final Requirement req : feature.getRequirements()) {
-                w.writeStartObject();
-                w.write(JSONConstants.REQCAP_NAMESPACE, req.getNamespace());
+                JsonObjectBuilder requirementObj = Json.createObjectBuilder();
+                requirementObj.add(JSONConstants.REQCAP_NAMESPACE, req.getNamespace());
                 if ( !req.getAttributes().isEmpty() ) {
-                    w.writeStartObject(JSONConstants.REQCAP_ATTRIBUTES);
-                    req.getAttributes().forEach((key, value) -> marshalAttribute(key, value, w::write));
-                    w.writeEnd();
+                    JsonObjectBuilder attrObj = Json.createObjectBuilder();
+                    req.getAttributes().forEach((key, value) -> marshalAttribute(key, value, attrObj::add));
+                    requirementObj.add(JSONConstants.REQCAP_ATTRIBUTES, attrObj.build());
                 }
                 if ( !req.getDirectives().isEmpty() ) {
-                    w.writeStartObject(JSONConstants.REQCAP_DIRECTIVES);
-                    req.getDirectives().forEach((key, value) -> marshalDirective(key, value, w::write));
-                    w.writeEnd();
+                    JsonObjectBuilder reqObj = Json.createObjectBuilder();
+                    req.getDirectives().forEach((key, value) -> marshalDirective(key, value, reqObj::add));
+                    requirementObj.add(JSONConstants.REQCAP_DIRECTIVES, reqObj.build());
                 }
-                w.writeEnd();
+                requirements.add(requirementObj.build());
             }
-            w.writeEnd();
+            ob.add(JSONConstants.FEATURE_REQUIREMENTS, requirements.build());
         }
 
         // capabilities
         if ( !feature.getCapabilities().isEmpty() ) {
-            w.writeStartArray(JSONConstants.FEATURE_CAPABILITIES);
+            JsonArrayBuilder capabilities = Json.createArrayBuilder();
 
             for(final Capability cap : feature.getCapabilities()) {
-                w.writeStartObject();
-                w.write(JSONConstants.REQCAP_NAMESPACE, cap.getNamespace());
+                JsonObjectBuilder capabilityObj = Json.createObjectBuilder();
+                capabilityObj.add(JSONConstants.REQCAP_NAMESPACE, cap.getNamespace());
                 if ( !cap.getAttributes().isEmpty() ) {
-                    w.writeStartObject(JSONConstants.REQCAP_ATTRIBUTES);
-                    cap.getAttributes().forEach((key, value) -> marshalAttribute(key, value, w::write));
-                    w.writeEnd();
+                    JsonObjectBuilder attrObj = Json.createObjectBuilder();
+                    cap.getAttributes().forEach((key, value) -> marshalAttribute(key, value, attrObj::add));
+                    capabilityObj.add(JSONConstants.REQCAP_ATTRIBUTES, attrObj.build());
                 }
                 if ( !cap.getDirectives().isEmpty() ) {
-                    w.writeStartObject(JSONConstants.REQCAP_DIRECTIVES);
-                    cap.getDirectives().forEach((key, value) -> marshalDirective(key, value, w::write));
-                    w.writeEnd();
+                    JsonObjectBuilder reqObj = Json.createObjectBuilder();
+                    cap.getDirectives().forEach((key, value) -> marshalDirective(key, value, reqObj::add));
+                    capabilityObj.add(JSONConstants.REQCAP_DIRECTIVES, reqObj.build());
                 }
-                w.writeEnd();
+                capabilities.add(capabilityObj.build());
             }
-            w.writeEnd();
+            ob.add(JSONConstants.FEATURE_CAPABILITIES, capabilities.build());
         }
 
         // bundles
-        writeBundles(w, feature.getBundles(), feature.getConfigurations());
+        writeBundles(ob, feature.getBundles(), feature.getConfigurations());
 
         // configurations
         final Configurations cfgs = new Configurations();
@@ -186,15 +189,18 @@ public class FeatureJSONWriter extends JSONWriterBase {
                 cfgs.add(cfg);
             }
         }
-        writeConfigurations(w, cfgs);
+        writeConfigurations(ob, cfgs);
 
         // framework properties
-        writeFrameworkProperties(w, feature.getFrameworkProperties());
+        writeFrameworkProperties(ob, feature.getFrameworkProperties());
 
         // extensions
-        writeExtensions(w, feature.getExtensions(), feature.getConfigurations());
+        writeExtensions(ob, feature.getExtensions(), feature.getConfigurations());
 
-        w.writeEnd();
-        w.flush();
+        JsonWriterFactory writerFactory = Json.createWriterFactory(
+                Collections.singletonMap(JsonGenerator.PRETTY_PRINTING, true));
+        JsonWriter jw = writerFactory.createWriter(writer);
+        jw.writeObject(ob.build());
+        jw.close();
     }
 }
diff --git a/featuremodel/feature-support/src/main/java/org/apache/sling/feature/support/json/JSONWriterBase.java b/featuremodel/feature-support/src/main/java/org/apache/sling/feature/support/json/JSONWriterBase.java
index ad6f1e0..f9a3aef 100644
--- a/featuremodel/feature-support/src/main/java/org/apache/sling/feature/support/json/JSONWriterBase.java
+++ b/featuremodel/feature-support/src/main/java/org/apache/sling/feature/support/json/JSONWriterBase.java
@@ -16,6 +16,14 @@
  */
 package org.apache.sling.feature.support.json;
 
+import org.apache.sling.feature.Artifact;
+import org.apache.sling.feature.Bundles;
+import org.apache.sling.feature.Configuration;
+import org.apache.sling.feature.Configurations;
+import org.apache.sling.feature.Extension;
+import org.apache.sling.feature.ExtensionType;
+import org.apache.sling.feature.KeyValueMap;
+
 import java.io.StringReader;
 import java.lang.reflect.Array;
 import java.util.Enumeration;
@@ -23,30 +31,22 @@ import java.util.List;
 import java.util.Map;
 
 import javax.json.Json;
+import javax.json.JsonArrayBuilder;
+import javax.json.JsonObject;
+import javax.json.JsonObjectBuilder;
 import javax.json.JsonStructure;
-import javax.json.stream.JsonGenerator;
-
-import org.apache.sling.feature.Artifact;
-import org.apache.sling.feature.Bundles;
-import org.apache.sling.feature.Configuration;
-import org.apache.sling.feature.Configurations;
-import org.apache.sling.feature.Extension;
-import org.apache.sling.feature.ExtensionType;
-import org.apache.sling.feature.KeyValueMap;
 
 
 /**
  * Common functionality for writing JSON
  */
 abstract class JSONWriterBase {
-
-
-    protected void writeBundles(final JsonGenerator w,
+    protected void writeBundles(final JsonObjectBuilder ob,
             final Bundles bundles,
             final Configurations allConfigs) {
         // bundles
         if ( !bundles.isEmpty() ) {
-            w.writeStartArray(JSONConstants.FEATURE_BUNDLES);
+            JsonArrayBuilder bundleArray = Json.createArrayBuilder();
 
             for(final Artifact artifact : bundles) {
                 final Configurations cfgs = new Configurations();
@@ -56,37 +56,45 @@ abstract class JSONWriterBase {
                         cfgs.add(cfg);
                     }
                 }
-                if ( artifact.getMetadata().isEmpty() && cfgs.isEmpty() ) {
-                    w.write(artifact.getId().toMvnId());
+                KeyValueMap md = artifact.getMetadata();
+                if ( md.isEmpty() && cfgs.isEmpty() ) {
+                    bundleArray.add(artifact.getId().toMvnId());
                 } else {
-                    w.writeStartObject();
-                    w.write(JSONConstants.ARTIFACT_ID, artifact.getId().toMvnId());
+                    JsonObjectBuilder bundleObj = Json.createObjectBuilder();
+                    bundleObj.add(JSONConstants.ARTIFACT_ID, artifact.getId().toMvnId());
+
+                    if (md.get("start-level") == null) {
+                        String so = md.get("start-order");
+                        if (so != null) {
+                            md.put("start-level", so);
+                        }
+                    }
+
+                    Object runmodes = md.remove("runmodes");
+                    if (runmodes instanceof String) {
+                        md.put("run-modes", runmodes);
+                    }
 
-                    for(final Map.Entry<String, String> me : artifact.getMetadata()) {
-                        w.write(me.getKey(), me.getValue());
+                    for(final Map.Entry<String, String> me : md) {
+                        bundleObj.add(me.getKey(), me.getValue());
                     }
 
-                    writeConfigurations(w, cfgs);
-                    w.writeEnd();
+                    bundleArray.add(bundleObj.build());
                 }
             }
-
-            w.writeEnd();
+            ob.add(JSONConstants.FEATURE_BUNDLES, bundleArray.build());
         }
     }
 
     /**
      * Write the list of configurations into a "configurations" element
-     * @param w The json generator
+     * @param ob The json generator
      * @param cfgs The list of configurations
      */
-    protected void writeConfigurations(final JsonGenerator w, final Configurations cfgs) {
+    protected void writeConfigurations(final JsonObjectBuilder ob, final Configurations cfgs) {
         if ( !cfgs.isEmpty() ) {
-            w.writeStartObject(JSONConstants.FEATURE_CONFIGURATIONS);
-
-            writeConfigurationsMap(w, cfgs);
-
-            w.writeEnd();
+            ob.add(JSONConstants.FEATURE_CONFIGURATIONS,
+                    writeConfigurationsMap(cfgs));
         }
     }
 
@@ -94,8 +102,10 @@ abstract class JSONWriterBase {
      * Write the list of configurations into a "configurations" element
      * @param w The json generator
      * @param cfgs The list of configurations
+     * @return
      */
-    protected void writeConfigurationsMap(final JsonGenerator w, final Configurations cfgs) {
+    protected JsonObject writeConfigurationsMap(final Configurations cfgs) {
+        JsonObjectBuilder configObj = Json.createObjectBuilder();
         for(final Configuration cfg : cfgs) {
             final String key;
             if ( cfg.isFactoryConfiguration() ) {
@@ -103,7 +113,7 @@ abstract class JSONWriterBase {
             } else {
                 key = cfg.getPid();
             }
-            w.writeStartObject(key);
+            JsonObjectBuilder cfgValObj = Json.createObjectBuilder();
 
             final Enumeration<String> e = cfg.getProperties().keys();
             while ( e.hasMoreElements() ) {
@@ -137,58 +147,57 @@ abstract class JSONWriterBase {
                 }
 
                 if ( val.getClass().isArray() ) {
-                    w.writeStartArray(name);
+                    JsonArrayBuilder ab = Json.createArrayBuilder();
                     for(int i=0; i<Array.getLength(val);i++ ) {
                         final Object obj = Array.get(val, i);
                         if ( typePostFix == null ) {
                             if ( obj instanceof String ) {
-                                w.write((String)obj);
+                                ab.add((String)obj);
                             } else if ( obj instanceof Boolean ) {
-                                w.write((Boolean)obj);
+                                ab.add((Boolean)obj);
                             } else if ( obj instanceof Long ) {
-                                w.write((Long)obj);
+                                ab.add((Long)obj);
                             } else if ( obj instanceof Double ) {
-                                w.write((Double)obj);
+                                ab.add((Double)obj);
                             }
                         } else {
-                            w.write(obj.toString());
+                            ab.add(obj.toString());
                         }
                     }
-                    w.writeEnd();
+                    cfgValObj.add(name, ab.build());
                 } else {
                     if ( typePostFix == null ) {
                         if ( val instanceof String ) {
-                            w.write(name, (String)val);
+                            cfgValObj.add(name, (String)val);
                         } else if ( val instanceof Boolean ) {
-                            w.write(name, (Boolean)val);
+                            cfgValObj.add(name, (Boolean)val);
                         } else if ( val instanceof Long ) {
-                            w.write(name, (Long)val);
+                            cfgValObj.add(name, (Long)val);
                         } else if ( val instanceof Double ) {
-                            w.write(name, (Double)val);
+                            cfgValObj.add(name, (Double)val);
                         }
                     } else {
-                        w.write(name + typePostFix, val.toString());
+                        cfgValObj.add(name + typePostFix, val.toString());
                     }
                 }
             }
-
-            w.writeEnd();
+            configObj.add(key, cfgValObj.build());
         }
+        return configObj.build();
     }
 
-    protected void writeFrameworkProperties(final JsonGenerator w, final KeyValueMap props) {
+    protected void writeFrameworkProperties(final JsonObjectBuilder ob, final KeyValueMap props) {
         // framework properties
         if ( !props.isEmpty() ) {
-            w.writeStartObject(JSONConstants.FEATURE_FRAMEWORK_PROPERTIES);
-
+            JsonObjectBuilder propsObj = Json.createObjectBuilder();
             for(final Map.Entry<String, String> entry : props) {
-                w.write(entry.getKey(), entry.getValue());
+                propsObj.add(entry.getKey(), entry.getValue());
             }
-            w.writeEnd();
+            ob.add(JSONConstants.FEATURE_FRAMEWORK_PROPERTIES, propsObj.build());
         }
     }
 
-    protected void writeExtensions(final JsonGenerator w,
+    protected void writeExtensions(final JsonObjectBuilder ob,
             final List<Extension> extensions,
             final Configurations allConfigs) {
         for(final Extension ext : extensions) {
@@ -198,11 +207,11 @@ abstract class JSONWriterBase {
                 try ( final StringReader reader = new StringReader(ext.getJSON()) ) {
                     struct = Json.createReader(reader).read();
                 }
-                w.write(key, struct);
+                ob.add(key, struct);
             } else if ( ext.getType() == ExtensionType.TEXT ) {
-                w.write(key, ext.getText());
+                ob.add(key, ext.getText());
             } else {
-                w.writeStartArray(key);
+                JsonArrayBuilder extensionArr = Json.createArrayBuilder();
                 for(final Artifact artifact : ext.getArtifacts()) {
                     final Configurations artifactCfgs = new Configurations();
                     for(final Configuration cfg : allConfigs) {
@@ -212,20 +221,20 @@ abstract class JSONWriterBase {
                         }
                     }
                     if ( artifact.getMetadata().isEmpty() && artifactCfgs.isEmpty() ) {
-                        w.write(artifact.getId().toMvnId());
+                        extensionArr.add(artifact.getId().toMvnId());
                     } else {
-                        w.writeStartObject();
-                        w.write(JSONConstants.ARTIFACT_ID, artifact.getId().toMvnId());
+                        JsonObjectBuilder extObj = Json.createObjectBuilder();
+                        extObj.add(JSONConstants.ARTIFACT_ID, artifact.getId().toMvnId());
 
                         for(final Map.Entry<String, String> me : artifact.getMetadata()) {
-                            w.write(me.getKey(), me.getValue());
+                            extObj.add(me.getKey(), me.getValue());
                         }
 
-                        writeConfigurations(w, artifactCfgs);
-                        w.writeEnd();
+                        writeConfigurations(ob, artifactCfgs);
+                        extensionArr.add(extObj.build());
                     }
                 }
-                w.writeEnd();
+                ob.add(key, extensionArr.build());
             }
         }
     }
diff --git a/featuremodel/feature-support/src/test/java/org/apache/sling/feature/support/json/FeatureJSONWriterTest.java b/featuremodel/feature-support/src/test/java/org/apache/sling/feature/support/json/FeatureJSONWriterTest.java
index 6ba5bd4..d1b4c72 100644
--- a/featuremodel/feature-support/src/test/java/org/apache/sling/feature/support/json/FeatureJSONWriterTest.java
+++ b/featuremodel/feature-support/src/test/java/org/apache/sling/feature/support/json/FeatureJSONWriterTest.java
@@ -28,17 +28,20 @@ import static org.junit.Assert.assertEquals;
 public class FeatureJSONWriterTest {
 
     @Test public void testRead() throws Exception {
-        final Feature feature = U.readFeature("test");
-        final Feature readFeature;
+        final Feature f = U.readFeature("test");
+        final Feature rf;
         try ( final StringWriter writer = new StringWriter() ) {
-            FeatureJSONWriter.write(writer, feature);
+            FeatureJSONWriter.write(writer, f);
             try ( final StringReader reader = new StringReader(writer.toString()) ) {
-                readFeature = FeatureJSONReader.read(reader, null);
+                rf = FeatureJSONReader.read(reader, null);
             }
         }
-        assertEquals(feature.getId(), readFeature.getId());
+        assertEquals(f.getId(), rf.getId());
+        assertEquals("org.apache.sling:test-feature:1.1", rf.getId().toMvnId());
+        assertEquals("The feature description", rf.getDescription());
+
         assertEquals(Arrays.asList("org.osgi.service.http.runtime.HttpServiceRuntime"),
-                U.findCapability(readFeature.getCapabilities(), "osgi.service").getAttributes().get("objectClass"));
+                U.findCapability(rf.getCapabilities(), "osgi.service").getAttributes().get("objectClass"));
     }
 
 }
diff --git a/featuremodel/feature-support/src/test/resources/features/test.json b/featuremodel/feature-support/src/test/resources/features/test.json
index 451b824..8ae346c 100644
--- a/featuremodel/feature-support/src/test/resources/features/test.json
+++ b/featuremodel/feature-support/src/test/resources/features/test.json
@@ -1,5 +1,6 @@
 {
     "id" : "org.apache.sling/test-feature/1.1",
+    "description": "The feature description",
 
     "includes" : [
          {

-- 
To stop receiving notification emails like this one, please contact
davidb@apache.org.