Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add buffered lookahead for Jackson #338

Merged
merged 1 commit into from
Mar 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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