
com.hazelcast.query.impl.getters.AbstractJsonGetter Maven / Gradle / Ivy
The newest version!
/*
* Copyright (c) 2008-2024, Hazelcast, Inc. All Rights Reserved.
*
* Licensed 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.
*/
package com.hazelcast.query.impl.getters;
import com.hazelcast.shaded.com.fasterxml.jackson.core.JsonParser;
import com.hazelcast.shaded.com.fasterxml.jackson.core.JsonToken;
import com.hazelcast.shaded.com.fasterxml.jackson.core.JsonTokenId;
import com.hazelcast.core.HazelcastException;
import com.hazelcast.internal.json.JsonValue;
import com.hazelcast.internal.json.NonTerminalJsonValue;
import com.hazelcast.internal.serialization.impl.NavigableJsonInputAdapter;
import com.hazelcast.internal.util.collection.WeightedEvictableList.WeightedItem;
import com.hazelcast.json.internal.JsonPattern;
import com.hazelcast.json.internal.JsonSchemaHelper;
import com.hazelcast.json.internal.JsonSchemaNode;
import java.io.IOException;
import java.util.List;
import static com.hazelcast.shaded.com.fasterxml.jackson.core.JsonToken.START_OBJECT;
public abstract class AbstractJsonGetter extends Getter {
private static final int QUERY_CONTEXT_CACHE_MAX_SIZE = 40;
private static final int QUERY_CONTEXT_CACHE_CLEANUP_SIZE = 3;
/**
* The number of times this getter will try to use previously observed
* patterns. The getter tries most commonly observed patterns first.
* If known patterns do not work, then a new pattern is created for
* this object.
*/
private static final int PATTERN_TRY_COUNT = 2;
private final JsonGetterContextCache contextCache =
new JsonGetterContextCache(QUERY_CONTEXT_CACHE_MAX_SIZE, QUERY_CONTEXT_CACHE_CLEANUP_SIZE);
AbstractJsonGetter(Getter parent) {
super(parent);
}
public static JsonPathCursor getPath(String attributePath) {
return JsonPathCursor.createCursor(attributePath);
}
/**
* Converts a JsonValue object to its corresponding Java object.
* Type mappings are as shown below
*
* - {@link com.hazelcast.internal.json.JsonString} to {@link String}
* - {@link com.hazelcast.internal.json.Json#NULL} to {@code null}
* - {@link com.hazelcast.internal.json.Json#TRUE} to {@code true}
* - {@link com.hazelcast.internal.json.Json#FALSE} to {@code false}
* -
* {@link com.hazelcast.internal.json.JsonNumber} to either
* {@code long} or {@code double}
*
*
*
* @param value
* @return
*/
public static Object convertFromJsonValue(JsonValue value) {
if (value == null) {
return null;
} else if (value.isNumber()) {
if (value.toString().contains(".")) {
return value.asDouble();
} else {
return value.asLong();
}
} else if (value.isBoolean()) {
return value.asBoolean();
} else if (value.isNull()) {
return null;
} else if (value.isString()) {
return value.asString();
} else if (value == NonTerminalJsonValue.INSTANCE) {
return value;
}
throw new IllegalArgumentException("Unknown Json type: " + value);
}
abstract JsonParser createParser(Object obj) throws IOException;
@Override
Object getValue(Object obj) {
throw new HazelcastException("Path agnostic value extraction is not supported");
}
@Override
public Object getValue(Object obj, String attributePath) {
JsonPathCursor pathCursor = getPath(attributePath);
try (JsonParser parser = createParser(obj)) {
parser.nextToken();
while (pathCursor.getNext() != null) {
if (pathCursor.isArray()) {
if (pathCursor.isAny()) {
return getMultiValue(parser, pathCursor);
}
JsonToken token = parser.currentToken();
if (token != JsonToken.START_ARRAY) {
return null;
}
token = parser.nextToken();
int arrayIndex = pathCursor.getArrayIndex();
for (int j = 0; j < arrayIndex; j++) {
if (token == JsonToken.END_ARRAY) {
return null;
}
parser.skipChildren();
token = parser.nextToken();
}
} else {
// non array case
if (!findAttribute(parser, pathCursor)) {
return null;
}
}
}
return convertJsonTokenToValue(parser);
} catch (IOException e) {
// Just return null in case of exception. Json strings are allowed to be invalid.
return null;
}
}
@Override
Object getValue(Object obj, String attributePath, Object metadata) throws Exception {
if (metadata == null) {
return getValue(obj, attributePath);
}
JsonSchemaNode schemaNode = (JsonSchemaNode) metadata;
NavigableJsonInputAdapter adapter = annotate(obj);
JsonGetterContext queryContext = contextCache.getContext(attributePath);
List> patternsSnapshot = queryContext.getPatternListSnapshot();
JsonPathCursor pathCursor = queryContext.newJsonPathCursor();
JsonPattern knownPattern;
for (int i = 0; i < PATTERN_TRY_COUNT && i < patternsSnapshot.size(); i++) {
WeightedItem patternWeightedItem = patternsSnapshot.get(i);
knownPattern = patternWeightedItem.getItem();
JsonValue value = JsonSchemaHelper.findValueWithPattern(adapter, schemaNode, knownPattern, pathCursor);
pathCursor.reset();
if (value != null) {
queryContext.voteFor(patternWeightedItem);
return convertFromJsonValue(value);
}
}
knownPattern = JsonSchemaHelper.createPattern(adapter, schemaNode, pathCursor);
pathCursor.reset();
if (knownPattern != null) {
if (knownPattern.hasAny()) {
return getValue(obj, attributePath);
}
queryContext.addOrVoteForPattern(knownPattern);
return convertFromJsonValue(JsonSchemaHelper.findValueWithPattern(adapter, schemaNode, knownPattern, pathCursor));
}
return null;
}
@Override
Class> getReturnType() {
throw new IllegalArgumentException("Non applicable for Json getters");
}
@Override
boolean isCacheable() {
return false;
}
int getContextCacheSize() {
return contextCache.getCacheSize();
}
protected abstract NavigableJsonInputAdapter annotate(Object object);
/**
* Looks for the attribute with the given name only in current
* object. If found, parser points to the value of the given
* attribute when this method returns. If given path does not exist
* in the current level, then parser points to matching
* {@code JsonToken.END_OBJECT} of the current object.
*
* Assumes the parser points to a {@code JsonToken.START_OBJECT}
*
* @param parser
* @param pathCursor
* @return {@code true} if given attribute name exists in the current object
* @throws IOException
*/
private boolean findAttribute(JsonParser parser, JsonPathCursor pathCursor) throws IOException {
JsonToken token = parser.getCurrentToken();
if (token != START_OBJECT) {
return false;
}
while (true) {
token = parser.nextToken();
if (token == JsonToken.END_OBJECT) {
return false;
}
if (token == JsonToken.FIELD_NAME && pathCursor.getCurrent().equals(parser.getCurrentName())) {
// current token matched, advance to next token before returning
parser.nextToken();
return true;
} else if (token.isStructStart()) {
// we want to match attributes, skip nested structures
// (in particular the ones that are values of other attributes)
parser.skipChildren();
}
}
}
/**
* Traverses given array. If {@code pathCursor#getNext()} is
* {@code null}, this method adds all the scalar values in current
* array to the result. Otherwise, it traverses all objects in
* given array and adds their scalar values named
* {@code pathCursor#getNext()} to the result.
*
* Assumes the parser points to an array.
*
* @param parser
* @param pathCursor
* @return All matches in the current array that conform to
* [any].lastPath search
* @throws IOException
*/
@SuppressWarnings("checkstyle:cyclomaticcomplexity")
private MultiResult getMultiValue(JsonParser parser,
JsonPathCursor pathCursor) throws IOException {
// consume [any] from cursor
pathCursor.getNext();
// stack for cursor state and backtracking
int initialState = pathCursor.saveState();
MultiResult
© 2015 - 2025 Weber Informatics LLC | Privacy Policy