Skip to content

Commit

Permalink
Add buffered lookahead for Jackson (opensearch-project#338)
Browse files Browse the repository at this point in the history
Signed-off-by: luneo7 <luneo7@gmail.com>
  • Loading branch information
luneo7 authored Mar 16, 2023
1 parent 5c444d2 commit aaeb185
Show file tree
Hide file tree
Showing 12 changed files with 599 additions and 42 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
- Document HTTP/2 support ([#330](https://github.com/opensearch-project/opensearch-java/pull/330))
- Require two maintainers to approve release ([#383](https://github.com/opensearch-project/opensearch-java/pull/383))
- Add support for mapping limit settings ([#382](https://github.com/opensearch-project/opensearch-java/pull/382))
- Add buffered lookahead for Jackson ([#338](https://github.com/opensearch-project/opensearch-java/pull/338))

### Dependencies
- Bumps `classgraph` from 4.8.149 to 4.8.154
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/

/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. 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.
*/

/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/

package org.opensearch.client.json;

import jakarta.json.JsonArray;
import jakarta.json.JsonObject;
import jakarta.json.JsonValue;
import jakarta.json.stream.JsonLocation;
import jakarta.json.stream.JsonParser;

import java.math.BigDecimal;
import java.util.Map;
import java.util.stream.Stream;

public abstract class DelegatingJsonParser implements JsonParser {

private final JsonParser parser;

public DelegatingJsonParser(JsonParser parser) {
this.parser = parser;
}

@Override
public boolean hasNext() {
return parser.hasNext();
}

@Override
public Event next() {
return parser.next();
}

@Override
public String getString() {
return parser.getString();
}

@Override
public boolean isIntegralNumber() {
return parser.isIntegralNumber();
}

@Override
public int getInt() {
return parser.getInt();
}

@Override
public long getLong() {
return parser.getLong();
}

@Override
public BigDecimal getBigDecimal() {
return parser.getBigDecimal();
}

@Override
public JsonLocation getLocation() {
return parser.getLocation();
}

@Override
public JsonObject getObject() {
return parser.getObject();
}

@Override
public JsonValue getValue() {
return parser.getValue();
}

@Override
public JsonArray getArray() {
return parser.getArray();
}

@Override
public Stream<JsonValue> getArrayStream() {
return parser.getArrayStream();
}

@Override
public Stream<Map.Entry<String, JsonValue>> getObjectStream() {
return parser.getObjectStream();
}

@Override
public Stream<JsonValue> getValueStream() {
return parser.getValueStream();
}

@Override
public void skipArray() {
parser.skipArray();
}

@Override
public void skipObject() {
parser.skipObject();
}

@Override
public void close() {
parser.close();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/

/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. 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.
*/

/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/

package org.opensearch.client.json;

import jakarta.json.stream.JsonLocation;

class JsonLocationImpl implements JsonLocation {

private final long columnNo;
private final long lineNo;
private final long offset;

JsonLocationImpl(long lineNo, long columnNo, long streamOffset) {
this.lineNo = lineNo;
this.columnNo = columnNo;
this.offset = streamOffset;
}

@Override
public long getLineNumber() {
return lineNo;
}

@Override
public long getColumnNumber() {
return columnNo;
}

@Override
public long getStreamOffset() {
return offset;
}

@Override
public String toString() {
return "(line no=" + lineNo + ", column no=" + columnNo + ", offset=" + offset + ")";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import jakarta.json.JsonString;
import jakarta.json.JsonValue;
import jakarta.json.stream.JsonGenerator;
import jakarta.json.stream.JsonLocation;
import jakarta.json.stream.JsonParser;
import jakarta.json.stream.JsonParser.Event;
import jakarta.json.stream.JsonParsingException;
Expand Down Expand Up @@ -135,20 +136,46 @@ public static <T> void serialize(T value, JsonGenerator generator, @Nullable Jso
public static Map.Entry<String, JsonParser> lookAheadFieldValue(
String name, String defaultValue, JsonParser parser, JsonpMapper mapper
) {
// FIXME: need a buffering parser wrapper so that we don't roundtrip through a JsonObject and a String
// FIXME: resulting parser should return locations that are offset with the original parser's location
JsonObject object = parser.getObject();
String result = object.getString(name, null);
JsonLocation location = parser.getLocation();

if (result == null) {
result = defaultValue;
}
if (parser instanceof LookAheadJsonParser) {
// Fast buffered path
Map.Entry<String, JsonParser> result = ((LookAheadJsonParser) parser).lookAheadFieldValue(name, defaultValue);
if (result.getKey() == null) {
throw new JsonParsingException("Property '" + name + "' not found", location);
}
return result;

if (result == null) {
throw new JsonParsingException("Property '" + name + "' not found", parser.getLocation());
} else {
// Unbuffered path: parse the object into a JsonObject, then extract the value and parse it again
JsonObject object = parser.getObject();
String result = object.getString(name, null);

if (result == null) {
result = defaultValue;
}

if (result == null) {
throw new JsonParsingException("Property '" + name + "' not found", location);
}

JsonParser newParser = objectParser(object, mapper);

// Pin location to the start of the look ahead, as the new parser will return locations in its own buffer
newParser = new DelegatingJsonParser(newParser) {
@Override
public JsonLocation getLocation() {
return new JsonLocationImpl(location.getLineNumber(), location.getColumnNumber(), location.getStreamOffset()) {
@Override
public String toString() {
return "(in object at " + super.toString().substring(1);
}
};
}
};

return new AbstractMap.SimpleImmutableEntry<>(result, newParser);
}

return new AbstractMap.SimpleImmutableEntry<>(result, objectParser(object, mapper));
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/

/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. 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.
*/

/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/

package org.opensearch.client.json;

import jakarta.json.stream.JsonParser;

import java.util.Map;

public interface LookAheadJsonParser extends JsonParser {

/**
* Look ahead the value of a text property in the JSON stream. The parser must be on the {@code START_OBJECT} event.
*
* @param name the field name to look up.
* @param defaultValue default value if the field is not found.
* @return a pair containing the field value (or {@code null} if not found), and a parser to be used to read the JSON object.
*/
Map.Entry<String, JsonParser> lookAheadFieldValue(String name, String defaultValue);

/**
* In union types, find the variant to be used by looking up property names in the JSON stream until we find one that
* uniquely identifies the variant.
*
* @param <Variant> the type of variant descriptors used by the caller.
* @param variants a map of variant descriptors, keyed by the property name that uniquely identifies the variant.
* @return a pair containing the variant descriptor (or {@code null} if not found), and a parser to be used to read the JSON object.
*/
<Variant> Map.Entry<Variant, JsonParser> findVariant(Map<String, Variant> variants);
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,17 +71,19 @@ public EnumSet<JsonParser.Event> acceptedEvents() {

@Override
public T deserialize(JsonParser parser, JsonpMapper mapper) {
if (mapper.<JsonpDeserializer<T>>attribute(name) == null) {
throw new JsonParsingException("Missing deserializer", parser.getLocation());
JsonpDeserializer<T> deserializer = mapper.attribute(name);
if (deserializer == null) {
throw new JsonParsingException("Missing deserializer for generic type: " + name, parser.getLocation());
}
return mapper.<JsonpDeserializer<T>>attribute(name).deserialize(parser, mapper);
return deserializer.deserialize(parser, mapper);
}

@Override
public T deserialize(JsonParser parser, JsonpMapper mapper, JsonParser.Event event) {
if (mapper.<JsonpDeserializer<T>>attribute(name) == null) {
throw new JsonParsingException("Missing deserializer", parser.getLocation());
JsonpDeserializer<T> deserializer = mapper.attribute(name);
if (deserializer == null) {
throw new JsonParsingException("Missing deserializer for generic type: " + name, parser.getLocation());
}
return mapper.<JsonpDeserializer<T>>attribute(name).deserialize(parser, mapper, event);
return deserializer.deserialize(parser, mapper, event);
}
}
Loading

0 comments on commit aaeb185

Please sign in to comment.