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) {


Reply via email to