You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by nf...@apache.org on 2024/08/02 07:39:28 UTC
(jackrabbit-oak) branch trunk updated: OAK-10989 - Performance improvements to JSON parsing #1612
This is an automated email from the ASF dual-hosted git repository.
nfsantos pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/jackrabbit-oak.git
The following commit(s) were added to refs/heads/trunk by this push:
new c4b5a6b2fd OAK-10989 - Performance improvements to JSON parsing #1612
c4b5a6b2fd is described below
commit c4b5a6b2fdabff92b3154b0d553274262285d237
Author: Nuno Santos <ns...@adobe.com>
AuthorDate: Fri Aug 2 09:39:23 2024 +0200
OAK-10989 - Performance improvements to JSON parsing #1612
- when reading a property, check for the most common types first (String).
- make instances reusable by creating a new MemoryNodeBuilder for every call to deserialize
- Remove usages of Guava collections.
- Move the methods in the inner class DeserializationSupport to top level methods in JsonDeserializer, as these inner class was not accessed anywhere outside JsonDeserializer and did not contain any state.
---
.../document/flatfile/NodeStateEntryReader.java | 6 +-
.../jackrabbit/oak/json/JsonDeserializer.java | 178 +++++++++------------
2 files changed, 82 insertions(+), 102 deletions(-)
diff --git a/oak-run-commons/src/main/java/org/apache/jackrabbit/oak/index/indexer/document/flatfile/NodeStateEntryReader.java b/oak-run-commons/src/main/java/org/apache/jackrabbit/oak/index/indexer/document/flatfile/NodeStateEntryReader.java
index 87852d0fc3..96732d4e96 100644
--- a/oak-run-commons/src/main/java/org/apache/jackrabbit/oak/index/indexer/document/flatfile/NodeStateEntryReader.java
+++ b/oak-run-commons/src/main/java/org/apache/jackrabbit/oak/index/indexer/document/flatfile/NodeStateEntryReader.java
@@ -29,10 +29,11 @@ import org.apache.jackrabbit.oak.spi.state.NodeState;
import static org.apache.jackrabbit.oak.commons.StringUtils.estimateMemoryUsage;
public class NodeStateEntryReader {
- private final BlobDeserializer blobDeserializer;
+ private final JsonDeserializer des;
public NodeStateEntryReader(BlobStore blobStore) {
- this.blobDeserializer = new BlobIdSerializer(blobStore);
+ BlobDeserializer blobDeserializer = new BlobIdSerializer(blobStore);
+ this.des = new JsonDeserializer(blobDeserializer);
}
public NodeStateEntry read(String line) {
@@ -43,7 +44,6 @@ public class NodeStateEntryReader {
}
protected NodeState parseState(String part) {
- JsonDeserializer des = new JsonDeserializer(blobDeserializer);
return des.deserialize(part);
}
}
diff --git a/oak-store-spi/src/main/java/org/apache/jackrabbit/oak/json/JsonDeserializer.java b/oak-store-spi/src/main/java/org/apache/jackrabbit/oak/json/JsonDeserializer.java
index 49f7d4137a..f10abb1835 100644
--- a/oak-store-spi/src/main/java/org/apache/jackrabbit/oak/json/JsonDeserializer.java
+++ b/oak-store-spi/src/main/java/org/apache/jackrabbit/oak/json/JsonDeserializer.java
@@ -19,12 +19,12 @@
package org.apache.jackrabbit.oak.json;
+import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import javax.jcr.PropertyType;
-import org.apache.jackrabbit.guava.common.collect.Lists;
import org.apache.jackrabbit.oak.api.PropertyState;
import org.apache.jackrabbit.oak.api.Type;
import org.apache.jackrabbit.oak.commons.LongUtils;
@@ -39,8 +39,6 @@ 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 org.apache.jackrabbit.guava.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;
@@ -48,26 +46,45 @@ 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;
+ /**
+ * Provides support for inferring types for some common property name and types
+ */
+ private static final Set<String> NAME_PROPS = Set.of(JCR_PRIMARYTYPE, JCR_MIXINTYPES);
+ private static final String ORDERABLE_TYPE = NT_UNSTRUCTURED;
+ private static final String OAK_CHILD_ORDER = ":childOrder";
- private JsonDeserializer(BlobDeserializer blobHandler, NodeBuilder builder, DeserializationSupport support) {
- this.blobHandler = blobHandler;
- this.builder = builder;
- this.deserializationSupport = support;
+ private static Type<?> inferPropertyType(String propertyName, String jsonString) {
+ if (NAME_PROPS.contains(propertyName) && hasSingleColon(jsonString)) {
+ return Type.NAME;
+ }
+ return Type.UNDEFINED;
}
- public JsonDeserializer(BlobDeserializer blobHandler) {
- this(blobHandler, EMPTY_NODE.builder(), DeserializationSupport.INSTANCE);
+ private static boolean hasOrderableChildren(NodeBuilder builder) {
+ PropertyState primaryType = builder.getProperty(JCR_PRIMARYTYPE);
+ return primaryType != null && ORDERABLE_TYPE.equals(primaryType.getValue(Type.NAME));
+ }
+
+ private static 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 ':'.
+ int colonCount = 0;
+ for (int i = 0; i < jsonString.length() && colonCount < 2; i++) {
+ if (jsonString.charAt(i) == ':') {
+ colonCount += 1;
+ }
+ }
+ return colonCount == 1;
}
- public JsonDeserializer(BlobDeserializer blobHandler, NodeBuilder builder) {
- this(blobHandler, builder, DeserializationSupport.INSTANCE);
+ private final BlobDeserializer blobHandler;
+
+ public JsonDeserializer(BlobDeserializer blobHandler) {
+ this.blobHandler = blobHandler;
}
- public NodeState deserialize(String json){
+ public NodeState deserialize(String json) {
JsopReader reader = new JsopTokenizer(json);
reader.read('{');
NodeState state = deserialize(reader);
@@ -75,13 +92,14 @@ public class JsonDeserializer {
return state;
}
- public NodeState deserialize(JsopReader reader){
+ public NodeState deserialize(JsopReader reader) {
+ NodeBuilder builder = EMPTY_NODE.builder();
readNode(reader, builder);
return builder.getNodeState();
}
private void readNode(JsopReader reader, NodeBuilder builder) {
- List<String> childNames = Lists.newArrayList();
+ List<String> childNames = new ArrayList<>();
if (!reader.matches('}')) {
do {
String key = reader.readString();
@@ -98,8 +116,7 @@ public class JsonDeserializer {
reader.read('}');
}
- if (deserializationSupport.hasOrderableChildren(builder)
- && !builder.hasProperty(OAK_CHILD_ORDER)){
+ if (hasOrderableChildren(builder) && !builder.hasProperty(OAK_CHILD_ORDER)) {
builder.setProperty(OAK_CHILD_ORDER, childNames, Type.NAMES);
}
@@ -112,48 +129,46 @@ public class JsonDeserializer {
* @return new property state
*/
private PropertyState readProperty(String name, JsopReader reader) {
- if (reader.matches(JsopReader.NUMBER)) {
- String number = reader.getToken();
- Long maybeLong = LongUtils.tryParse(number);
- if (maybeLong == null) {
- return new DoublePropertyState(name, Double.parseDouble(number));
- } else {
- return new LongPropertyState(name, maybeLong);
- }
- } else if (reader.matches(JsopReader.TRUE)) {
- return BooleanPropertyState.booleanProperty(name, true);
- } else if (reader.matches(JsopReader.FALSE)) {
- return BooleanPropertyState.booleanProperty(name, false);
- } else if (reader.matches(JsopReader.STRING)) {
+ // Test for the most common types first
+ if (reader.matches(JsopReader.STRING)) {
String jsonString = reader.getToken();
- Type inferredType = deserializationSupport.inferPropertyType(name, jsonString);
+ Type<?> inferredType = inferPropertyType(name, jsonString);
if (jsonString.startsWith(TypeCodes.EMPTY_ARRAY)) {
- int type = PropertyType.valueFromName(
- jsonString.substring(TypeCodes.EMPTY_ARRAY.length()));
- return PropertyStates.createProperty(
- name, emptyList(), Type.fromTag(type, true));
+ int type = PropertyType.valueFromName(jsonString.substring(TypeCodes.EMPTY_ARRAY.length()));
+ return PropertyStates.createProperty(name, List.of(), Type.fromTag(type, true));
}
int split = TypeCodes.split(jsonString);
if (split != -1) {
int type = TypeCodes.decodeType(split, jsonString);
String value = TypeCodes.decodeName(split, jsonString);
if (type == PropertyType.BINARY) {
- return BinaryPropertyState.binaryProperty(
- name, blobHandler.deserialize(value));
+ 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;
+ if (type == PropertyType.UNDEFINED) {
+ Type<?> oakType = inferredType != Type.UNDEFINED ? inferredType : Type.STRING;
return createProperty(name, jsonString, oakType);
}
return createProperty(name, value, type);
}
} else {
- Type oakType = inferredType != Type.UNDEFINED ? inferredType : Type.STRING;
+ Type<?> oakType = inferredType != Type.UNDEFINED ? inferredType : Type.STRING;
return createProperty(name, jsonString, oakType);
}
+ } else if (reader.matches(JsopReader.NUMBER)) {
+ String number = reader.getToken();
+ Long maybeLong = LongUtils.tryParse(number);
+ if (maybeLong == null) {
+ return new DoublePropertyState(name, Double.parseDouble(number));
+ } else {
+ return new LongPropertyState(name, maybeLong);
+ }
+ } else if (reader.matches(JsopReader.TRUE)) {
+ return BooleanPropertyState.booleanProperty(name, true);
+ } else if (reader.matches(JsopReader.FALSE)) {
+ return BooleanPropertyState.booleanProperty(name, false);
} else {
throw new IllegalArgumentException("Unexpected token: " + reader.getToken());
}
@@ -167,27 +182,12 @@ public class JsonDeserializer {
*/
private PropertyState readArrayProperty(String name, JsopReader reader) {
int type = PropertyType.STRING;
- List<Object> values = Lists.newArrayList();
+ List<Object> values = new ArrayList<>();
while (!reader.matches(']')) {
- if (reader.matches(JsopReader.NUMBER)) {
- String number = reader.getToken();
- Long maybeLong = LongUtils.tryParse(number);
- if (maybeLong == null) {
- type = PropertyType.DOUBLE;
- values.add(Double.parseDouble(number));
- } else {
- type = PropertyType.LONG;
- values.add(maybeLong);
- }
- } else if (reader.matches(JsopReader.TRUE)) {
- type = PropertyType.BOOLEAN;
- values.add(true);
- } else if (reader.matches(JsopReader.FALSE)) {
- type = PropertyType.BOOLEAN;
- values.add(false);
- } else if (reader.matches(JsopReader.STRING)) {
+ // Test for the most common types first
+ if (reader.matches(JsopReader.STRING)) {
String jsonString = reader.getToken();
- Type inferredType = deserializationSupport.inferPropertyType(name, jsonString);
+ Type<?> inferredType = inferPropertyType(name, jsonString);
int split = TypeCodes.split(jsonString);
if (split != -1) {
type = TypeCodes.decodeType(split, jsonString);
@@ -199,7 +199,7 @@ public class JsonDeserializer {
} else if (type == PropertyType.DECIMAL) {
values.add(Conversions.convert(value).toDecimal());
} else {
- if (type == PropertyType.UNDEFINED){
+ 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;
@@ -212,6 +212,22 @@ public class JsonDeserializer {
type = inferredType != Type.UNDEFINED ? inferredType.tag() : PropertyType.STRING;
values.add(jsonString);
}
+ } else if (reader.matches(JsopReader.NUMBER)) {
+ String number = reader.getToken();
+ Long maybeLong = LongUtils.tryParse(number);
+ if (maybeLong == null) {
+ type = PropertyType.DOUBLE;
+ values.add(Double.parseDouble(number));
+ } else {
+ type = PropertyType.LONG;
+ values.add(maybeLong);
+ }
+ } else if (reader.matches(JsopReader.TRUE)) {
+ type = PropertyType.BOOLEAN;
+ values.add(Boolean.TRUE);
+ } else if (reader.matches(JsopReader.FALSE)) {
+ type = PropertyType.BOOLEAN;
+ values.add(Boolean.FALSE);
} else {
throw new IllegalArgumentException("Unexpected token: " + reader.getToken());
}
@@ -219,40 +235,4 @@ public class JsonDeserializer {
}
return createProperty(name, values, Type.fromTag(type, true));
}
-
-
- /**
- * 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 ':'.
- int colonCount = 0;
- for (int i = 0; i < jsonString.length() && colonCount < 2; i++) {
- if (jsonString.charAt(i) == ':') {
- colonCount += 1;
- }
- }
- return colonCount == 1;
- }
- }
-
}