
org.elasticsearch.index.mapper.DocumentParserContext 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 - Open Source, Distributed, RESTful Search Engine
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
package org.elasticsearch.index.mapper;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.StringField;
import org.apache.lucene.index.IndexableField;
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.common.time.DateFormatter;
import org.elasticsearch.core.Tuple;
import org.elasticsearch.index.IndexMode;
import org.elasticsearch.index.IndexSettings;
import org.elasticsearch.index.analysis.IndexAnalyzers;
import org.elasticsearch.index.mapper.MapperService.MergeReason;
import org.elasticsearch.xcontent.FilterXContentParserWrapper;
import org.elasticsearch.xcontent.FlatteningXContentParser;
import org.elasticsearch.xcontent.XContentBuilder;
import org.elasticsearch.xcontent.XContentParser;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Context used when parsing incoming documents. Holds everything that is needed to parse a document as well as
* the lucene data structures and mappings to be dynamically created as the outcome of parsing a document.
*/
public abstract class DocumentParserContext {
/**
* Wraps a given context while allowing to override some of its behaviour by re-implementing some of the non final methods
*/
private static class Wrapper extends DocumentParserContext {
private final DocumentParserContext in;
private Wrapper(ObjectMapper parent, DocumentParserContext in) {
super(parent, parent.dynamic == null ? in.dynamic : parent.dynamic, in);
this.in = in;
}
@Override
public Iterable nonRootDocuments() {
return in.nonRootDocuments();
}
@Override
public boolean isWithinCopyTo() {
return in.isWithinCopyTo();
}
@Override
public ContentPath path() {
return in.path();
}
@Override
public XContentParser parser() {
return in.parser();
}
@Override
public LuceneDocument rootDoc() {
return in.rootDoc();
}
@Override
public LuceneDocument doc() {
return in.doc();
}
@Override
protected void addDoc(LuceneDocument doc) {
in.addDoc(doc);
}
}
/**
* Tracks the number of dynamically added mappers.
* All {@link DocumentParserContext}s that are created via {@link DocumentParserContext#createChildContext(ObjectMapper)}
* share the same mutable instance so that we can track the total size of dynamic mappers
* that are added on any level of the object graph.
*/
private static final class DynamicMapperSize {
private int dynamicMapperSize = 0;
public void add(int mapperSize) {
dynamicMapperSize += mapperSize;
}
public int get() {
return dynamicMapperSize;
}
}
/**
* Defines the scope parser is currently in.
* This is used for synthetic source related logic during parsing.
*/
private enum Scope {
SINGLETON,
ARRAY,
NESTED
}
private final MappingLookup mappingLookup;
private final MappingParserContext mappingParserContext;
private final SourceToParse sourceToParse;
private final Set ignoredFields;
private final List ignoredFieldValues;
private Scope currentScope;
private final Map> dynamicMappers;
private final DynamicMapperSize dynamicMappersSize;
private final Map dynamicObjectMappers;
private final Map> dynamicRuntimeFields;
private final DocumentDimensions dimensions;
private final ObjectMapper parent;
private final ObjectMapper.Dynamic dynamic;
private String id;
private Field version;
private final SeqNoFieldMapper.SequenceIDFields seqID;
private final Set fieldsAppliedFromTemplates;
/**
* Fields that are copied from values of other fields via copy_to.
* This per-document state is needed since it is possible
* that copy_to field in introduced using a dynamic template
* in this document and therefore is not present in mapping yet.
*/
private final Set copyToFields;
// Indicates if the source for this context has been marked to be recorded. Applies to synthetic source only.
private boolean recordedSource;
private DocumentParserContext(
MappingLookup mappingLookup,
MappingParserContext mappingParserContext,
SourceToParse sourceToParse,
Set ignoreFields,
List ignoredFieldValues,
Scope currentScope,
Map> dynamicMappers,
Map dynamicObjectMappers,
Map> dynamicRuntimeFields,
String id,
Field version,
SeqNoFieldMapper.SequenceIDFields seqID,
DocumentDimensions dimensions,
ObjectMapper parent,
ObjectMapper.Dynamic dynamic,
Set fieldsAppliedFromTemplates,
Set copyToFields,
DynamicMapperSize dynamicMapperSize,
boolean recordedSource
) {
this.mappingLookup = mappingLookup;
this.mappingParserContext = mappingParserContext;
this.sourceToParse = sourceToParse;
this.ignoredFields = ignoreFields;
this.ignoredFieldValues = ignoredFieldValues;
this.currentScope = currentScope;
this.dynamicMappers = dynamicMappers;
this.dynamicObjectMappers = dynamicObjectMappers;
this.dynamicRuntimeFields = dynamicRuntimeFields;
this.id = id;
this.version = version;
this.seqID = seqID;
this.dimensions = dimensions;
this.parent = parent;
this.dynamic = dynamic;
this.fieldsAppliedFromTemplates = fieldsAppliedFromTemplates;
this.copyToFields = copyToFields;
this.dynamicMappersSize = dynamicMapperSize;
this.recordedSource = recordedSource;
}
private DocumentParserContext(ObjectMapper parent, ObjectMapper.Dynamic dynamic, DocumentParserContext in) {
this(
in.mappingLookup,
in.mappingParserContext,
in.sourceToParse,
in.ignoredFields,
in.ignoredFieldValues,
in.currentScope,
in.dynamicMappers,
in.dynamicObjectMappers,
in.dynamicRuntimeFields,
in.id,
in.version,
in.seqID,
in.dimensions,
parent,
dynamic,
in.fieldsAppliedFromTemplates,
in.copyToFields,
in.dynamicMappersSize,
in.recordedSource
);
}
protected DocumentParserContext(
MappingLookup mappingLookup,
MappingParserContext mappingParserContext,
SourceToParse source,
ObjectMapper parent,
ObjectMapper.Dynamic dynamic
) {
this(
mappingLookup,
mappingParserContext,
source,
new HashSet<>(),
new ArrayList<>(),
Scope.SINGLETON,
new HashMap<>(),
new HashMap<>(),
new HashMap<>(),
null,
null,
SeqNoFieldMapper.SequenceIDFields.emptySeqID(),
DocumentDimensions.fromIndexSettings(mappingParserContext.getIndexSettings()),
parent,
dynamic,
new HashSet<>(),
new HashSet<>(mappingLookup.fieldTypesLookup().getCopyToDestinationFields()),
new DynamicMapperSize(),
false
);
}
public final IndexSettings indexSettings() {
return mappingParserContext.getIndexSettings();
}
public final IndexAnalyzers indexAnalyzers() {
return mappingParserContext.getIndexAnalyzers();
}
public final RootObjectMapper root() {
return this.mappingLookup.getMapping().getRoot();
}
public final ObjectMapper parent() {
return parent;
}
public final MappingLookup mappingLookup() {
return mappingLookup;
}
public final MetadataFieldMapper getMetadataMapper(String mapperName) {
return mappingLookup.getMapping().getMetadataMapperByName(mapperName);
}
public final MappingParserContext dynamicTemplateParserContext(DateFormatter dateFormatter) {
return mappingParserContext.createDynamicTemplateContext(dateFormatter);
}
public final SourceToParse sourceToParse() {
return this.sourceToParse;
}
public final String routing() {
return mappingParserContext.getIndexSettings().getMode() == IndexMode.TIME_SERIES ? null : sourceToParse.routing();
}
/**
* Add the given {@code field} to the set of ignored fields.
*/
public final void addIgnoredField(String field) {
ignoredFields.add(field);
}
/**
* Return the collection of fields that have been ignored so far.
*/
public final Collection getIgnoredFields() {
return Collections.unmodifiableCollection(ignoredFields);
}
/**
* Add the given ignored values to the corresponding list.
*/
public final void addIgnoredField(IgnoredSourceFieldMapper.NameValue values) {
if (canAddIgnoredField()) {
// Skip tracking the source for this field twice, it's already tracked for the entire parsing subcontext.
ignoredFieldValues.add(values);
}
}
final void removeLastIgnoredField(String name) {
if (ignoredFieldValues.isEmpty() == false && ignoredFieldValues.get(ignoredFieldValues.size() - 1).name().equals(name)) {
ignoredFieldValues.remove(ignoredFieldValues.size() - 1);
}
}
/**
* Return the collection of values for fields that have been ignored so far.
*/
public final Collection getIgnoredFieldValues() {
return Collections.unmodifiableCollection(ignoredFieldValues);
}
/**
* Adds an ignored field from the parser context, capturing an object or an array.
*
* In case of nested arrays, i.e. capturing an array within an array, elements tracked as ignored fields may interfere with
* the rest, as ignored source contents take precedence over regular field contents with the same leaf name. To prevent
* missing array elements from synthetic source, all array elements get recorded in ignored source. Otherwise, just the value in
* the current parsing context gets captured.
*
* In both cases, a new parser sub-context gets created from the current {@link DocumentParserContext} and returned, indicating
* that the source for the sub-context has been captured, to avoid double-storing parts of its contents to ignored source.
*/
public final DocumentParserContext addIgnoredFieldFromContext(IgnoredSourceFieldMapper.NameValue ignoredFieldWithNoSource)
throws IOException {
if (canAddIgnoredField()) {
assert ignoredFieldWithNoSource != null;
assert ignoredFieldWithNoSource.value() == null;
Tuple tuple = XContentDataHelper.cloneSubContext(this);
addIgnoredField(ignoredFieldWithNoSource.cloneWithValue(XContentDataHelper.encodeXContentBuilder(tuple.v2())));
return tuple.v1();
}
return this;
}
/**
* Wraps {@link XContentDataHelper#encodeToken}, disabling dot expansion from {@link DotExpandingXContentParser}.
* This helps avoid producing duplicate names in the same scope, due to expanding dots to objects.
* For instance: { "a.b": "b", "a.c": "c" } => { "a": { "b": "b" }, "a": { "c": "c" } }
* This can happen when storing parts of document source that are not indexed (e.g. disabled objects).
*/
BytesRef encodeFlattenedToken() throws IOException {
boolean old = path().isWithinLeafObject();
path().setWithinLeafObject(true);
BytesRef encoded = XContentDataHelper.encodeToken(parser());
path().setWithinLeafObject(old);
return encoded;
}
/**
* Clones the current context to mark it as an array, if it's not already marked, or restore it if it's within a nested object.
* Applies to synthetic source only.
*/
public final DocumentParserContext maybeCloneForArray(Mapper mapper) throws IOException {
if (canAddIgnoredField()
&& mapper instanceof ObjectMapper
&& mapper instanceof NestedObjectMapper == false
&& currentScope != Scope.ARRAY) {
DocumentParserContext subcontext = switchParser(parser());
subcontext.currentScope = Scope.ARRAY;
return subcontext;
}
return this;
}
/**
* Creates a sub-context from the current {@link DocumentParserContext} to indicate that the source for the sub-context has been
* recorded and avoid duplicate recording for parts of the sub-context. Applies to synthetic source only.
*/
public final DocumentParserContext cloneWithRecordedSource() throws IOException {
if (canAddIgnoredField()) {
DocumentParserContext subcontext = createChildContext(parent());
subcontext.setRecordedSource(); // Avoids double-storing parts of the source for the same parser subtree.
return subcontext;
}
return this;
}
/**
* Add the given {@code field} to the _field_names field
*
* Use this if an exists query run against the field cannot use docvalues
* or norms.
*/
public final void addToFieldNames(String field) {
FieldNamesFieldMapper fieldNamesFieldMapper = (FieldNamesFieldMapper) getMetadataMapper(FieldNamesFieldMapper.NAME);
if (fieldNamesFieldMapper != null) {
fieldNamesFieldMapper.addFieldNames(this, field);
}
}
public final Field version() {
return this.version;
}
public final void version(Field version) {
this.version = version;
}
public final String id() {
if (id == null) {
assert false : "id field mapper has not set the id";
throw new IllegalStateException("id field mapper has not set the id");
}
return id;
}
public final void id(String id) {
this.id = id;
}
public final SeqNoFieldMapper.SequenceIDFields seqID() {
return this.seqID;
}
final void setRecordedSource() {
this.recordedSource = true;
}
final boolean getRecordedSource() {
return recordedSource;
}
public final boolean canAddIgnoredField() {
return mappingLookup.isSourceSynthetic() && recordedSource == false && indexSettings().getSkipIgnoredSourceWrite() == false;
}
Mapper.SourceKeepMode sourceKeepModeFromIndexSettings() {
return indexSettings().sourceKeepMode();
}
/**
* Description on the document being parsed used in error messages. Not
* called unless there is an error.
*/
public final String documentDescription() {
IdFieldMapper idMapper = (IdFieldMapper) getMetadataMapper(IdFieldMapper.NAME);
return idMapper.documentDescription(this);
}
public Mapper getMapper(String name) {
return parent.getMapper(name);
}
public ObjectMapper.Dynamic dynamic() {
return dynamic;
}
public void markFieldAsAppliedFromTemplate(String fieldName) {
fieldsAppliedFromTemplates.add(fieldName);
}
public boolean isFieldAppliedFromTemplate(String name) {
return fieldsAppliedFromTemplates.contains(name);
}
public void markFieldAsCopyTo(String fieldName) {
copyToFields.add(fieldName);
}
public boolean isCopyToDestinationField(String name) {
return copyToFields.contains(name);
}
public Set getCopyToFields() {
return copyToFields;
}
/**
* Add a new mapper dynamically created while parsing.
*
* @return returns true
if the mapper could be created, false
if the dynamic mapper has been ignored due to
* the field limit
* @throws IllegalArgumentException if the field limit has been exceeded.
* This can happen when dynamic is set to {@link ObjectMapper.Dynamic#TRUE} or {@link ObjectMapper.Dynamic#RUNTIME}.
*/
public final boolean addDynamicMapper(Mapper mapper) {
// eagerly check object depth limit here to avoid stack overflow errors
if (mapper instanceof ObjectMapper) {
MappingLookup.checkObjectDepthLimit(indexSettings().getMappingDepthLimit(), mapper.fullPath());
}
// eagerly check field name limit here to avoid OOM errors
// only check fields that are not already mapped or tracked in order to avoid hitting field limit too early via double-counting
// note that existing fields can also receive dynamic mapping updates (e.g. constant_keyword to fix the value)
if (mappingLookup.getMapper(mapper.fullPath()) == null
&& mappingLookup.objectMappers().containsKey(mapper.fullPath()) == false
&& dynamicMappers.containsKey(mapper.fullPath()) == false) {
int mapperSize = mapper.getTotalFieldsCount();
int additionalFieldsToAdd = getNewFieldsSize() + mapperSize;
if (indexSettings().isIgnoreDynamicFieldsBeyondLimit()) {
if (mappingLookup.exceedsLimit(indexSettings().getMappingTotalFieldsLimit(), additionalFieldsToAdd)) {
if (canAddIgnoredField()) {
try {
addIgnoredField(
IgnoredSourceFieldMapper.NameValue.fromContext(this, mapper.fullPath(), encodeFlattenedToken())
);
} catch (IOException e) {
throw new IllegalArgumentException("failed to parse field [" + mapper.fullPath() + " ]", e);
}
}
addIgnoredField(mapper.fullPath());
return false;
}
} else {
mappingLookup.checkFieldLimit(indexSettings().getMappingTotalFieldsLimit(), additionalFieldsToAdd);
}
dynamicMappersSize.add(mapperSize);
}
if (mapper instanceof ObjectMapper objectMapper) {
dynamicObjectMappers.put(objectMapper.fullPath(), objectMapper);
// dynamic object mappers may have been obtained from applying a dynamic template, in which case their definition may contain
// sub-fields as well as sub-objects that need to be added to the mappings
for (Mapper submapper : objectMapper.mappers.values()) {
// we could potentially skip the step of adding these to the dynamic mappers, because their parent is already added to
// that list, and what is important is that all of the intermediate objects are added to the dynamic object mappers so that
// they can be looked up once sub-fields need to be added to them. For simplicity, we treat these like any other object
addDynamicMapper(submapper);
}
}
// TODO we may want to stop adding object mappers to the dynamic mappers list: most times they will be mapped when parsing their
// sub-fields (see ObjectMapper.Builder#addDynamic), which causes extra work as the two variants of the same object field
// will be merged together when creating the final dynamic update. The only cases where object fields need extra treatment are
// dynamically mapped objects when the incoming document defines no sub-fields in them:
// 1) by default, they would be empty containers in the mappings, is it then important to map them?
// 2) they can be the result of applying a dynamic template which may define sub-fields or set dynamic, enabled or subobjects.
dynamicMappers.computeIfAbsent(mapper.fullPath(), k -> new ArrayList<>()).add(mapper);
return true;
}
/*
* Returns an approximation of the number of dynamically mapped fields and runtime fields that will be added to the mapping.
* This is to validate early and to fail fast during document parsing.
* There will be another authoritative (but more expensive) validation step when making the actual update mapping request.
* During the mapping update, the actual number fields is determined by counting the total number of fields of the merged mapping.
* Therefore, both over-counting and under-counting here is not critical.
* However, in order for users to get to the field limit, we should try to be as close as possible to the actual field count.
* If we under-count fields here, we may only know that we exceed the field limit during the mapping update.
* This can happen when merging the mappers for the same field results in a mapper with a larger size than the individual mappers.
* This leads to document rejection instead of ignoring fields above the limit
* if ignore_dynamic_beyond_limit is configured for the index.
* If we over-count the fields (for example by counting all mappers with the same name),
* we may reject fields earlier than necessary and before actually hitting the field limit.
*/
int getNewFieldsSize() {
return dynamicMappersSize.get() + dynamicRuntimeFields.size();
}
/**
* @return true if either {@link #getDynamicMappers} or {@link #getDynamicRuntimeFields()} will return a non-empty result
*/
public final boolean hasDynamicMappersOrRuntimeFields() {
return hasDynamicMappers() || dynamicRuntimeFields.isEmpty() == false;
}
/**
* @return true if either {@link #getDynamicMappers} will return a non-empty mapper list
*/
public final boolean hasDynamicMappers() {
return dynamicMappers.isEmpty() == false;
}
/**
* Get dynamic mappers created as a result of parsing an incoming document. Responsible for exposing all the newly created
* fields that need to be merged into the existing mappings. Used to create the required mapping update at the end of document parsing.
* Consists of a all {@link Mapper}s that will need to be added to their respective parent {@link ObjectMapper}s in order
* to become part of the resulting dynamic mapping update.
*/
public final List getDynamicMappers() {
return dynamicMappers.values().stream().flatMap(List::stream).toList();
}
/**
* Returns the dynamic Consists of a flat set of {@link Mapper}s associated with a field name that will need to be added to their
* respective parent {@link ObjectMapper}s in order to become part of the resulting dynamic mapping update.
* @param fieldName Full field name with dot-notation.
* @return List of Mappers or null
*/
public final List getDynamicMappers(String fieldName) {
return dynamicMappers.get(fieldName);
}
public void updateDynamicMappers(String name, List mappers) {
dynamicMappers.remove(name);
mappers.forEach(this::addDynamicMapper);
}
/**
* Get a dynamic object mapper by name. Allows consumers to lookup objects that have been dynamically added as a result
* of parsing an incoming document. Used to find the parent object for new fields that are being dynamically mapped whose parent is
* also not mapped yet. Such new fields will need to be dynamically added to their parent according to its dynamic behaviour.
* Holds a flat set of object mappers, meaning that an object field named foo.bar
can be looked up directly with its
* dotted name.
*/
final ObjectMapper getDynamicObjectMapper(String name) {
return dynamicObjectMappers.get(name);
}
/**
* Add a new runtime field dynamically created while parsing.
* We use the same set for both new indexed and new runtime fields,
* because for dynamic mappings, a new field can be either mapped
* as runtime or indexed, but never both.
*/
final boolean addDynamicRuntimeField(RuntimeField runtimeField) {
if (dynamicRuntimeFields.containsKey(runtimeField.name()) == false) {
if (indexSettings().isIgnoreDynamicFieldsBeyondLimit()) {
if (mappingLookup.exceedsLimit(indexSettings().getMappingTotalFieldsLimit(), getNewFieldsSize() + 1)) {
addIgnoredField(runtimeField.name());
return false;
}
} else {
mappingLookup.checkFieldLimit(indexSettings().getMappingTotalFieldsLimit(), getNewFieldsSize() + 1);
}
}
dynamicRuntimeFields.computeIfAbsent(runtimeField.name(), k -> new ArrayList<>(1)).add(runtimeField);
return true;
}
/**
* Get dynamic runtime fields created while parsing. Holds a flat set of {@link RuntimeField}s.
* Runtime fields get dynamically mapped when {@link org.elasticsearch.index.mapper.ObjectMapper.Dynamic#RUNTIME} is used,
* or when dynamic templates specify a runtime
section.
*/
public final List getDynamicRuntimeFields() {
return dynamicRuntimeFields.values().stream().flatMap(List::stream).toList();
}
/**
* Returns an Iterable over all non-root documents. If there are no non-root documents
* the iterable will return an empty iterator.
*/
public abstract Iterable nonRootDocuments();
/**
* @return a RootObjectMapper.Builder to be used to construct a dynamic mapping update
*/
public final RootObjectMapper.Builder updateRoot() {
return mappingLookup.getMapping().getRoot().newBuilder(mappingParserContext.getIndexSettings().getIndexVersionCreated());
}
public boolean isWithinCopyTo() {
return false;
}
boolean inArrayScope() {
return currentScope == Scope.ARRAY;
}
public final DocumentParserContext createChildContext(ObjectMapper parent) {
return new Wrapper(parent, this);
}
/**
* Return a new context that will be used within a nested document.
*/
public final DocumentParserContext createNestedContext(NestedObjectMapper nestedMapper) {
if (isWithinCopyTo()) {
// nested context will already have been set up for copy_to fields
return this;
}
final LuceneDocument doc = new LuceneDocument(nestedMapper.fullPath(), doc());
// We need to add the uid or id to this nested Lucene document too,
// If we do not do this then when a document gets deleted only the root Lucene document gets deleted and
// not the nested Lucene documents! Besides the fact that we would have zombie Lucene documents, the ordering of
// documents inside the Lucene index (document blocks) will be incorrect, as nested documents of different root
// documents are then aligned with other root documents. This will lead to the nested query, sorting, aggregations
// and inner hits to fail or yield incorrect results.
IndexableField idField = doc.getParent().getField(IdFieldMapper.NAME);
if (idField != null) {
// We just need to store the id as indexed field, so that IndexWriter#deleteDocuments(term) can then
// delete it when the root document is deleted too.
// NOTE: we don't support nested fields in tsdb so it's safe to assume the standard id mapper.
doc.add(new StringField(IdFieldMapper.NAME, idField.binaryValue(), Field.Store.NO));
} else {
throw new IllegalStateException("The root document of a nested document should have an _id field");
}
doc.add(NestedPathFieldMapper.field(indexSettings().getIndexVersionCreated(), nestedMapper.nestedTypePath()));
addDoc(doc);
return switchDoc(doc);
}
/**
* Return a new context that has the provided document as the current document.
*/
public final DocumentParserContext switchDoc(final LuceneDocument document) {
DocumentParserContext cloned = new Wrapper(this.parent, this) {
@Override
public LuceneDocument doc() {
return document;
}
};
cloned.currentScope = Scope.NESTED;
return cloned;
}
/**
* Return a context for copy_to directives
* @param copyToField the name of the field to copy to
* @param doc the document to target
*/
public final DocumentParserContext createCopyToContext(String copyToField, LuceneDocument doc) throws IOException {
ContentPath path = new ContentPath();
XContentParser parser = DotExpandingXContentParser.expandDots(new CopyToParser(copyToField, parser()), path);
return new Wrapper(root(), this) {
@Override
public ContentPath path() {
return path;
}
@Override
public XContentParser parser() {
return parser;
}
@Override
public boolean isWithinCopyTo() {
return true;
}
@Override
public LuceneDocument doc() {
return doc;
}
};
}
/**
* Return a context for flattening subobjects
* @param fieldName the name of the field to be flattened
*/
public final DocumentParserContext createFlattenContext(String fieldName) {
XContentParser flatteningParser = new FlatteningXContentParser(parser(), fieldName);
return new Wrapper(this.parent(), this) {
@Override
public XContentParser parser() {
return flatteningParser;
}
};
}
/**
* Clone this context, replacing the XContentParser with the passed one
* @param parser the replacement parser
* @return a new context with a replaced parser
*/
public final DocumentParserContext switchParser(XContentParser parser) {
return new Wrapper(this.parent, this) {
@Override
public XContentParser parser() {
return parser;
}
};
}
/**
* The collection of dimensions for this document.
*/
public DocumentDimensions getDimensions() {
return dimensions;
}
public abstract ContentPath path();
/**
* Creates a context to build dynamic mappers
*/
public final MapperBuilderContext createDynamicMapperBuilderContext() {
String p = path().pathAsText("");
if (p.endsWith(".")) {
p = p.substring(0, p.length() - 1);
}
boolean containsDimensions = false;
ObjectMapper objectMapper = mappingLookup.objectMappers().get(p);
if (objectMapper instanceof PassThroughObjectMapper passThroughObjectMapper) {
containsDimensions = passThroughObjectMapper.containsDimensions();
}
return new MapperBuilderContext(
p,
mappingLookup.isSourceSynthetic(),
mappingLookup.isDataStreamTimestampFieldEnabled(),
containsDimensions,
dynamic,
MergeReason.MAPPING_UPDATE,
false
);
}
public abstract XContentParser parser();
public abstract LuceneDocument rootDoc();
public abstract LuceneDocument doc();
protected abstract void addDoc(LuceneDocument doc);
/**
* Find a dynamic mapping template for the given field and its matching type
*
* @param fieldName the name of the field
* @param matchType the expecting matchType of the field
* @return the matching template; otherwise returns null
* @throws DocumentParsingException if the given field has a dynamic template name specified, but no template matches that name.
*/
public final DynamicTemplate findDynamicTemplate(String fieldName, DynamicTemplate.XContentFieldType matchType) {
final String pathAsString = path().pathAsText(fieldName);
final String matchTemplateName = sourceToParse().dynamicTemplates().get(pathAsString);
for (DynamicTemplate template : root().dynamicTemplates()) {
if (template.match(matchTemplateName, pathAsString, fieldName, matchType)) {
return template;
}
}
if (matchTemplateName != null) {
throw new DocumentParsingException(
parser().getTokenLocation(),
"Can't find dynamic template for dynamic template name [" + matchTemplateName + "] of field [" + pathAsString + "]"
);
}
return null;
}
// XContentParser that wraps an existing parser positioned on a value,
// and a field name, and returns a stream that looks like { 'field' : 'value' }
private static class CopyToParser extends FilterXContentParserWrapper {
enum State {
FIELD,
VALUE
}
private State state = State.FIELD;
private final String field;
CopyToParser(String fieldName, XContentParser in) {
super(in);
this.field = fieldName;
assert in.currentToken().isValue() || in.currentToken() == Token.VALUE_NULL;
}
@Override
public Token nextToken() throws IOException {
if (state == State.FIELD) {
state = State.VALUE;
return delegate().currentToken();
}
return Token.END_OBJECT;
}
@Override
public Token currentToken() {
if (state == State.FIELD) {
return Token.FIELD_NAME;
}
return delegate().currentToken();
}
@Override
public String currentName() throws IOException {
return field;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy