SLING-7346: handle JSONResponse properties as json if possible.
diff --git a/src/main/java/org/apache/sling/servlets/post/JSONResponse.java b/src/main/java/org/apache/sling/servlets/post/JSONResponse.java
index e65e90c..8cffaf8 100644
--- a/src/main/java/org/apache/sling/servlets/post/JSONResponse.java
+++ b/src/main/java/org/apache/sling/servlets/post/JSONResponse.java
@@ -18,12 +18,19 @@
*/
package org.apache.sling.servlets.post;
+
+import org.apache.sling.servlets.post.impl.JsonTicksConverter;
+
import javax.json.Json;
import javax.json.JsonArrayBuilder;
import javax.json.JsonObject;
import javax.json.JsonObjectBuilder;
+import javax.json.JsonStructure;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
+import java.io.StringReader;
+import java.math.BigDecimal;
+import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -51,6 +58,8 @@
private Map<String, Object> json = new HashMap<>();
+ private Map<String, JsonStructure> jsonCached = new HashMap<>();
+
private List<Map<String, Object>> changes = new ArrayList<>();
private Throwable error;
@@ -78,9 +87,31 @@
return this.error;
}
+ /**
+ * This method accepts values that correspond to json primitives or otherwise assumes that the toString() of the value
+ * can be parsed as json. If neither is the case it will throw an Exception.
+ *
+ * Assuming the above holds, it will put the value as json directly into the json value part of the response.
+ *
+ * @param name name of the property
+ * @param value value of the property - either of type {String, Boolean, Number, null}
+ * or the toString() is parseable as json
+ * @throws JSONResponseException if the value is not usable
+ */
@Override
public void setProperty(String name, Object value) {
- json.put(name, value);
+ if (value instanceof String || value instanceof Boolean || value instanceof Number || value == null) {
+ json.put(name, value);
+ }
+ else {
+ try {
+ String valueString = JsonTicksConverter.tickToDoubleQuote(value.toString());
+ jsonCached.put(name, Json.createReader(new StringReader(valueString)).read());
+ json.put(name, value);
+ } catch (Exception ex) {
+ throw new JSONResponseException(ex);
+ }
+ }
}
@Override
@@ -102,11 +133,42 @@
JsonObject getJson() {
JsonObjectBuilder jsonBuilder = Json.createObjectBuilder();
for (Map.Entry<String, Object> entry : json.entrySet()) {
- if (entry.getValue() != null) {
- jsonBuilder.add(entry.getKey(), entry.getValue().toString());
+ Object value = entry.getValue();
+ if (value instanceof String) {
+ jsonBuilder.add(entry.getKey(), (String) entry.getValue());
+ }
+ else if (value instanceof Boolean) {
+ jsonBuilder.add(entry.getKey(), (Boolean) value);
+ }
+ else if (value instanceof BigInteger) {
+ jsonBuilder.add(entry.getKey(), (BigInteger) value);
+ }
+ else if (value instanceof BigDecimal) {
+ jsonBuilder.add(entry.getKey(), (BigDecimal) value);
+ }
+ else if (value instanceof Byte) {
+ jsonBuilder.add(entry.getKey(), (Byte) value);
+ }
+ else if (value instanceof Short) {
+ jsonBuilder.add(entry.getKey(), (Short) value);
+ }
+ else if (value instanceof Integer) {
+ jsonBuilder.add(entry.getKey(), (Integer) value);
+ }
+ else if (value instanceof Long) {
+ jsonBuilder.add(entry.getKey(), (Long) value);
+ }
+ else if (value instanceof Double) {
+ jsonBuilder.add(entry.getKey(), (Double) value);
+ }
+ else if (value instanceof Float) {
+ jsonBuilder.add(entry.getKey(), (Float) value);
+ }
+ else if (value == null) {
+ jsonBuilder.addNull(entry.getKey());
}
else {
- jsonBuilder.addNull(entry.getKey());
+ jsonBuilder.add(entry.getKey(), jsonCached.get(entry.getKey()));
}
}
if (this.error != null) {
diff --git a/src/main/java/org/apache/sling/servlets/post/impl/JsonTicksConverter.java b/src/main/java/org/apache/sling/servlets/post/impl/JsonTicksConverter.java
new file mode 100644
index 0000000..510e64b
--- /dev/null
+++ b/src/main/java/org/apache/sling/servlets/post/impl/JsonTicksConverter.java
@@ -0,0 +1,106 @@
+/*
+ * 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.post.impl;
+
+/**
+ * Converts JSON with ticks to JSON with quotes.
+ * <p>Conversions:</p>
+ * <ul>
+ * <li>Converts ticks ' to " when used as quotation marks for names or string values</li>
+ * <li>Within names or string values quoted with ticks, ticks have to be escaped with <code>\'</code>.
+ * This escaping sign is removed on the conversion, because in JSON ticks must not be escaped.</li>
+ * <li>Within names or string values quoted with ticks, double quotes may or may not be escaped.
+ * After the conversion they are always escaped.</li>
+ * </ul>
+ */
+public final class JsonTicksConverter {
+
+ private JsonTicksConverter() {
+ // static methods only
+ }
+
+ public static String tickToDoubleQuote(final String input) {
+ final int len = input.length();
+ final StringBuilder output = new StringBuilder(len);
+ boolean quoted = false;
+ boolean tickQuoted = false;
+ boolean escaped = false;
+ boolean comment = false;
+ char lastChar = ' ';
+ for (int i = 0; i < len; i++) {
+ char in = input.charAt(i);
+ if (quoted || tickQuoted) {
+ if (escaped) {
+ if (in != '\'') {
+ output.append("\\");
+ }
+ if (in == '\\') {
+ output.append("\\");
+ }
+ escaped = false;
+ }
+ else {
+ if (in == '"') {
+ if (quoted) {
+ quoted = false;
+ }
+ else if (tickQuoted) {
+ output.append("\\");
+ }
+ }
+ else if (in == '\'') {
+ if (tickQuoted) {
+ in = '"';
+ tickQuoted = false;
+ }
+ }
+ else if (in == '\\') {
+ escaped = true;
+ }
+ }
+ }
+ else {
+ if (comment) {
+ if (lastChar == '*' && in == '/') {
+ comment = false;
+ }
+ }
+ else {
+ if (lastChar == '/' && in == '*') {
+ comment = true;
+ }
+ else if (in == '\'') {
+ in = '"';
+ tickQuoted = true;
+ }
+ else if (in == '"') {
+ quoted = true;
+ }
+ }
+ }
+ if (in == '\\') {
+ continue;
+ }
+ output.append(in);
+ lastChar = in;
+ }
+ return output.toString();
+ }
+
+}
diff --git a/src/test/java/org/apache/sling/servlets/post/JsonResponseTest.java b/src/test/java/org/apache/sling/servlets/post/JsonResponseTest.java
index d591628..c2b18ee 100644
--- a/src/test/java/org/apache/sling/servlets/post/JsonResponseTest.java
+++ b/src/test/java/org/apache/sling/servlets/post/JsonResponseTest.java
@@ -24,7 +24,9 @@
import javax.json.Json;
import javax.json.JsonArray;
+import javax.json.JsonNumber;
import javax.json.JsonObject;
+import javax.json.JsonObjectBuilder;
import javax.json.JsonString;
import javax.json.JsonValue;
import javax.servlet.http.HttpServletResponse;
@@ -75,7 +77,7 @@
MockSlingHttpServletResponse response = new MockSlingHttpServletResponse();
res.send(response, true);
JsonObject result = Json.createReader(new StringReader(response.getOutput().toString())).readObject();
- assertProperty(result, HtmlResponse.PN_STATUS_CODE, Integer.toString(HttpServletResponse.SC_OK));
+ assertProperty(result, HtmlResponse.PN_STATUS_CODE, HttpServletResponse.SC_OK);
assertEquals(JSONResponse.RESPONSE_CONTENT_TYPE, response.getContentType());
assertEquals(JSONResponse.RESPONSE_CHARSET, response.getCharacterEncoding());
}
@@ -88,7 +90,7 @@
MockResponseWithHeader response = new MockResponseWithHeader();
res.send(response, true);
JsonObject result = Json.createReader(new StringReader(response.getOutput().toString())).readObject();
- assertProperty(result, HtmlResponse.PN_STATUS_CODE, Integer.toString(HttpServletResponse.SC_CREATED));
+ assertProperty(result, HtmlResponse.PN_STATUS_CODE, HttpServletResponse.SC_CREATED);
assertEquals(location, response.getHeader("Location"));
}
@@ -102,7 +104,7 @@
MockResponseWithHeader response = new MockResponseWithHeader();
res.send(response, true);
JsonObject result = Json.createReader(new StringReader(response.getOutput().toString())).readObject();
- assertProperty(result, HtmlResponse.PN_STATUS_CODE, Integer.toString(status));
+ assertProperty(result, HtmlResponse.PN_STATUS_CODE, status);
assertEquals(location, response.getHeader("Location"));
}
}
@@ -115,18 +117,40 @@
assertEquals(0, obj.getJsonArray("changes").size());
}
+ public void testSendWithJsonAsPropertyValue() throws Exception {
+ String testResponseJson = "{\"user\":\"testUser\",\"properties\":{\"id\":\"testId\", \"name\":\"test\"}}";
+ JsonObject customProperty = Json.createReader(new StringReader(testResponseJson)).readObject();
+ res.setProperty("response", customProperty);
+ MockResponseWithHeader response = new MockResponseWithHeader();
+ res.send(response, true);
+ JsonObject result = Json.createReader(new StringReader(response.getOutput().toString())).readObject();
+ assertProperty(result, "response", customProperty);
+ }
+
private static JsonValue assertProperty(JsonObject obj, String key) {
assertTrue("JSON object does not have property " + key, obj.containsKey(key));
return obj.get(key);
}
@SuppressWarnings({"unchecked"})
- private static JsonString assertProperty(JsonObject obj, String key, String expected) {
+ private static JsonValue assertProperty(JsonObject obj, String key, int expected) {
+ JsonNumber res = (JsonNumber) assertProperty(obj, key);
+ assertEquals(expected, res.intValue());
+ return res;
+ }
+
+ private static JsonValue assertProperty(JsonObject obj, String key, String expected) {
JsonString res = (JsonString) assertProperty(obj, key);
assertEquals(expected, res.getString());
return res;
}
+ private static JsonValue assertProperty(JsonObject obj, String key, JsonObject expected) {
+ JsonObject res = (JsonObject) assertProperty(obj, key);
+ assertEquals(expected, res);
+ return res;
+ }
+
@SuppressWarnings({"unchecked"})
private static <T> T assertInstanceOf(Object obj, Class<T> clazz) {
try {