org.elasticsearch.index.mapper.DotExpandingXContentParser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of elasticsearch Show documentation
Show all versions of elasticsearch Show documentation
Elasticsearch subproject :server
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
package org.elasticsearch.index.mapper;
import org.elasticsearch.core.CheckedFunction;
import org.elasticsearch.xcontent.FilterXContentParser;
import org.elasticsearch.xcontent.FilterXContentParserWrapper;
import org.elasticsearch.xcontent.XContentLocation;
import org.elasticsearch.xcontent.XContentParser;
import org.elasticsearch.xcontent.XContentSubParser;
import java.io.IOException;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.List;
import java.util.Map;
import java.util.function.BooleanSupplier;
import java.util.function.Supplier;
/**
* An XContentParser that reinterprets field names containing dots as an object structure.
*
* A field name named {@code "foo.bar.baz":...} will be parsed instead as {@code 'foo':{'bar':{'baz':...}}}.
* The token location is preserved so that error messages refer to the original content being parsed.
* This parser can output duplicate keys, but that is fine given that it's used for document parsing. The mapping
* lookups will return the same mapper/field type, and we never load incoming documents in a map where duplicate
* keys would end up overriding each other.
*/
class DotExpandingXContentParser extends FilterXContentParserWrapper {
private static final class WrappingParser extends FilterXContentParser {
private final BooleanSupplier isWithinLeafObject;
final Deque parsers = new ArrayDeque<>();
WrappingParser(XContentParser in, BooleanSupplier isWithinLeafObject) throws IOException {
this.isWithinLeafObject = isWithinLeafObject;
parsers.push(in);
if (in.currentToken() == Token.FIELD_NAME) {
expandDots();
}
}
@Override
public Token nextToken() throws IOException {
Token token;
while ((token = delegate().nextToken()) == null) {
parsers.pop();
if (parsers.isEmpty()) {
return null;
}
}
if (token != Token.FIELD_NAME) {
return token;
}
expandDots();
return Token.FIELD_NAME;
}
private void expandDots() throws IOException {
// this handles fields that belong to objects that can't hold subobjects, where the document specifies
// the object holding the flat fields
// e.g. { "metrics.service": { "time.max" : 10 } } with service having subobjects set to false
if (isWithinLeafObject.getAsBoolean()) {
return;
}
XContentParser delegate = delegate();
String field = delegate.currentName();
String[] subpaths = splitAndValidatePath(field);
if (subpaths.length == 0) {
throw new IllegalArgumentException("field name cannot contain only dots: [" + field + "]");
}
// Corner case: if the input has a single trailing '.', eg 'field.', then we will get a single
// subpath due to the way String.split() works. We can only return fast here if this is not
// the case
// TODO make this case throw an error instead? https://github.com/elastic/elasticsearch/issues/28948
if (subpaths.length == 1 && field.endsWith(".") == false) {
return;
}
XContentLocation location = delegate.getTokenLocation();
Token token = delegate.nextToken();
if (token == Token.END_OBJECT || token == Token.END_ARRAY) {
throw new IllegalStateException("Expecting START_OBJECT or START_ARRAY or VALUE but got [" + token + "]");
} else {
XContentParser subParser = token == Token.START_OBJECT || token == Token.START_ARRAY
? new XContentSubParser(delegate)
: new SingletonValueXContentParser(delegate);
parsers.push(new DotExpandingXContentParser(subParser, subpaths, location, isWithinLeafObject));
}
}
@Override
protected XContentParser delegate() {
return parsers.peek();
}
@Override
public Map map() throws IOException {
throw new UnsupportedOperationException();
}
@Override
public Map mapOrdered() throws IOException {
throw new UnsupportedOperationException();
}
@Override
public Map mapStrings() throws IOException {
throw new UnsupportedOperationException();
}
@Override
public Map map(Supplier