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> <small>(left brace)</small> and ending
+ * with <code>}</code> <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> <small>(left brace)</small> and ending
+ * with <code>}</code> <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> <small>(left brace)</small> and ending
+ * with <code>}</code> <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());
}
}