Author: chetanm
Date: Fri Jul 21 05:24:25 2017
New Revision: 1802544
URL: http://svn.apache.org/viewvc?rev=1802544&view=rev
Log:
OAK-6476 - Support deserializing json as NodeState
Support for non strict parsing of json content where types and orderable
property for some known types is inferred
Support inferring types for some common properties like jcr:primaryType and
jcr:mixins
Also synthesize the ":childOrder" property for nodes which have orderable
children like "nt:unstructured" which is one of the common such types
Modified:
jackrabbit/oak/trunk/oak-store-spi/src/main/java/org/apache/jackrabbit/oak/json/JsonDeserializer.java
jackrabbit/oak/trunk/oak-store-spi/src/test/java/org/apache/jackrabbit/oak/json/JsonDeserializerTest.java
Modified:
jackrabbit/oak/trunk/oak-store-spi/src/main/java/org/apache/jackrabbit/oak/json/JsonDeserializer.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-store-spi/src/main/java/org/apache/jackrabbit/oak/json/JsonDeserializer.java?rev=1802544&r1=1802543&r2=1802544&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-store-spi/src/main/java/org/apache/jackrabbit/oak/json/JsonDeserializer.java
(original)
+++
jackrabbit/oak/trunk/oak-store-spi/src/main/java/org/apache/jackrabbit/oak/json/JsonDeserializer.java
Fri Jul 21 05:24:25 2017
@@ -20,9 +20,11 @@
package org.apache.jackrabbit.oak.json;
import java.util.List;
+import java.util.Set;
import javax.jcr.PropertyType;
+import com.google.common.base.CharMatcher;
import com.google.common.collect.Lists;
import org.apache.jackrabbit.oak.api.PropertyState;
import org.apache.jackrabbit.oak.api.Type;
@@ -33,26 +35,36 @@ import org.apache.jackrabbit.oak.plugins
import org.apache.jackrabbit.oak.plugins.memory.DoublePropertyState;
import org.apache.jackrabbit.oak.plugins.memory.LongPropertyState;
import org.apache.jackrabbit.oak.plugins.memory.PropertyStates;
-import org.apache.jackrabbit.oak.plugins.memory.StringPropertyState;
import org.apache.jackrabbit.oak.plugins.value.Conversions;
import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
import org.apache.jackrabbit.oak.spi.state.NodeState;
+import static com.google.common.collect.ImmutableSet.of;
import static java.util.Collections.emptyList;
+import static org.apache.jackrabbit.JcrConstants.JCR_MIXINTYPES;
+import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE;
+import static org.apache.jackrabbit.JcrConstants.NT_UNSTRUCTURED;
import static
org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE;
import static
org.apache.jackrabbit.oak.plugins.memory.PropertyStates.createProperty;
public class JsonDeserializer {
+ public static final String OAK_CHILD_ORDER = ":childOrder";
private final BlobDeserializer blobHandler;
private final NodeBuilder builder;
+ private final DeserializationSupport deserializationSupport;
- public JsonDeserializer(BlobDeserializer blobHandler, NodeBuilder builder)
{
+ private JsonDeserializer(BlobDeserializer blobHandler, NodeBuilder
builder, DeserializationSupport support) {
this.blobHandler = blobHandler;
this.builder = builder;
+ this.deserializationSupport = support;
}
public JsonDeserializer(BlobDeserializer blobHandler) {
- this(blobHandler, EMPTY_NODE.builder());
+ this(blobHandler, EMPTY_NODE.builder(),
DeserializationSupport.INSTANCE);
+ }
+
+ public JsonDeserializer(BlobDeserializer blobHandler, NodeBuilder builder)
{
+ this(blobHandler, builder, DeserializationSupport.INSTANCE);
}
public NodeState deserialize(String json){
@@ -65,23 +77,32 @@ public class JsonDeserializer {
public NodeState deserialize(JsopReader reader){
readNode(reader, builder);
- reader.read('}');
return builder.getNodeState();
}
private void readNode(JsopReader reader, NodeBuilder builder) {
- do {
- String key = reader.readString();
- reader.read(':');
- if (reader.matches('{')) {
- readNode(reader, builder.child(key));
- reader.read('}');
- } else if (reader.matches('[')){
- builder.setProperty(readArrayProperty(key, reader));
- } else {
- builder.setProperty(readProperty(key, reader));
- }
- } while (reader.matches(','));
+ List<String> childNames = Lists.newArrayList();
+ if (!reader.matches('}')) {
+ do {
+ String key = reader.readString();
+ reader.read(':');
+ if (reader.matches('{')) {
+ childNames.add(key);
+ readNode(reader, builder.child(key));
+ } else if (reader.matches('[')) {
+ builder.setProperty(readArrayProperty(key, reader));
+ } else {
+ builder.setProperty(readProperty(key, reader));
+ }
+ } while (reader.matches(','));
+ reader.read('}');
+ }
+
+ if (deserializationSupport.hasOrderableChildren(builder)
+ && !builder.hasProperty(OAK_CHILD_ORDER)){
+ builder.setProperty(OAK_CHILD_ORDER, childNames, Type.NAMES);
+ }
+
}
/**
@@ -104,6 +125,7 @@ public class JsonDeserializer {
return BooleanPropertyState.booleanProperty(name, false);
} else if (reader.matches(JsopReader.STRING)) {
String jsonString = reader.getToken();
+ Type inferredType = deserializationSupport.inferPropertyType(name,
jsonString);
if (jsonString.startsWith(TypeCodes.EMPTY_ARRAY)) {
int type = PropertyType.valueFromName(
jsonString.substring(TypeCodes.EMPTY_ARRAY.length()));
@@ -118,11 +140,18 @@ public class JsonDeserializer {
return BinaryPropertyState.binaryProperty(
name, blobHandler.deserialize(value));
} else {
+ //It can happen that a value like oak:Unstructured is also
interpreted
+ //as type code. So if oakType is not undefined then use
raw value
+ //Also default to STRING in case of UNDEFINED
+ if (type == PropertyType.UNDEFINED){
+ Type oakType = inferredType != Type.UNDEFINED ?
inferredType : Type.STRING;
+ return createProperty(name, jsonString, oakType);
+ }
return createProperty(name, value, type);
}
} else {
- return StringPropertyState.stringProperty(
- name, jsonString);
+ Type oakType = inferredType != Type.UNDEFINED ? inferredType :
Type.STRING;
+ return createProperty(name, jsonString, oakType);
}
} else {
throw new IllegalArgumentException("Unexpected token: " +
reader.getToken());
@@ -156,6 +185,7 @@ public class JsonDeserializer {
values.add(false);
} else if (reader.matches(JsopReader.STRING)) {
String jsonString = reader.getToken();
+ Type inferredType =
deserializationSupport.inferPropertyType(name, jsonString);
int split = TypeCodes.split(jsonString);
if (split != -1) {
type = TypeCodes.decodeType(split, jsonString);
@@ -167,10 +197,17 @@ public class JsonDeserializer {
} else if (type == PropertyType.DECIMAL) {
values.add(Conversions.convert(value).toDecimal());
} else {
- values.add(value);
+ if (type == PropertyType.UNDEFINED){
+ //If determine type is undefined then check if
inferred type is defined
+ //else default to STRING
+ type = inferredType != Type.UNDEFINED ?
inferredType.tag() : PropertyType.STRING;
+ values.add(jsonString);
+ } else {
+ values.add(value);
+ }
}
} else {
- type = PropertyType.STRING;
+ type = inferredType != Type.UNDEFINED ? inferredType.tag()
: PropertyType.STRING;
values.add(jsonString);
}
} else {
@@ -182,4 +219,31 @@ public class JsonDeserializer {
}
+ /**
+ * Provides support for inferring types for some common property name and
types
+ */
+ private static class DeserializationSupport {
+ static final Set<String> NAME_PROPS = of(JCR_PRIMARYTYPE,
JCR_MIXINTYPES);
+ static final Set<String> ORDERABLE_TYPES = of(NT_UNSTRUCTURED);
+ static final DeserializationSupport INSTANCE = new
DeserializationSupport();
+
+ Type inferPropertyType(String propertyName, String jsonString) {
+ if (NAME_PROPS.contains(propertyName) &&
hasSingleColon(jsonString)) {
+ return Type.NAME;
+ }
+ return Type.UNDEFINED;
+ }
+
+ boolean hasOrderableChildren(NodeBuilder builder) {
+ PropertyState primaryType = builder.getProperty(JCR_PRIMARYTYPE);
+ return primaryType != null &&
ORDERABLE_TYPES.contains(primaryType.getValue(Type.NAME));
+ }
+
+ private boolean hasSingleColon(String jsonString) {
+ //In case the primaryType was encoded then it would be like
nam:oak:Unstructured
+ //So check if there is only one occurrence of ';'
+ return CharMatcher.is(':').countIn(jsonString) == 1;
+ }
+ }
+
}
Modified:
jackrabbit/oak/trunk/oak-store-spi/src/test/java/org/apache/jackrabbit/oak/json/JsonDeserializerTest.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-store-spi/src/test/java/org/apache/jackrabbit/oak/json/JsonDeserializerTest.java?rev=1802544&r1=1802543&r2=1802544&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-store-spi/src/test/java/org/apache/jackrabbit/oak/json/JsonDeserializerTest.java
(original)
+++
jackrabbit/oak/trunk/oak-store-spi/src/test/java/org/apache/jackrabbit/oak/json/JsonDeserializerTest.java
Fri Jul 21 05:24:25 2017
@@ -19,11 +19,14 @@
package org.apache.jackrabbit.oak.json;
+import java.math.BigDecimal;
import java.util.Collections;
import java.util.Random;
import com.google.common.collect.Lists;
+import org.apache.jackrabbit.JcrConstants;
import org.apache.jackrabbit.oak.api.Blob;
+import org.apache.jackrabbit.oak.api.PropertyState;
import org.apache.jackrabbit.oak.api.Type;
import org.apache.jackrabbit.oak.commons.json.JsopBuilder;
import org.apache.jackrabbit.oak.plugins.memory.ArrayBasedBlob;
@@ -32,9 +35,11 @@ import org.apache.jackrabbit.oak.spi.sta
import org.apache.jackrabbit.oak.spi.state.NodeState;
import org.junit.Test;
+import static java.util.Arrays.asList;
import static
org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE;
-import static org.junit.Assert.*;
-import static org.mockito.Mockito.mock;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
public class JsonDeserializerTest {
private Base64BlobSerializer blobHandler = new Base64BlobSerializer();
@@ -82,13 +87,59 @@ public class JsonDeserializerTest {
}
@Test
- public void singleProperty() throws Exception{
+ public void primaryType() throws Exception{
+ String json = "{\"jcr:primaryType\":\"oak:Unstructured\"}";
+ NodeState nodeState2 = deserialize(json);
+ assertEquals(Type.NAME,
nodeState2.getProperty(JcrConstants.JCR_PRIMARYTYPE).getType());
+ }
+
+ @Test
+ public void stringPropertyWithNamespace() throws Exception{
+ String json = "{\"name\":\"jcr:content/metadata\"}";
+ NodeState nodeState = deserialize(json);
+ PropertyState name = nodeState.getProperty("name");
+ assertEquals("jcr:content/metadata", name.getValue(Type.STRING));
+ assertEquals(Type.STRING, name.getType());
+ }
+
+ @Test
+ public void stringArrayPropertyWithNamespace() throws Exception{
+ String json = "{\"name\": [\"jcr:content/metadata\"] }";
+ NodeState nodeState = deserialize(json);
+ PropertyState name = nodeState.getProperty("name");
+ assertEquals("jcr:content/metadata", name.getValue(Type.STRING, 0));
+ assertEquals(Type.STRINGS, name.getType());
+ }
+
+ @Test
+ public void mixins() throws Exception{
+ String json = "{\"jcr:mixinTypes\": [\"oak:Unstructured\",
\"mixin:title\"]}";
+ NodeState nodeState = deserialize(json);
+ assertEquals(Type.NAMES,
nodeState.getProperty(JcrConstants.JCR_MIXINTYPES).getType());
+ assertEquals(asList("oak:Unstructured", "mixin:title"),
+
nodeState.getProperty(JcrConstants.JCR_MIXINTYPES).getValue(Type.NAMES));
+
+ }
+
+ @Test
+ public void childOrder() throws Exception{
+ String json =
"{\"jcr:primaryType\":\"nam:nt:unstructured\",\"a\":{},\"c\":{},\"b\":{}}";
+ NodeState nodeState = deserialize(json);
+
+ PropertyState childOrder = nodeState.getProperty(":childOrder");
+ assertNotNull(childOrder);
+
+ assertEquals(asList("a", "c", "b"), childOrder.getValue(Type.NAMES));
+ }
+
+ @Test
+ public void otherArrayTypes() throws Exception{
NodeBuilder builder = EMPTY_NODE.builder();
- builder.child("a").setProperty("foo3", 1.1);
- //builder.child("a").setProperty("foo3", Lists.newArrayList(true,
false), Type.BOOLEANS);
+ builder.setProperty("foo1", asList("/content", "/libs"), Type.PATHS);
+ builder.setProperty("foo2", asList(1.2, 1.4), Type.DOUBLES);
+ builder.setProperty("foo3", asList(new BigDecimal("3.14159"), new
BigDecimal("42.737")), Type.DECIMALS);
assertDeserialization(builder);
-
}
private Blob createBlob(int length) {