SLING-6683: Replace commons.json usage in org.apache.sling.servlets.get

git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1789113 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/pom.xml b/pom.xml
index 1cdfd4b..8c17c0c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -91,8 +91,8 @@
         </dependency>
         <dependency>
             <groupId>org.apache.sling</groupId>
-            <artifactId>org.apache.sling.commons.json</artifactId>
-            <version>2.0.8</version>
+            <artifactId>org.apache.sling.commons.johnzon</artifactId>
+            <version>0.1.0-SNAPSHOT</version>
             <scope>provided</scope>
         </dependency>
         <dependency>
diff --git a/src/main/java/org/apache/sling/servlets/get/impl/helpers/JsonRendererServlet.java b/src/main/java/org/apache/sling/servlets/get/impl/helpers/JsonRendererServlet.java
index 9d7a58a..c2bfb37 100644
--- a/src/main/java/org/apache/sling/servlets/get/impl/helpers/JsonRendererServlet.java
+++ b/src/main/java/org/apache/sling/servlets/get/impl/helpers/JsonRendererServlet.java
@@ -18,6 +18,8 @@
 
 import java.io.IOException;
 
+import javax.json.Json;
+import javax.json.stream.JsonGenerator;
 import javax.servlet.http.HttpServletResponse;
 
 import org.apache.commons.lang3.StringUtils;
@@ -28,10 +30,8 @@
 import org.apache.sling.api.resource.ResourceNotFoundException;
 import org.apache.sling.api.resource.ResourceUtil;
 import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
-import org.apache.sling.commons.json.JSONException;
-import org.apache.sling.commons.json.io.JSONRenderer;
-import org.apache.sling.commons.json.io.JSONWriter;
-import org.apache.sling.commons.json.sling.ResourceTraversor;
+import org.apache.sling.servlets.get.impl.util.JsonRenderer;
+import org.apache.sling.servlets.get.impl.util.ResourceTraversor;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -63,7 +63,7 @@
 
     private long maximumResults;
 
-    private final JSONRenderer renderer = new JSONRenderer();
+    private final JsonRenderer renderer = new JsonRenderer();
 
     public JsonRendererServlet(long maximumResults) {
         this.maximumResults = maximumResults;
@@ -96,19 +96,19 @@
         final boolean harray = hasSelector(req, HARRAY);
         ResourceTraversor traversor = null;
         try {
-            traversor = new ResourceTraversor(maxRecursionLevels, maximumResults, r, tidy);
+            traversor = new ResourceTraversor(maxRecursionLevels, maximumResults, r);
             allowedLevel = traversor.collectResources();
             if ( allowedLevel != -1 ) {
-			    allowDump = false;
+                allowDump = false;
             }
-        } catch (final JSONException e) {
+        } catch (final Exception e) {
             reportException(e);
         }
         try {
             // Dump the resource if we can
             if (allowDump) {
                 if (tidy || harray) {
-                    final JSONRenderer.Options opt = renderer.options()
+                    final JsonRenderer.Options opt = renderer.options()
                             .withIndent(tidy ? INDENT_SPACES : 0)
                             .withArraysForChildren(harray);
                     resp.getWriter().write(renderer.prettyPrint(traversor.getJSONObject(), opt));
@@ -116,7 +116,7 @@
                     // If no rendering options, use the plain toString() method, for
                     // backwards compatibility. Output might be slightly different
                     // with prettyPrint and no options
-                    resp.getWriter().write(traversor.getJSONObject().toString());
+                    Json.createGenerator(resp.getWriter()).write(traversor.getJSONObject()).close();
                 }
 
             } else {
@@ -124,15 +124,16 @@
                 // Send a 300
                 String tidyUrl = (tidy) ? "tidy." : "";
                 resp.setStatus(HttpServletResponse.SC_MULTIPLE_CHOICES);
-                JSONWriter writer = new JSONWriter(resp.getWriter());
-                writer.array();
+                JsonGenerator writer = Json.createGenerator(resp.getWriter());
+                writer.writeStartArray();
                 while (allowedLevel >= 0) {
-                    writer.value(r.getResourceMetadata().getResolutionPath() + "." + tidyUrl + allowedLevel + ".json");
+                    writer.write(r.getResourceMetadata().getResolutionPath() + "." + tidyUrl + allowedLevel + ".json");
                     allowedLevel--;
                 }
-                writer.endArray();
+                writer.writeEnd();
+                writer.close();
             }
-        } catch (JSONException je) {
+        } catch (Exception je) {
             reportException(je);
         }
     }
diff --git a/src/main/java/org/apache/sling/servlets/get/impl/impl/info/SlingInfoServlet.java b/src/main/java/org/apache/sling/servlets/get/impl/impl/info/SlingInfoServlet.java
index 6e6600b..42e8bbc 100644
--- a/src/main/java/org/apache/sling/servlets/get/impl/impl/info/SlingInfoServlet.java
+++ b/src/main/java/org/apache/sling/servlets/get/impl/impl/info/SlingInfoServlet.java
@@ -24,6 +24,10 @@
 import java.util.HashMap;
 import java.util.Map;
 
+import javax.json.Json;
+import javax.json.JsonException;
+import javax.json.JsonWriter;
+import javax.json.stream.JsonGenerator;
 import javax.servlet.Servlet;
 import javax.servlet.http.HttpServletResponse;
 
@@ -32,8 +36,6 @@
 import org.apache.sling.api.request.ResponseUtil;
 import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
-import org.apache.sling.commons.json.JSONException;
-import org.apache.sling.commons.json.io.JSONWriter;
 import org.osgi.service.component.annotations.Component;
 
 /**
@@ -109,21 +111,19 @@
         response.setCharacterEncoding("UTF-8");
 
         final Writer out = response.getWriter();
-        final JSONWriter w = new JSONWriter(out);
+        final JsonGenerator w = Json.createGenerator(out);
 
         try {
-            w.object();
+            w.writeStartObject();
             for (final Map.Entry<String, String> e : data.entrySet()) {
-                w.key(e.getKey());
-                w.value(e.getValue());
+                w.write(e.getKey(), e.getValue());
             }
-            w.endObject();
-
-        } catch (JSONException jse) {
+            w.writeEnd();
+        } catch (JsonException jse) {
             out.write(jse.toString());
-
-        } finally {
             out.flush();
+        } finally {
+            w.flush();
         }
     }
 
diff --git a/src/main/java/org/apache/sling/servlets/get/impl/util/JsonObjectCreator.java b/src/main/java/org/apache/sling/servlets/get/impl/util/JsonObjectCreator.java
new file mode 100644
index 0000000..f37ef16
--- /dev/null
+++ b/src/main/java/org/apache/sling/servlets/get/impl/util/JsonObjectCreator.java
@@ -0,0 +1,238 @@
+/*
+ * 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.sling.servlets.get.impl.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Array;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+
+import javax.json.Json;
+import javax.json.JsonArrayBuilder;
+import javax.json.JsonObjectBuilder;
+import javax.json.JsonValue;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceUtil;
+import org.apache.sling.api.resource.ValueMap;
+
+public abstract class JsonObjectCreator {
+
+    /**
+     * Dump given resource in JSON, optionally recursing into its objects
+     */
+    public static JsonObjectBuilder create(final Resource resource, final int maxRecursionLevels) {
+        return create(resource, 0, maxRecursionLevels);
+    }
+
+
+    /** Dump given resource in JSON, optionally recursing into its objects */
+    private static JsonObjectBuilder create(final Resource resource,
+            final int currentRecursionLevel,
+            final int maxRecursionLevels) {
+        final ValueMap valueMap = resource.adaptTo(ValueMap.class);
+
+        final Map propertyMap = (valueMap != null)
+                ? valueMap
+                : resource.adaptTo(Map.class);
+
+        final JsonObjectBuilder obj = Json.createObjectBuilder();
+
+        if (propertyMap == null) {
+
+            // no map available, try string
+            final String value = resource.adaptTo(String.class);
+            if (value != null) {
+
+                // single value property or just plain String resource or...
+                obj.add(ResourceUtil.getName(resource), value.toString());
+
+            } else {
+
+                // Try multi-value "property"
+                final String[] values = resource.adaptTo(String[].class);
+                if (values != null) {
+                    JsonArrayBuilder builder = Json.createArrayBuilder();
+                    for (String v : values)
+                    {
+                        builder.add(v);
+                    }
+                    obj.add(ResourceUtil.getName(resource), builder);
+                }
+
+            }
+
+        } else {
+
+            @SuppressWarnings("unchecked")
+            final Iterator<Map.Entry> props = propertyMap.entrySet().iterator();
+
+            // the node's actual properties
+            while (props.hasNext()) {
+                final Map.Entry prop = props.next();
+
+                if ( prop.getValue() != null ) {
+                    createProperty(obj, valueMap, prop.getKey().toString(),
+                        prop.getValue());
+                }
+            }
+        }
+
+        // the child nodes
+        if (recursionLevelActive(currentRecursionLevel, maxRecursionLevels)) {
+            final Iterator<Resource> children = ResourceUtil.listChildren(resource);
+            while (children.hasNext()) {
+                final Resource n = children.next();
+                createSingleResource(n, obj, currentRecursionLevel,
+                    maxRecursionLevels);
+            }
+        }
+
+        return obj;
+    }
+
+    /** Used to format date values */
+    private static final String ECMA_DATE_FORMAT = "EEE MMM dd yyyy HH:mm:ss 'GMT'Z";
+
+    /** The Locale used to format date values */
+    static final Locale DATE_FORMAT_LOCALE = Locale.US;
+
+
+    public static String format(final Calendar date) {
+        DateFormat formatter = new SimpleDateFormat(ECMA_DATE_FORMAT, DATE_FORMAT_LOCALE);
+        formatter.setTimeZone(date.getTimeZone());
+        return formatter.format(date.getTime());
+    }
+
+    /** Dump only a value in the correct format */
+    public static JsonValue getValue(final Object value) {
+        JsonObjectBuilder builder = Json.createObjectBuilder();
+        if ( value instanceof InputStream ) {
+            // input stream is already handled
+            builder.add("entry", 0);
+        } else if ( value instanceof Calendar ) {
+            builder.add("entry", format((Calendar)value));
+        } else if ( value instanceof Boolean ) {
+            builder.add("entry", (Boolean) value);
+        } else if ( value instanceof Long ) {
+            builder.add("entry", (Long) value);
+        } else if ( value instanceof Integer ) {
+            builder.add("entry", (Integer) value);
+        } else if ( value instanceof Double ) {
+            builder.add("entry", (Double) value);
+        } else if ( value != null ) {
+            builder.add("entry", value.toString());
+        } else {
+            builder.add("entry", "");
+        }
+        return builder.build().get("entry");
+    }
+
+    /** Dump a single node */
+    private static void createSingleResource(final Resource n, final JsonObjectBuilder parent,
+            final int currentRecursionLevel, final int maxRecursionLevels) {
+        if (recursionLevelActive(currentRecursionLevel, maxRecursionLevels)) {
+            parent.add(ResourceUtil.getName(n), create(n, currentRecursionLevel + 1, maxRecursionLevels));
+        }
+    }
+
+    /** true if the current recursion level is active */
+    private static boolean recursionLevelActive(final int currentRecursionLevel,
+            final int maxRecursionLevels) {
+        return maxRecursionLevels < 0
+            || currentRecursionLevel < maxRecursionLevels;
+    }
+
+    /**
+     * Write a single property
+     */
+    public static void createProperty(final JsonObjectBuilder obj,
+                                 final ValueMap valueMap,
+                                 final String key,
+                                 final Object value) {
+        Object[] values = null;
+        if (value.getClass().isArray()) {
+            final int length = Array.getLength(value);
+            // write out empty array
+            if ( length == 0 ) {
+                obj.add(key, Json.createArrayBuilder());
+                return;
+            }
+            values = new Object[Array.getLength(value)];
+            for(int i=0; i<length; i++) {
+                values[i] = Array.get(value, i);
+            }
+        }
+
+        // special handling for binaries: we dump the length and not the data!
+        if (value instanceof InputStream
+            || (values != null && values[0] instanceof InputStream)) {
+            // TODO for now we mark binary properties with an initial colon in
+            // their name
+            // (colon is not allowed as a JCR property name)
+            // in the name, and the value should be the size of the binary data
+            if (values == null) {
+                obj.add(":" + key, getLength(valueMap, -1, key, (InputStream)value));
+            } else {
+                final JsonArrayBuilder result = Json.createArrayBuilder();
+                for (int i = 0; i < values.length; i++) {
+                    result.add(getLength(valueMap, i, key, (InputStream)values[i]));
+                }
+                obj.add(":" + key, result);
+            }
+            return;
+        }
+
+        if (!value.getClass().isArray()) {
+            obj.add(key, getValue(value));
+        } else {
+            final JsonArrayBuilder result = Json.createArrayBuilder();
+            for (Object v : values) {
+                result.add(getValue(v));
+            }
+            obj.add(key, result);
+        }
+    }
+
+    private static long getLength(final ValueMap    valueMap,
+                           final int         index,
+                           final String      key,
+                           final InputStream stream) {
+        try {
+            stream.close();
+        } catch (IOException ignore) {}
+        long length = -1;
+        if ( valueMap != null ) {
+            if ( index == -1 ) {
+                length = valueMap.get(key, length);
+            } else {
+                Long[] lengths = valueMap.get(key, Long[].class);
+                if ( lengths != null && lengths.length > index ) {
+                    length = lengths[index];
+                }
+            }
+        }
+        return length;
+    }
+}
diff --git a/src/main/java/org/apache/sling/servlets/get/impl/util/JsonRenderer.java b/src/main/java/org/apache/sling/servlets/get/impl/util/JsonRenderer.java
new file mode 100644
index 0000000..332d4f8
--- /dev/null
+++ b/src/main/java/org/apache/sling/servlets/get/impl/util/JsonRenderer.java
@@ -0,0 +1,503 @@
+/*
+ * 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.sling.servlets.get.impl.util;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.Iterator;
+import java.util.Map;
+
+import javax.json.Json;
+import javax.json.JsonArray;
+import javax.json.JsonArrayBuilder;
+import javax.json.JsonObject;
+import javax.json.JsonObjectBuilder;
+import javax.json.JsonString;
+import javax.json.JsonValue;
+
+public class JsonRenderer
+{
+    /** Rendering options */
+    static public class Options {
+        int indent;
+        private boolean indentIsPositive;
+        int initialIndent;
+        boolean arraysForChildren;
+
+        public static final String DEFAULT_CHILDREN_KEY = "__children__";
+        public static final String DEFAULT_CHILD_NAME_KEY = "__name__";
+
+        String childrenKey = DEFAULT_CHILDREN_KEY;
+        String childNameKey = DEFAULT_CHILD_NAME_KEY;
+
+        /** Clients use JSONRenderer.options() to create objects */
+        private Options() {
+        }
+
+        Options(Options opt) {
+            this.indent = opt.indent;
+            this.indentIsPositive = opt.indentIsPositive;
+            this.initialIndent = opt.initialIndent;
+            this.arraysForChildren = opt.arraysForChildren;
+        }
+
+        public Options withIndent(int n) {
+            indent = n;
+            indentIsPositive = indent > 0;
+            return this;
+        }
+
+        public Options withInitialIndent(int n) {
+            initialIndent = n;
+            return this;
+        }
+
+        public Options withArraysForChildren(boolean b) {
+            arraysForChildren = b;
+            return this;
+        }
+
+        public Options withChildNameKey(String key) {
+            childNameKey = key;
+            return this;
+        }
+
+        public Options withChildrenKey(String key) {
+            childrenKey = key;
+            return this;
+        }
+
+        boolean hasIndent() {
+            return indentIsPositive;
+        }
+    }
+
+    /** Return an Options object with default values */
+    public Options options() {
+        return new Options();
+    }
+
+    /** Write N spaces to sb for indentation */
+    private void indent(StringBuilder sb, int howMuch) {
+        for (int i=0; i < howMuch; i++) {
+            sb.append(' ');
+        }
+    }
+
+    /** Render the supplied JSONObject to a String, in
+     *  the simplest possible way.
+     */
+    public String toString(JsonObject jo) {
+        try {
+            StringWriter writer = new StringWriter();
+            Json.createGenerator(writer).write(jo).close();
+            return writer.toString();
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    /** Make a JSON text of the supplied JSONArray. For compactness, no
+     *  unnecessary whitespace is added. If it is not possible to produce a
+     *  syntactically correct JSON text then null will be returned instead. This
+     *  could occur if the array contains an invalid number.
+     *  <p>Warning: This method assumes that the data structure is acyclical.
+     *
+     *  @return a printable, displayable, transmittable
+     *  representation of the array.
+     */
+    public String toString(JsonArray ja) {
+        try {
+            return '[' + join(ja,",") + ']';
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    /** Quote the supplied string for JSON */
+    public String quote(String string) {
+        if (string == null || string.length() == 0) {
+            return "\"\"";
+        }
+
+        char          b;
+        char          c = 0;
+        int           i;
+        int           len = string.length();
+        StringBuilder sb = new StringBuilder(len + 2);
+        String        t;
+
+        sb.append('"');
+        for (i = 0; i < len; i += 1) {
+            b = c;
+            c = string.charAt(i);
+            switch (c) {
+                case '\\':
+                case '"':
+                    sb.append('\\');
+                    sb.append(c);
+                    break;
+                case '/':
+                    if (b == '<') {
+                        sb.append('\\');
+                    }
+                    sb.append(c);
+                    break;
+                case '\b':
+                    sb.append("\\b");
+                    break;
+                case '\t':
+                    sb.append("\\t");
+                    break;
+                case '\n':
+                    sb.append("\\n");
+                    break;
+                case '\f':
+                    sb.append("\\f");
+                    break;
+                case '\r':
+                    sb.append("\\r");
+                    break;
+                default:
+                    if (c < ' ' || (c >= '\u0080' && c < '\u00a0') ||
+                            (c >= '\u2000' && c < '\u2100')) {
+                        t = "000" + Integer.toHexString(c);
+                        sb.append("\\u").append(t.substring(t.length() - 4));
+                    } else {
+                        sb.append(c);
+                    }
+            }
+        }
+        sb.append('"');
+        return sb.toString();
+    }
+
+    /** Quote the supplied string for JSON, to the supplied Writer */
+    public void quote(Writer w, String string) throws IOException {
+        w.write(quote(string));
+    }
+
+    /**
+     * Make a JSON text of an Object value. 
+     * <p>
+     * Warning: This method assumes that the data structure is acyclical.
+     * @param value The value to be serialized.
+     * @return a printable, displayable, transmittable
+     *  representation of the object, beginning
+     *  with <code>{</code>&nbsp;<small>(left brace)</small> and ending
+     *  with <code>}</code>&nbsp;<small>(right brace)</small>.
+     * @throws JSONException If the value is or contains an invalid number.
+     */
+    public String valueToString(Object value) {
+        // TODO call the other valueToString instead
+        if (value == null || value.equals(null)) {
+            return "null";
+        }
+        if (value instanceof JsonString) {
+            quote(((JsonString)value).getString());
+        }
+        if (value instanceof Number) {
+            return numberToString((Number) value);
+        }
+        if (value instanceof Boolean) {
+            return value.toString();
+        }
+        if (value instanceof JsonObject || value instanceof JsonArray) {
+            StringWriter writer = new StringWriter();
+            Json.createGenerator(writer).write((JsonValue) value).close();
+            return writer.toString();
+        }
+        return quote(value.toString());
+    }
+
+    /** Make a JSON String of an Object value, with rendering options
+     * <p>
+     * Warning: This method assumes that the data structure is acyclical.
+     * @param value The value to be serialized.
+     * @return a printable, displayable, transmittable
+     *  representation of the object, beginning
+     *  with <code>{</code>&nbsp;<small>(left brace)</small> and ending
+     *  with <code>}</code>&nbsp;<small>(right brace)</small>.
+     * @throws JSONException If the object contains an invalid number.
+     */
+    public String valueToString(Object value, Options opt) {
+        if (value == null || value.equals(null)) {
+            return "null";
+        }
+        if (value instanceof JsonString) {
+            return quote(((JsonString)value).getString());
+        }
+        if (value instanceof Number) {
+            return numberToString((Number) value);
+        }
+        if (value instanceof Boolean) {
+            return value.toString();
+        }
+        if (value instanceof JsonObject) {
+            return prettyPrint((JsonObject)value, opt);
+        }
+        if (value instanceof JsonArray) {
+            return prettyPrint((JsonArray)value, opt);
+        }
+        return quote(value.toString());
+
+    }
+
+    /**
+     * Produce a string from a Number.
+     * @param  n A Number
+     * @return A String.
+     * @throws JSONException If n is a non-finite number.
+     */
+    public String numberToString(Number n) {
+        if (n == null) {
+            throw new NullPointerException("Null pointer");
+        }
+        testNumberValidity(n);
+
+        // Shave off trailing zeros and decimal point, if possible.
+
+        String s = n.toString();
+        if (s.indexOf('.') > 0 && s.indexOf('e') < 0 && s.indexOf('E') < 0) {
+            while (s.endsWith("0")) {
+                s = s.substring(0, s.length() - 1);
+            }
+            if (s.endsWith(".")) {
+                s = s.substring(0, s.length() - 1);
+            }
+        }
+        return s;
+    }
+
+    /** Decide whether o must be skipped and added to a, when rendering a JSONObject */
+    private boolean skipChildObject(JsonArrayBuilder a, Options  opt, String key, Object value) {
+        if(opt.arraysForChildren && (value instanceof JsonObject)) {
+            JsonObjectBuilder builder = Json.createObjectBuilder();
+            builder.add(opt.childNameKey, key);
+            for (Map.Entry<String, JsonValue> entry : ((JsonObject) value).entrySet()) {
+                builder.add(entry.getKey(), entry.getValue());
+            }
+            a.add(builder);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Make a prettyprinted JSON text of this JSONObject.
+     * <p>
+     * Warning: This method assumes that the data structure is acyclical.
+     * @return a printable, displayable, transmittable
+     *  representation of the object, beginning
+     *  with <code>{</code>&nbsp;<small>(left brace)</small> and ending
+     *  with <code>}</code>&nbsp;<small>(right brace)</small>.
+     * @throws IllegalArgumentException If the object contains an invalid number.
+     */
+    public String prettyPrint(JsonObject jo, Options opt) {
+        int n = jo.size();
+        if (n == 0) {
+            return "{}";
+        }
+        final JsonArrayBuilder children = Json.createArrayBuilder();
+        Iterator<String> keys = jo.keySet().iterator();
+        StringBuilder sb = new StringBuilder("{");
+        int newindent = opt.initialIndent + opt.indent;
+        String o;
+        if (n == 1) {
+            o = keys.next();
+            final Object v = jo.get(o);
+            if(!skipChildObject(children, opt, o, v)) {
+                sb.append(quote(o));
+                sb.append(": ");
+                sb.append(valueToString(v, opt));
+            }
+        } else {
+            while (keys.hasNext()) {
+                o = keys.next();
+                final Object v = jo.get(o);
+                if(skipChildObject(children, opt, o, v)) {
+                    continue;
+                }
+                if (sb.length() > 1) {
+                    sb.append(",\n");
+                } else {
+                    sb.append('\n');
+                }
+                indent(sb, newindent);
+                sb.append(quote(o.toString()));
+                sb.append(": ");
+                sb.append(valueToString(v,
+                        options().withIndent(opt.indent).withInitialIndent(newindent)));
+            }
+            if (sb.length() > 1) {
+                sb.append('\n');
+                indent(sb, newindent);
+            }
+        }
+
+        /** Render children if any were skipped (in "children in arrays" mode) */
+        JsonArray childrenArray = children.build();
+        if(childrenArray.size() > 0) {
+            if (sb.length() > 1) {
+                sb.append(",\n");
+            } else {
+                sb.append('\n');
+            }
+            final Options childOpt = new Options(opt);
+            childOpt.withInitialIndent(childOpt.initialIndent + newindent);
+            indent(sb, childOpt.initialIndent);
+            sb.append(quote(opt.childrenKey)).append(":");
+            sb.append(prettyPrint(childrenArray, childOpt));
+        }
+
+        sb.append('}');
+        return sb.toString();
+    }
+
+    /** Pretty-print a JSONArray */
+    public String prettyPrint(JsonArray ja, Options opt) {
+        int len = ja.size();
+        if (len == 0) {
+            return "[]";
+        }
+        int i;
+        StringBuilder sb = new StringBuilder("[");
+        if (len == 1) {
+            sb.append(valueToString(ja.get(0), opt));
+        } else {
+            final int newindent = opt.initialIndent + opt.indent;
+            if(opt.hasIndent()) {
+                sb.append('\n');
+            }
+            for (i = 0; i < len; i += 1) {
+                if (i > 0) {
+                    sb.append(',');
+                    if(opt.hasIndent()) {
+                        sb.append('\n');
+                    }
+                }
+                indent(sb, newindent);
+                sb.append(valueToString(ja.get(i), opt));
+            }
+            if(opt.hasIndent()) {
+                sb.append('\n');
+            }
+            indent(sb, opt.initialIndent);
+        }
+        sb.append(']');
+        return sb.toString();
+    }
+
+    /**
+     * Throw an exception if the object is an NaN or infinite number.
+     * @param o The object to test.
+     * @throws IllegalArgumentException If o is a non-finite number.
+     */
+    public void testNumberValidity(Object o) {
+        if (o != null) {
+            if (o instanceof Double) {
+                if (((Double)o).isInfinite() || ((Double)o).isNaN()) {
+                    throw new IllegalArgumentException(
+                        "JSON does not allow non-finite numbers");
+                }
+            } else if (o instanceof Float) {
+                if (((Float)o).isInfinite() || ((Float)o).isNaN()) {
+                    throw new IllegalArgumentException(
+                        "JSON does not allow non-finite numbers.");
+                }
+            }
+        }
+    }
+
+    /**
+     * Make a string from the contents of this JSONArray. The
+     * <code>separator</code> string is inserted between each element.
+     * Warning: This method assumes that the data structure is acyclical.
+     * @param separator A string that will be inserted between the elements.
+     * @return a string.
+     * @throws JSONException If the array contains an invalid number.
+     */
+    public String join(JsonArray ja, String separator) {
+        final int len = ja.size();
+        StringBuffer sb = new StringBuffer();
+
+        for (int i = 0; i < len; i += 1) {
+            if (i > 0) {
+                sb.append(separator);
+            }
+            sb.append(valueToString(ja.get(i)));
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Write the contents of the supplied JSONObject as JSON text to a writer.
+     * For compactness, no whitespace is added.
+     * <p>
+     * Warning: This method assumes that the data structure is acyclical.
+     *
+     * @return The writer.
+     * @throws IOException
+     */
+    public Writer write(Writer writer, JsonObject jo) throws IOException{
+       Json.createGenerator(writer).write(jo).flush();
+        
+       return writer;
+    }
+
+    /**
+     * Write the contents of the supplied JSONArray as JSON text to a writer.
+     * For compactness, no whitespace is added.
+     * <p>
+     * Warning: This method assumes that the data structure is acyclical.
+     *
+     * @return The writer.
+     * @throws IOException
+     */
+    public Writer write(Writer writer, JsonArray ja) throws IOException {
+        Json.createGenerator(writer).write(ja).flush();
+        return writer;
+    }
+
+    /**
+     * Produce a string from a double. The string "null" will be returned if
+     * the number is not finite.
+     * @param  d A double.
+     * @return A String.
+     */
+    public String doubleToString(double d) {
+        if (Double.isInfinite(d) || Double.isNaN(d)) {
+            return "null";
+        }
+
+        // Shave off trailing zeros and decimal point, if possible.
+
+        String s = Double.toString(d);
+        if (s.indexOf('.') > 0 && s.indexOf('e') < 0 && s.indexOf('E') < 0) {
+            while (s.endsWith("0")) {
+                s = s.substring(0, s.length() - 1);
+            }
+            if (s.endsWith(".")) {
+                s = s.substring(0, s.length() - 1);
+            }
+        }
+        return s;
+    }
+}
diff --git a/src/main/java/org/apache/sling/servlets/get/impl/util/ResourceTraversor.java b/src/main/java/org/apache/sling/servlets/get/impl/util/ResourceTraversor.java
new file mode 100644
index 0000000..5f6fecf
--- /dev/null
+++ b/src/main/java/org/apache/sling/servlets/get/impl/util/ResourceTraversor.java
@@ -0,0 +1,182 @@
+/*
+ * 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.sling.servlets.get.impl.util;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import javax.json.JsonObject;
+import javax.json.JsonObjectBuilder;
+
+import org.apache.sling.api.request.RecursionTooDeepException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceUtil;
+
+public class ResourceTraversor
+{
+    public static final class Entry {
+        public final Resource resource;
+        public final JsonObjectBuilder json;
+
+        public Entry(final Resource r, final JsonObjectBuilder o) {
+            this.resource = r;
+            this.json = o;
+        }
+    }
+    
+    Map<JsonObjectBuilder, List<Entry>> tree = new HashMap<>();
+
+    private long count;
+
+    private long maxResources;
+
+    private final int maxRecursionLevels;
+
+    private final JsonObjectBuilder startObject;
+
+    private LinkedList<Entry> currentQueue;
+
+    private LinkedList<Entry> nextQueue;
+
+    private final Resource startResource;
+
+    /** Create a ResourceTraversor, optionally limiting recursion and total number of resources
+     * @param levels recursion levels limit, -1 means no limit
+     * @param maxResources maximum number of resources to collect, ignored if levels == 1
+     * @param resource the root resource to traverse
+     * @param tidy not used
+     * @throws JSONException
+     */
+    public ResourceTraversor(final int levels, final long maxResources, final Resource resource) {
+        this.maxResources = maxResources;
+        this.maxRecursionLevels = levels;
+        this.startResource = resource;
+        currentQueue = new LinkedList<Entry>();
+        nextQueue = new LinkedList<Entry>();
+        this.startObject = this.adapt(resource);
+    }
+
+    /**
+     * Recursive descent from startResource, collecting JSONObjects into
+     * startObject. Throws a RecursionTooDeepException if the maximum number of
+     * nodes is reached on a "deep" traversal (where "deep" === level greater
+     * than 1).
+     *
+     * @return -1 if everything went fine, a positive valuew when the resource
+     *            has more child nodes then allowed.
+     * @throws JSONException
+     */
+    public int collectResources() throws RecursionTooDeepException {
+        return collectChildren(startResource, this.startObject, 0);
+    }
+
+    /**
+     * @param resource
+     * @param currentLevel
+     * @throws JSONException
+     */
+    private int collectChildren(final Resource resource,
+            final JsonObjectBuilder jsonObj,
+            int currentLevel) {
+
+        if (maxRecursionLevels == -1 || currentLevel < maxRecursionLevels) {
+            final Iterator<Resource> children = ResourceUtil.listChildren(resource);
+            while (children.hasNext()) {
+                count++;
+                final Resource res = children.next();
+                // SLING-2320: always allow enumeration of one's children;
+                // DOS-limitation is for deeper traversals.
+                if (count > maxResources && maxRecursionLevels != 1) {
+                    return currentLevel;
+                }
+                Entry child = new Entry(res, adapt(res));
+                nextQueue.addLast(child);
+                List<Entry> childTree = tree.get(jsonObj);
+                if (childTree == null)
+                {
+                    childTree = new ArrayList<>();
+                    tree.put(jsonObj, childTree);
+                }
+                childTree.add(child);
+            }
+        }
+
+        // do processing only at first level to avoid unnecessary recursion
+        if (currentLevel > 0) {
+            return -1;
+        }
+
+        List<Entry> entries = new ArrayList<>();
+        while (!currentQueue.isEmpty() || !nextQueue.isEmpty()) {
+            if (currentQueue.isEmpty()) {
+                currentLevel++;
+                currentQueue = nextQueue;
+                nextQueue = new LinkedList<Entry>();
+            }
+            final Entry nextResource = currentQueue.removeFirst();
+            final int maxLevel = collectChildren(nextResource.resource, nextResource.json, currentLevel);
+            if ( maxLevel != -1 ) {
+                return maxLevel;
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * Adapt a Resource to a JSON Object.
+     *
+     * @param resource The resource to adapt.
+     * @return The JSON representation of the Resource
+     * @throws JSONException
+     */
+    private JsonObjectBuilder adapt(final Resource resource) {
+        return JsonObjectCreator.create(resource, 0);
+    }
+
+    /**
+     * @return The number of resources this visitor found.
+     */
+    public long getCount() {
+        return count;
+    }
+
+    public JsonObject getJSONObject() {
+        
+        return addChildren(startObject).build();
+    }
+    
+    private JsonObjectBuilder addChildren(JsonObjectBuilder builder) {
+        List<Entry> children = tree.get(builder);
+        
+        if (children != null)
+        {
+            for (Entry child : children) {
+                addChildren(child.json);
+
+                builder.add(ResourceUtil.getName(child.resource), child.json);
+            }
+        }
+        
+        return builder;
+    }
+}
diff --git a/src/main/java/org/apache/sling/servlets/get/impl/version/VersionInfoServlet.java b/src/main/java/org/apache/sling/servlets/get/impl/version/VersionInfoServlet.java
index e277e75..c0a883c 100644
--- a/src/main/java/org/apache/sling/servlets/get/impl/version/VersionInfoServlet.java
+++ b/src/main/java/org/apache/sling/servlets/get/impl/version/VersionInfoServlet.java
@@ -21,7 +21,6 @@
 
 import java.io.IOException;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
 
@@ -30,6 +29,10 @@
 import javax.jcr.version.Version;
 import javax.jcr.version.VersionHistory;
 import javax.jcr.version.VersionIterator;
+import javax.json.Json;
+import javax.json.JsonArrayBuilder;
+import javax.json.JsonObject;
+import javax.json.JsonObjectBuilder;
 import javax.servlet.Servlet;
 import javax.servlet.ServletException;
 
@@ -38,10 +41,8 @@
 import org.apache.sling.api.SlingHttpServletResponse;
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
-import org.apache.sling.commons.json.JSONException;
-import org.apache.sling.commons.json.JSONObject;
-import org.apache.sling.commons.json.io.JSONRenderer;
-import org.apache.sling.commons.json.jcr.JsonItemWriter;
+import org.apache.sling.servlets.get.impl.util.JsonObjectCreator;
+import org.apache.sling.servlets.get.impl.util.JsonRenderer;
 import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.ConfigurationPolicy;
 import org.osgi.service.metatype.annotations.AttributeDefinition;
@@ -88,8 +89,8 @@
 
     /** How much to indent in tidy mode */
     public static final int INDENT_SPACES = 2;
-
-    private final JSONRenderer renderer = new JSONRenderer();
+    
+    private final JsonRenderer renderer = new JsonRenderer();
 
     @Override
     public void doGet(SlingHttpServletRequest req, SlingHttpServletResponse resp) throws ServletException,
@@ -99,40 +100,58 @@
         final boolean tidy = hasSelector(req, TIDY);
         final boolean harray = hasSelector(req, HARRAY);
 
-        final JSONRenderer.Options opt = renderer.options().withIndent(tidy ? INDENT_SPACES : 0)
-                .withArraysForChildren(harray);
+        final JsonRenderer.Options opt = renderer.options().withIndent(tidy ? INDENT_SPACES : 0)
+                    .withArraysForChildren(harray);
+        
         try {
             resp.getWriter().write(renderer.prettyPrint(getJsonObject(req.getResource()), opt));
-        } catch (RepositoryException e) {
-            throw new ServletException(e);
-        } catch (JSONException e) {
+        } catch (Exception e) {
             throw new ServletException(e);
         }
     }
 
-    private JSONObject getJsonObject(Resource resource) throws RepositoryException, JSONException {
-        final JSONObject result = new JSONObject();
+    private JsonObject getJsonObject(Resource resource) throws RepositoryException {
+        final JsonObjectBuilder result = Json.createObjectBuilder();
         final Node node = resource.adaptTo(Node.class);
         if (node == null || !node.isNodeType(JcrConstants.MIX_VERSIONABLE)) {
-            return result;
+            return result.build();
         }
 
         final VersionHistory history = node.getVersionHistory();
         final Version baseVersion = node.getBaseVersion();
         for (final VersionIterator it = history.getAllVersions(); it.hasNext();) {
             final Version v = it.nextVersion();
-            final JSONObject obj = new JSONObject();
-            obj.put("created", createdDate(v));
-            obj.put("successors", getNames(v.getSuccessors()));
-            obj.put("predecessors", getNames(v.getPredecessors()));
-            obj.put("labels", Arrays.asList(history.getVersionLabels(v)));
-            obj.put("baseVersion", baseVersion.isSame(v));
-            result.put(v.getName(), obj);
+            final JsonObjectBuilder obj = Json.createObjectBuilder();
+            obj.add("created", createdDate(v));
+            obj.add("successors", getArrayBuilder(getNames(v.getSuccessors())));
+            obj.add("predecessors", getArrayBuilder(getNames(v.getPredecessors())));
+            
+            obj.add("labels", getArrayBuilder(history.getVersionLabels(v)));
+            obj.add("baseVersion", baseVersion.isSame(v));
+            result.add(v.getName(), obj);
         }
 
-        final JSONObject wrapper = new JSONObject();
-        wrapper.put("versions", result);
-        return wrapper;
+        return Json.createObjectBuilder().add("versions", result).build();
+    }
+    
+    private JsonArrayBuilder getArrayBuilder(String[] values) {
+        JsonArrayBuilder builder = Json.createArrayBuilder();
+        
+        for (String value : values) {
+            builder.add(value);
+        }
+        
+        return builder;
+    }
+    
+    private JsonArrayBuilder getArrayBuilder(Collection<String> values) {
+        JsonArrayBuilder builder = Json.createArrayBuilder();
+        
+        for (String value : values) {
+            builder.add(value);
+        }
+        
+        return builder;
     }
 
     private static Collection<String> getNames(Version[] versions) throws RepositoryException {
@@ -154,7 +173,7 @@
     }
 
     private static String createdDate(Node node) throws RepositoryException {
-        return JsonItemWriter.format(node.getProperty(JcrConstants.JCR_CREATED).getDate());
+        return JsonObjectCreator.format(node.getProperty(JcrConstants.JCR_CREATED).getDate());
     }
 
 }