diff --git a/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/FilterXContentParser.java b/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/FilterXContentParser.java new file mode 100644 index 0000000000000..de56b5716b3e9 --- /dev/null +++ b/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/FilterXContentParser.java @@ -0,0 +1,246 @@ +/* + * 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.common.xcontent; + +import org.elasticsearch.common.CheckedFunction; +import org.elasticsearch.common.RestApiVersion; + +import java.io.IOException; +import java.nio.CharBuffer; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; + +/** + * Filters an existing XContentParser by using a delegate + */ +public abstract class FilterXContentParser implements XContentParser { + + protected final XContentParser in; + + protected FilterXContentParser(XContentParser in) { + this.in = in; + } + + @Override + public XContentType contentType() { + return in.contentType(); + } + + @Override + public Token nextToken() throws IOException { + return in.nextToken(); + } + + @Override + public void skipChildren() throws IOException { + in.skipChildren(); + } + + @Override + public Token currentToken() { + return in.currentToken(); + } + + @Override + public String currentName() throws IOException { + return in.currentName(); + } + + @Override + public Map map() throws IOException { + return in.map(); + } + + @Override + public Map mapOrdered() throws IOException { + return in.mapOrdered(); + } + + @Override + public Map mapStrings() throws IOException { + return in.mapStrings(); + } + + @Override + public Map map( + Supplier> mapFactory, CheckedFunction mapValueParser) throws IOException { + return in.map(mapFactory, mapValueParser); + } + + @Override + public List list() throws IOException { + return in.list(); + } + + @Override + public List listOrderedMap() throws IOException { + return in.listOrderedMap(); + } + + @Override + public String text() throws IOException { + return in.text(); + } + + @Override + public String textOrNull() throws IOException { + return in.textOrNull(); + } + + @Override + public CharBuffer charBufferOrNull() throws IOException { + return in.charBufferOrNull(); + } + + @Override + public CharBuffer charBuffer() throws IOException { + return in.charBuffer(); + } + + @Override + public Object objectText() throws IOException { + return in.objectText(); + } + + @Override + public Object objectBytes() throws IOException { + return in.objectBytes(); + } + + @Override + public boolean hasTextCharacters() { + return in.hasTextCharacters(); + } + + @Override + public char[] textCharacters() throws IOException { + return in.textCharacters(); + } + + @Override + public int textLength() throws IOException { + return in.textLength(); + } + + @Override + public int textOffset() throws IOException { + return in.textOffset(); + } + + @Override + public Number numberValue() throws IOException { + return in.numberValue(); + } + + @Override + public NumberType numberType() throws IOException { + return in.numberType(); + } + + @Override + public short shortValue(boolean coerce) throws IOException { + return in.shortValue(coerce); + } + + @Override + public int intValue(boolean coerce) throws IOException { + return in.intValue(coerce); + } + + @Override + public long longValue(boolean coerce) throws IOException { + return in.longValue(coerce); + } + + @Override + public float floatValue(boolean coerce) throws IOException { + return in.floatValue(coerce); + } + + @Override + public double doubleValue(boolean coerce) throws IOException { + return in.doubleValue(coerce); + } + + @Override + public short shortValue() throws IOException { + return in.shortValue(); + } + + @Override + public int intValue() throws IOException { + return in.intValue(); + } + + @Override + public long longValue() throws IOException { + return in.longValue(); + } + + @Override + public float floatValue() throws IOException { + return in.floatValue(); + } + + @Override + public double doubleValue() throws IOException { + return in.doubleValue(); + } + + @Override + public boolean isBooleanValue() throws IOException { + return in.isBooleanValue(); + } + + @Override + public boolean booleanValue() throws IOException { + return in.booleanValue(); + } + + @Override + public byte[] binaryValue() throws IOException { + return in.binaryValue(); + } + + @Override + public XContentLocation getTokenLocation() { + return in.getTokenLocation(); + } + + @Override + public T namedObject(Class categoryClass, String name, Object context) throws IOException { + return in.namedObject(categoryClass, name, context); + } + + @Override + public NamedXContentRegistry getXContentRegistry() { + return in.getXContentRegistry(); + } + + @Override + public boolean isClosed() { + return in.isClosed(); + } + + @Override + public void close() throws IOException { + in.close(); + } + + @Override + public RestApiVersion getRestApiVersion() { + return in.getRestApiVersion(); + } + + @Override + public DeprecationHandler getDeprecationHandler() { + return in.getDeprecationHandler(); + } +} diff --git a/modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/MatchOnlyTextFieldMapper.java b/modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/MatchOnlyTextFieldMapper.java index 9b6d26abc4292..592a2bbce7f76 100644 --- a/modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/MatchOnlyTextFieldMapper.java +++ b/modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/MatchOnlyTextFieldMapper.java @@ -318,12 +318,7 @@ public FieldMapper.Builder getMergeBuilder() { @Override protected void parseCreateField(ParseContext context) throws IOException { - final String value; - if (context.externalValueSet()) { - value = context.externalValue().toString(); - } else { - value = context.parser().textOrNull(); - } + final String value = context.parser().textOrNull(); if (value == null) { return; diff --git a/modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/RankFeatureFieldMapper.java b/modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/RankFeatureFieldMapper.java index 1f0f06a697001..8c27e3c4fecd6 100644 --- a/modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/RankFeatureFieldMapper.java +++ b/modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/RankFeatureFieldMapper.java @@ -136,10 +136,7 @@ public RankFeatureFieldType fieldType() { @Override protected void parseCreateField(ParseContext context) throws IOException { float value; - if (context.externalValueSet()) { - Object v = context.externalValue(); - value = objectToFloat(v); - } else if (context.parser().currentToken() == Token.VALUE_NULL) { + if (context.parser().currentToken() == Token.VALUE_NULL) { // skip return; } else { diff --git a/modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/RankFeaturesFieldMapper.java b/modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/RankFeaturesFieldMapper.java index d331d53985b64..73cf6314827b8 100644 --- a/modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/RankFeaturesFieldMapper.java +++ b/modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/RankFeaturesFieldMapper.java @@ -56,7 +56,7 @@ name, new RankFeaturesFieldType(buildFullName(contentPath), meta.getValue(), pos } } - public static final TypeParser PARSER = new TypeParser((n, c) -> new Builder(n)); + public static final TypeParser PARSER = new TypeParser((n, c) -> new Builder(n), notInMultiFields(CONTENT_TYPE)); public static final class RankFeaturesFieldType extends MappedFieldType { @@ -117,9 +117,6 @@ public RankFeaturesFieldType fieldType() { @Override public void parse(ParseContext context) throws IOException { - if (context.externalValueSet()) { - throw new IllegalArgumentException("[rank_features] fields can't be used in multi-fields"); - } if (context.parser().currentToken() != Token.START_OBJECT) { throw new IllegalArgumentException("[rank_features] fields must be json objects, expected a START_OBJECT but got: " + diff --git a/modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/ScaledFloatFieldMapper.java b/modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/ScaledFloatFieldMapper.java index 7a6d4d2994153..37f349844b0c5 100644 --- a/modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/ScaledFloatFieldMapper.java +++ b/modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/ScaledFloatFieldMapper.java @@ -316,9 +316,7 @@ protected void parseCreateField(ParseContext context) throws IOException { XContentParser parser = context.parser(); Object value; Number numericValue = null; - if (context.externalValueSet()) { - value = context.externalValue(); - } else if (parser.currentToken() == Token.VALUE_NULL) { + if (parser.currentToken() == Token.VALUE_NULL) { value = null; } else if (coerce.value() && parser.currentToken() == Token.VALUE_STRING diff --git a/modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/SearchAsYouTypeFieldMapper.java b/modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/SearchAsYouTypeFieldMapper.java index 5e86b3d067eb3..f53fcafe52461 100644 --- a/modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/SearchAsYouTypeFieldMapper.java +++ b/modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/SearchAsYouTypeFieldMapper.java @@ -571,7 +571,7 @@ public SearchAsYouTypeFieldMapper(String simpleName, @Override protected void parseCreateField(ParseContext context) throws IOException { - final String value = context.externalValueSet() ? context.externalValue().toString() : context.parser().textOrNull(); + final String value = context.parser().textOrNull(); if (value == null) { return; } diff --git a/modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/TokenCountFieldMapper.java b/modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/TokenCountFieldMapper.java index 3d563d97aa18a..793d60ffdfb9f 100644 --- a/modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/TokenCountFieldMapper.java +++ b/modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/TokenCountFieldMapper.java @@ -111,12 +111,7 @@ protected TokenCountFieldMapper(String simpleName, MappedFieldType defaultFieldT @Override protected void parseCreateField(ParseContext context) throws IOException { - final String value; - if (context.externalValueSet()) { - value = context.externalValue().toString(); - } else { - value = context.parser().textOrNull(); - } + final String value = context.parser().textOrNull(); if (value == null && nullValue == null) { return; diff --git a/modules/mapper-extras/src/test/java/org/elasticsearch/index/mapper/RankFeaturesFieldMapperTests.java b/modules/mapper-extras/src/test/java/org/elasticsearch/index/mapper/RankFeaturesFieldMapperTests.java index 45e60a8ea341f..a1b14312266b7 100644 --- a/modules/mapper-extras/src/test/java/org/elasticsearch/index/mapper/RankFeaturesFieldMapperTests.java +++ b/modules/mapper-extras/src/test/java/org/elasticsearch/index/mapper/RankFeaturesFieldMapperTests.java @@ -21,6 +21,8 @@ import java.util.List; import java.util.Map; +import static org.hamcrest.Matchers.containsString; + public class RankFeaturesFieldMapperTests extends MapperTestCase { @Override @@ -136,6 +138,18 @@ public void testRejectMultiValuedFields() throws MapperParsingException, IOExcep "the same document", e.getCause().getMessage()); } + public void testCannotBeUsedInMultifields() { + Exception e = expectThrows(MapperParsingException.class, () -> createMapperService(fieldMapping(b -> { + b.field("type", "keyword"); + b.startObject("fields"); + b.startObject("feature"); + b.field("type", "rank_features"); + b.endObject(); + b.endObject(); + }))); + assertThat(e.getMessage(), containsString("Field [feature] of type [rank_features] can't be used in multifields")); + } + @Override protected Object generateRandomInputValue(MappedFieldType ft) { assumeFalse("Test implemented in a follow up", true); diff --git a/plugins/analysis-icu/src/main/java/org/elasticsearch/index/mapper/ICUCollationKeywordFieldMapper.java b/plugins/analysis-icu/src/main/java/org/elasticsearch/index/mapper/ICUCollationKeywordFieldMapper.java index e18401cd4d855..ec1fdecf3f15a 100644 --- a/plugins/analysis-icu/src/main/java/org/elasticsearch/index/mapper/ICUCollationKeywordFieldMapper.java +++ b/plugins/analysis-icu/src/main/java/org/elasticsearch/index/mapper/ICUCollationKeywordFieldMapper.java @@ -437,15 +437,11 @@ public FieldMapper.Builder getMergeBuilder() { @Override protected void parseCreateField(ParseContext context) throws IOException { final String value; - if (context.externalValueSet()) { - value = context.externalValue().toString(); + XContentParser parser = context.parser(); + if (parser.currentToken() == XContentParser.Token.VALUE_NULL) { + value = nullValue; } else { - XContentParser parser = context.parser(); - if (parser.currentToken() == XContentParser.Token.VALUE_NULL) { - value = nullValue; - } else { - value = parser.textOrNull(); - } + value = parser.textOrNull(); } if (value == null || value.length() > ignoreAbove) { diff --git a/plugins/mapper-annotated-text/src/main/java/org/elasticsearch/index/mapper/annotatedtext/AnnotatedTextFieldMapper.java b/plugins/mapper-annotated-text/src/main/java/org/elasticsearch/index/mapper/annotatedtext/AnnotatedTextFieldMapper.java index 1adf8412d2ca1..3bb3ed7816728 100644 --- a/plugins/mapper-annotated-text/src/main/java/org/elasticsearch/index/mapper/annotatedtext/AnnotatedTextFieldMapper.java +++ b/plugins/mapper-annotated-text/src/main/java/org/elasticsearch/index/mapper/annotatedtext/AnnotatedTextFieldMapper.java @@ -517,12 +517,7 @@ protected AnnotatedTextFieldMapper(String simpleName, FieldType fieldType, Annot @Override protected void parseCreateField(ParseContext context) throws IOException { - final String value; - if (context.externalValueSet()) { - value = context.externalValue().toString(); - } else { - value = context.parser().textOrNull(); - } + final String value = context.parser().textOrNull(); if (value == null) { return; diff --git a/plugins/mapper-murmur3/src/main/java/org/elasticsearch/index/mapper/murmur3/Murmur3FieldMapper.java b/plugins/mapper-murmur3/src/main/java/org/elasticsearch/index/mapper/murmur3/Murmur3FieldMapper.java index 75dad11e6ce50..00adef515b82a 100644 --- a/plugins/mapper-murmur3/src/main/java/org/elasticsearch/index/mapper/murmur3/Murmur3FieldMapper.java +++ b/plugins/mapper-murmur3/src/main/java/org/elasticsearch/index/mapper/murmur3/Murmur3FieldMapper.java @@ -123,12 +123,7 @@ protected String contentType() { @Override protected void parseCreateField(ParseContext context) throws IOException { - final Object value; - if (context.externalValueSet()) { - value = context.externalValue(); - } else { - value = context.parser().textOrNull(); - } + final String value = context.parser().textOrNull(); if (value != null) { final BytesRef bytes = new BytesRef(value.toString()); final long hash = MurmurHash3.hash128(bytes.bytes, bytes.offset, bytes.length, 0, new MurmurHash3.Hash128()).h1; diff --git a/server/src/internalClusterTest/java/org/elasticsearch/index/mapper/ExternalValuesMapperIntegrationIT.java b/server/src/internalClusterTest/java/org/elasticsearch/index/mapper/ExternalValuesMapperIntegrationIT.java deleted file mode 100644 index 6d17400b6eae5..0000000000000 --- a/server/src/internalClusterTest/java/org/elasticsearch/index/mapper/ExternalValuesMapperIntegrationIT.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * 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.action.search.SearchResponse; -import org.elasticsearch.common.xcontent.XContentFactory; -import org.elasticsearch.index.query.QueryBuilders; -import org.elasticsearch.plugins.Plugin; -import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder; -import org.elasticsearch.test.ESIntegTestCase; - -import java.util.Collection; -import java.util.Collections; - -import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertSearchResponse; -import static org.hamcrest.Matchers.equalTo; - -public class ExternalValuesMapperIntegrationIT extends ESIntegTestCase { - @Override - protected Collection> nodePlugins() { - return Collections.singletonList(ExternalMapperPlugin.class); - } - - public void testHighlightingOnCustomString() throws Exception { - prepareCreate("test-idx").setMapping( - XContentFactory.jsonBuilder().startObject().startObject("_doc") - .startObject("properties") - .startObject("field").field("type", FakeStringFieldMapper.CONTENT_TYPE).endObject() - .endObject() - .endObject().endObject()).execute().get(); - - index("test-idx", "1", XContentFactory.jsonBuilder() - .startObject() - .field("field", "Every day is exactly the same") - .endObject()); - refresh(); - - SearchResponse response; - // test if the highlighting is excluded when we use wildcards - response = client().prepareSearch("test-idx") - .setQuery(QueryBuilders.matchQuery("field", "exactly the same")) - .highlighter(new HighlightBuilder().field("*")) - .execute().actionGet(); - assertSearchResponse(response); - assertThat(response.getHits().getTotalHits().value, equalTo(1L)); - assertThat(response.getHits().getAt(0).getHighlightFields().size(), equalTo(0)); - - // make sure it is not excluded when we explicitly provide the fieldname - response = client().prepareSearch("test-idx") - .setQuery(QueryBuilders.matchQuery("field", "exactly the same")) - .highlighter(new HighlightBuilder().field("field")) - .execute().actionGet(); - assertSearchResponse(response); - assertThat(response.getHits().getTotalHits().value, equalTo(1L)); - assertThat(response.getHits().getAt(0).getHighlightFields().size(), equalTo(1)); - assertThat(response.getHits().getAt(0).getHighlightFields().get("field").fragments()[0].string(), equalTo("Every day is " + - "exactly the same")); - - // make sure it is not excluded when we explicitly provide the fieldname and a wildcard - response = client().prepareSearch("test-idx") - .setQuery(QueryBuilders.matchQuery("field", "exactly the same")) - .highlighter(new HighlightBuilder().field("*").field("field")) - .execute().actionGet(); - assertSearchResponse(response); - assertThat(response.getHits().getTotalHits().value, equalTo(1L)); - assertThat(response.getHits().getAt(0).getHighlightFields().size(), equalTo(1)); - assertThat(response.getHits().getAt(0).getHighlightFields().get("field").fragments()[0].string(), equalTo("Every day is " + - "exactly the same")); - } - - public void testExternalValues() throws Exception { - prepareCreate("test-idx").setMapping( - XContentFactory.jsonBuilder().startObject().startObject("_doc") - .startObject("properties") - .startObject("field").field("type", ExternalMapperPlugin.EXTERNAL).endObject() - .endObject() - .endObject().endObject()).execute().get(); - - index("test-idx", "1", XContentFactory.jsonBuilder() - .startObject() - .field("field", "1234") - .endObject()); - refresh(); - - SearchResponse response; - - response = client().prepareSearch("test-idx") - .setPostFilter(QueryBuilders.termQuery("field.bool", "true")) - .execute().actionGet(); - - assertThat(response.getHits().getTotalHits().value, equalTo((long) 1)); - - response = client().prepareSearch("test-idx") - .setPostFilter(QueryBuilders.termQuery("field.field", "foo")) - .execute().actionGet(); - - assertThat(response.getHits().getTotalHits().value, equalTo((long) 1)); - } - - public void testExternalValuesWithMultifield() throws Exception { - prepareCreate("test-idx").setMapping( - XContentFactory.jsonBuilder().startObject().startObject("_doc").startObject("properties") - .startObject("f") - .field("type", ExternalMapperPlugin.EXTERNAL_UPPER) - .startObject("fields") - .startObject("g") - .field("type", "keyword") - .field("store", true) - .endObject() - .endObject() - .endObject() - .endObject().endObject().endObject()).execute().get(); - - indexDoc("test-idx", "1", "f", "This is my text"); - refresh(); - - SearchResponse response = client().prepareSearch("test-idx") - .setQuery(QueryBuilders.termQuery("f.g", "FOO BAR")) - .execute().actionGet(); - - assertThat(response.getHits().getTotalHits().value, equalTo((long) 1)); - } -} diff --git a/server/src/main/java/org/elasticsearch/index/mapper/BinaryFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/BinaryFieldMapper.java index e912b462694bc..c1c87f762de6b 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/BinaryFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/BinaryFieldMapper.java @@ -141,15 +141,10 @@ protected void parseCreateField(ParseContext context) throws IOException { if (stored == false && hasDocValues == false) { return; } - byte[] value = context.parseExternalValue(byte[].class); - if (value == null) { - if (context.parser().currentToken() == XContentParser.Token.VALUE_NULL) { - return; - } else { - value = context.parser().binaryValue(); - } + if (context.parser().currentToken() == XContentParser.Token.VALUE_NULL) { + return; } - indexValue(context, value); + indexValue(context, context.parser().binaryValue()); } public void indexValue(ParseContext context, byte[] value) { diff --git a/server/src/main/java/org/elasticsearch/index/mapper/BooleanFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/BooleanFieldMapper.java index 461f5e7c29edd..cba7ca4aa38f9 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/BooleanFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/BooleanFieldMapper.java @@ -266,16 +266,14 @@ protected void parseCreateField(ParseContext context) throws IOException { return; } - Boolean value = context.parseExternalValue(Boolean.class); - if (value == null) { - XContentParser.Token token = context.parser().currentToken(); - if (token == XContentParser.Token.VALUE_NULL) { - if (nullValue != null) { - value = nullValue; - } - } else { - value = context.parser().booleanValue(); + Boolean value = null; + XContentParser.Token token = context.parser().currentToken(); + if (token == XContentParser.Token.VALUE_NULL) { + if (nullValue != null) { + value = nullValue; } + } else { + value = context.parser().booleanValue(); } indexValue(context, value); } diff --git a/server/src/main/java/org/elasticsearch/index/mapper/CompletionFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/CompletionFieldMapper.java index 704d2c3b1dd83..94547da873ddd 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/CompletionFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/CompletionFieldMapper.java @@ -24,10 +24,12 @@ import org.elasticsearch.common.logging.DeprecationLogger; import org.elasticsearch.common.unit.Fuzziness; import org.elasticsearch.common.util.set.Sets; +import org.elasticsearch.common.xcontent.FilterXContentParser; import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentParser.NumberType; import org.elasticsearch.common.xcontent.XContentParser.Token; +import org.elasticsearch.common.xcontent.support.MapXContentParser; import org.elasticsearch.index.analysis.AnalyzerScope; import org.elasticsearch.index.analysis.NamedAnalyzer; import org.elasticsearch.index.query.SearchExecutionContext; @@ -36,6 +38,7 @@ import org.elasticsearch.search.suggest.completion.context.ContextMappings; import java.io.IOException; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -339,9 +342,7 @@ public void parse(ParseContext context) throws IOException { Token token = parser.currentToken(); Map inputMap = new HashMap<>(1); - if (context.externalValueSet()) { - inputMap = getInputMapFromExternalValue(context); - } else if (token == Token.VALUE_NULL) { // ignore null values + if (token == Token.VALUE_NULL) { // ignore null values return; } else if (token == Token.START_ARRAY) { while ((token = parser.nextToken()) != Token.END_ARRAY) { @@ -378,27 +379,11 @@ public void parse(ParseContext context) throws IOException { context.addToFieldNames(fieldType().name()); for (CompletionInputMetadata metadata: inputMap.values()) { - ParseContext externalValueContext = context.createExternalValueContext(metadata); + ParseContext externalValueContext = context.switchParser(new CompletionParser(metadata)); multiFields.parse(this, externalValueContext); } } - private Map getInputMapFromExternalValue(ParseContext context) { - Map inputMap; - if (isExternalValueOfClass(context, CompletionInputMetadata.class)) { - CompletionInputMetadata inputAndMeta = (CompletionInputMetadata) context.externalValue(); - inputMap = Collections.singletonMap(inputAndMeta.input, inputAndMeta); - } else { - String fieldName = context.externalValue().toString(); - inputMap = Collections.singletonMap(fieldName, new CompletionInputMetadata(fieldName, Collections.emptyMap(), 1)); - } - return inputMap; - } - - private boolean isExternalValueOfClass(ParseContext context, Class clazz) { - return context.externalValue().getClass().equals(clazz); - } - /** * Acceptable inputs: * "STRING" - interpreted as the field value (input) @@ -510,6 +495,21 @@ static class CompletionInputMetadata { public String toString() { return input; } + + Map toMap() { + Map map = new HashMap<>(); + map.put("input", input); + map.put("weight", weight); + if (contexts.isEmpty() == false) { + Map> contextsAsList = new HashMap<>(); + contexts.forEach((k, v) -> { + List l = new ArrayList<>(v); + contextsAsList.put(k, l); + }); + map.put("contexts", contextsAsList); + } + return map; + } } @Override @@ -530,4 +530,29 @@ public void doValidate(MappingLookup mappers) { } } } + + private static class CompletionParser extends FilterXContentParser { + + boolean advanced = false; + final String textValue; + + private CompletionParser(CompletionInputMetadata metadata) throws IOException { + super(MapXContentParser.wrapObject(metadata.toMap())); + this.textValue = metadata.input; + } + + @Override + public String textOrNull() throws IOException { + if (advanced == false) { + return textValue; + } + return super.textOrNull(); + } + + @Override + public Token nextToken() throws IOException { + advanced = true; + return super.nextToken(); + } + } } diff --git a/server/src/main/java/org/elasticsearch/index/mapper/DateFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/DateFieldMapper.java index d07984ca41213..3eeb8a0809182 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/DateFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/DateFieldMapper.java @@ -690,17 +690,7 @@ protected String contentType() { @Override protected void parseCreateField(ParseContext context) throws IOException { - String dateAsString; - if (context.externalValueSet()) { - Object dateAsObject = context.externalValue(); - if (dateAsObject == null) { - dateAsString = null; - } else { - dateAsString = dateAsObject.toString(); - } - } else { - dateAsString = context.parser().textOrNull(); - } + String dateAsString = context.parser().textOrNull(); long timestamp; if (dateAsString == null) { diff --git a/server/src/main/java/org/elasticsearch/index/mapper/FieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/FieldMapper.java index 7fe80c9cebae0..6e173f33d1102 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/FieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/FieldMapper.java @@ -40,6 +40,7 @@ import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; @@ -1200,23 +1201,41 @@ private static boolean isDeprecatedParameter(String propName, Version indexCreat } } + public static BiConsumer notInMultiFields(String type) { + return (n, c) -> { + if (c.isWithinMultiField()) { + throw new MapperParsingException("Field [" + n + "] of type [" + type + "] can't be used in multifields"); + } + }; + } + /** * TypeParser implementation that automatically handles parsing */ public static final class TypeParser implements Mapper.TypeParser { private final BiFunction builderFunction; + private final BiConsumer contextValidator; /** * Creates a new TypeParser * @param builderFunction a function that produces a Builder from a name and parsercontext */ public TypeParser(BiFunction builderFunction) { + this(builderFunction, (n, c) -> {}); + } + + public TypeParser( + BiFunction builderFunction, + BiConsumer contextValidator + ) { this.builderFunction = builderFunction; + this.contextValidator = contextValidator; } @Override public Builder parse(String name, Map node, ParserContext parserContext) throws MapperParsingException { + contextValidator.accept(name, parserContext); Builder builder = builderFunction.apply(name, parserContext); builder.parse(name, parserContext, node); return builder; diff --git a/server/src/main/java/org/elasticsearch/index/mapper/GeoPointFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/GeoPointFieldMapper.java index d0f3fce989805..5fd3fe4eebe4c 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/GeoPointFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/GeoPointFieldMapper.java @@ -25,6 +25,7 @@ import org.elasticsearch.common.geo.GeometryParser; import org.elasticsearch.common.geo.ShapeRelation; import org.elasticsearch.common.unit.DistanceUnit; +import org.elasticsearch.common.xcontent.support.MapXContentParser; import org.elasticsearch.geometry.Geometry; import org.elasticsearch.geometry.Point; import org.elasticsearch.index.fielddata.IndexFieldData; @@ -197,7 +198,7 @@ protected void index(ParseContext context, GeoPoint geometry) throws IOException context.doc().add(new StoredField(fieldType().name(), geometry.toString())); } // TODO phase out geohash (which is currently used in the CompletionSuggester) - multiFields.parse(this, context.createExternalValueContext(geometry.geohash())); + multiFields.parse(this, context.switchParser(MapXContentParser.wrapObject(geometry.geohash()))); } @Override diff --git a/server/src/main/java/org/elasticsearch/index/mapper/IpFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/IpFieldMapper.java index 098a1cc77cb90..2864bc660cd50 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/IpFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/IpFieldMapper.java @@ -417,12 +417,7 @@ protected String contentType() { @Override protected void parseCreateField(ParseContext context) throws IOException { - Object addressAsObject; - if (context.externalValueSet()) { - addressAsObject = context.externalValue(); - } else { - addressAsObject = context.parser().textOrNull(); - } + Object addressAsObject = context.parser().textOrNull(); if (addressAsObject == null) { addressAsObject = nullValue; diff --git a/server/src/main/java/org/elasticsearch/index/mapper/KeywordFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/KeywordFieldMapper.java index 26fe154185259..1842d5afc0387 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/KeywordFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/KeywordFieldMapper.java @@ -396,15 +396,11 @@ public KeywordFieldType fieldType() { @Override protected void parseCreateField(ParseContext context) throws IOException { String value; - if (context.externalValueSet()) { - value = context.externalValue().toString(); + XContentParser parser = context.parser(); + if (parser.currentToken() == XContentParser.Token.VALUE_NULL) { + value = nullValue; } else { - XContentParser parser = context.parser(); - if (parser.currentToken() == XContentParser.Token.VALUE_NULL) { - value = nullValue; - } else { - value = parser.textOrNull(); - } + value = parser.textOrNull(); } indexValue(context, value); diff --git a/server/src/main/java/org/elasticsearch/index/mapper/NumberFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/NumberFieldMapper.java index 23fdbe824cfeb..99ac9d3ca446b 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/NumberFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/NumberFieldMapper.java @@ -1113,9 +1113,7 @@ protected void parseCreateField(ParseContext context) throws IOException { XContentParser parser = context.parser(); Object value; Number numericValue = null; - if (context.externalValueSet()) { - value = context.externalValue(); - } else if (parser.currentToken() == Token.VALUE_NULL) { + if (parser.currentToken() == Token.VALUE_NULL) { value = null; } else if (coerce.value() && parser.currentToken() == Token.VALUE_STRING diff --git a/server/src/main/java/org/elasticsearch/index/mapper/ParseContext.java b/server/src/main/java/org/elasticsearch/index/mapper/ParseContext.java index 34c78967e2dab..76c817af36228 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/ParseContext.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/ParseContext.java @@ -253,16 +253,6 @@ public void seqID(SeqNoFieldMapper.SequenceIDFields seqID) { in.seqID(seqID); } - @Override - public boolean externalValueSet() { - return in.externalValueSet(); - } - - @Override - public Object externalValue() { - return in.externalValue(); - } - @Override public void addDynamicMapper(Mapper update) { in.addDynamicMapper(update); @@ -606,6 +596,20 @@ public ContentPath path() { }; } + /** + * @deprecated we are actively deprecating and removing the ability to pass + * complex objects to multifields, so try and avoid using this method + */ + @Deprecated + public final ParseContext switchParser(XContentParser parser) { + return new FilterParseContext(this) { + @Override + public XContentParser parser() { + return parser; + } + }; + } + public boolean isWithinMultiFields() { return false; } @@ -640,47 +644,6 @@ public boolean isWithinMultiFields() { public abstract void seqID(SeqNoFieldMapper.SequenceIDFields seqID); - /** - * Return a new context that will have the external value set. - */ - public final ParseContext createExternalValueContext(final Object externalValue) { - return new FilterParseContext(this) { - @Override - public boolean externalValueSet() { - return true; - } - @Override - public Object externalValue() { - return externalValue; - } - }; - } - - public boolean externalValueSet() { - return false; - } - - public Object externalValue() { - throw new IllegalStateException("External value is not set"); - } - - /** - * Try to parse an externalValue if any - * @param clazz Expected class for external value - * @return null if no external value has been set or the value - */ - public final T parseExternalValue(Class clazz) { - if (externalValueSet() == false || externalValue() == null) { - return null; - } - - if (clazz.isInstance(externalValue()) == false) { - throw new IllegalArgumentException("illegal external value class [" - + externalValue().getClass().getName() + "]. Should be " + clazz.getName()); - } - return clazz.cast(externalValue()); - } - /** * Add a new mapper dynamically created while parsing. */ diff --git a/server/src/main/java/org/elasticsearch/index/mapper/RangeFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/RangeFieldMapper.java index 3e8e475db0c37..d122aa3cfa87b 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/RangeFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/RangeFieldMapper.java @@ -302,59 +302,55 @@ protected String contentType() { @Override protected void parseCreateField(ParseContext context) throws IOException { Range range; - if (context.externalValueSet()) { - range = context.parseExternalValue(Range.class); - } else { - XContentParser parser = context.parser(); - final XContentParser.Token start = parser.currentToken(); - if (start == XContentParser.Token.VALUE_NULL) { - return; - } else if (start == XContentParser.Token.START_OBJECT) { - RangeFieldType fieldType = fieldType(); - RangeType rangeType = fieldType.rangeType; - String fieldName = null; - Object from = rangeType.minValue(); - Object to = rangeType.maxValue(); - boolean includeFrom = DEFAULT_INCLUDE_LOWER; - boolean includeTo = DEFAULT_INCLUDE_UPPER; - XContentParser.Token token; - while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { - if (token == XContentParser.Token.FIELD_NAME) { - fieldName = parser.currentName(); - } else { - if (fieldName.equals(GT_FIELD.getPreferredName())) { - includeFrom = false; - if (parser.currentToken() != XContentParser.Token.VALUE_NULL) { - from = rangeType.parseFrom(fieldType, parser, coerce.value(), includeFrom); - } - } else if (fieldName.equals(GTE_FIELD.getPreferredName())) { - includeFrom = true; - if (parser.currentToken() != XContentParser.Token.VALUE_NULL) { - from = rangeType.parseFrom(fieldType, parser, coerce.value(), includeFrom); - } - } else if (fieldName.equals(LT_FIELD.getPreferredName())) { - includeTo = false; - if (parser.currentToken() != XContentParser.Token.VALUE_NULL) { - to = rangeType.parseTo(fieldType, parser, coerce.value(), includeTo); - } - } else if (fieldName.equals(LTE_FIELD.getPreferredName())) { - includeTo = true; - if (parser.currentToken() != XContentParser.Token.VALUE_NULL) { - to = rangeType.parseTo(fieldType, parser, coerce.value(), includeTo); - } - } else { - throw new MapperParsingException("error parsing field [" + - name() + "], with unknown parameter [" + fieldName + "]"); + XContentParser parser = context.parser(); + final XContentParser.Token start = parser.currentToken(); + if (start == XContentParser.Token.VALUE_NULL) { + return; + } else if (start == XContentParser.Token.START_OBJECT) { + RangeFieldType fieldType = fieldType(); + RangeType rangeType = fieldType.rangeType; + String fieldName = null; + Object from = rangeType.minValue(); + Object to = rangeType.maxValue(); + boolean includeFrom = DEFAULT_INCLUDE_LOWER; + boolean includeTo = DEFAULT_INCLUDE_UPPER; + XContentParser.Token token; + while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { + if (token == XContentParser.Token.FIELD_NAME) { + fieldName = parser.currentName(); + } else { + if (fieldName.equals(GT_FIELD.getPreferredName())) { + includeFrom = false; + if (parser.currentToken() != XContentParser.Token.VALUE_NULL) { + from = rangeType.parseFrom(fieldType, parser, coerce.value(), includeFrom); + } + } else if (fieldName.equals(GTE_FIELD.getPreferredName())) { + includeFrom = true; + if (parser.currentToken() != XContentParser.Token.VALUE_NULL) { + from = rangeType.parseFrom(fieldType, parser, coerce.value(), includeFrom); + } + } else if (fieldName.equals(LT_FIELD.getPreferredName())) { + includeTo = false; + if (parser.currentToken() != XContentParser.Token.VALUE_NULL) { + to = rangeType.parseTo(fieldType, parser, coerce.value(), includeTo); } + } else if (fieldName.equals(LTE_FIELD.getPreferredName())) { + includeTo = true; + if (parser.currentToken() != XContentParser.Token.VALUE_NULL) { + to = rangeType.parseTo(fieldType, parser, coerce.value(), includeTo); + } + } else { + throw new MapperParsingException("error parsing field [" + + name() + "], with unknown parameter [" + fieldName + "]"); } } - range = new Range(rangeType, from, to, includeFrom, includeTo); - } else if (fieldType().rangeType == RangeType.IP && start == XContentParser.Token.VALUE_STRING) { - range = parseIpRangeFromCidr(parser); - } else { - throw new MapperParsingException("error parsing field [" - + name() + "], expected an object but got " + parser.currentName()); } + range = new Range(rangeType, from, to, includeFrom, includeTo); + } else if (fieldType().rangeType == RangeType.IP && start == XContentParser.Token.VALUE_STRING) { + range = parseIpRangeFromCidr(parser); + } else { + throw new MapperParsingException("error parsing field [" + + name() + "], expected an object but got " + parser.currentName()); } context.doc().addAll(fieldType().rangeType.createFields(context, name(), range, index, hasDocValues, store)); diff --git a/server/src/main/java/org/elasticsearch/index/mapper/TextFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/TextFieldMapper.java index b8c983ea03cfc..1f4ce0efee5f4 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/TextFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/TextFieldMapper.java @@ -851,12 +851,7 @@ public FieldMapper.Builder getMergeBuilder() { @Override protected void parseCreateField(ParseContext context) throws IOException { - final String value; - if (context.externalValueSet()) { - value = context.externalValue().toString(); - } else { - value = context.parser().textOrNull(); - } + final String value = context.parser().textOrNull(); if (value == null) { return; diff --git a/server/src/test/java/org/elasticsearch/index/mapper/CompletionFieldMapperTests.java b/server/src/test/java/org/elasticsearch/index/mapper/CompletionFieldMapperTests.java index bfaec5431aa51..ec46fb440e91e 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/CompletionFieldMapperTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/CompletionFieldMapperTests.java @@ -7,6 +7,7 @@ */ package org.elasticsearch.index.mapper; +import org.apache.lucene.analysis.TokenStream; import org.apache.lucene.analysis.core.SimpleAnalyzer; import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.apache.lucene.document.SortedSetDocValuesField; @@ -24,6 +25,7 @@ import org.apache.lucene.util.automaton.RegExp; import org.elasticsearch.common.Strings; import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.common.lucene.Lucene; import org.elasticsearch.common.unit.Fuzziness; import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; @@ -304,6 +306,13 @@ public void testCompletionWithContextAndSubCompletion() throws Exception { contextSuggestField("timmy"), contextSuggestField("starbucks") )); + // check that the indexable fields produce tokenstreams without throwing an exception + // if this breaks it is likely a problem with setting contexts + try (TokenStream ts = indexableFields.getFields("field.subsuggest")[0].tokenStream(Lucene.WHITESPACE_ANALYZER, null)) { + ts.reset(); + while (ts.incrementToken()) {} + ts.end(); + } //unable to assert about context, covered in a REST test } diff --git a/server/src/test/java/org/elasticsearch/index/mapper/ExternalFieldMapperTests.java b/server/src/test/java/org/elasticsearch/index/mapper/ExternalFieldMapperTests.java deleted file mode 100644 index f84466da586e0..0000000000000 --- a/server/src/test/java/org/elasticsearch/index/mapper/ExternalFieldMapperTests.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * 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.apache.lucene.index.IndexableField; -import org.elasticsearch.plugins.Plugin; - -import java.util.Collection; -import java.util.Collections; - -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.notNullValue; - -public class ExternalFieldMapperTests extends MapperServiceTestCase { - - @Override - protected Collection getPlugins() { - return Collections.singletonList(new ExternalMapperPlugin()); - } - - public void testExternalValues() throws Exception { - - DocumentMapper documentMapper = createDocumentMapper(fieldMapping(b -> b.field("type", "external"))); - - ParsedDocument doc = documentMapper.parse(source(b -> b.field("field", "1234"))); - - assertThat(doc.rootDoc().getField("field.bool"), notNullValue()); - assertThat(doc.rootDoc().getField("field.bool").stringValue(), is("T")); - - assertThat(doc.rootDoc().getField("field.field"), notNullValue()); - assertThat(doc.rootDoc().getField("field.field").stringValue(), is("foo")); - - assertThat(doc.rootDoc().getField(ExternalMetadataMapper.FIELD_NAME).stringValue(), is(ExternalMetadataMapper.FIELD_VALUE)); - - } - - public void testExternalValuesWithMultifield() throws Exception { - - DocumentMapper documentMapper = createDocumentMapper(fieldMapping(b -> { - b.field("type", ExternalMapperPlugin.EXTERNAL); - b.startObject("fields"); - { - b.startObject("text"); - { - b.field("type", "text"); - b.field("store", true); - } - b.endObject(); - } - b.endObject(); - })); - - ParsedDocument doc = documentMapper.parse(source(b -> b.field("field", "1234"))); - - assertThat(doc.rootDoc().getField("field.bool"), notNullValue()); - assertThat(doc.rootDoc().getField("field.bool").stringValue(), is("T")); - - IndexableField field = doc.rootDoc().getField("field.text"); - assertThat(field, notNullValue()); - assertThat(field.stringValue(), is("foo")); - } -} diff --git a/server/src/test/java/org/elasticsearch/index/mapper/ExternalMapper.java b/server/src/test/java/org/elasticsearch/index/mapper/ExternalMapper.java deleted file mode 100644 index 3703a35bd5156..0000000000000 --- a/server/src/test/java/org/elasticsearch/index/mapper/ExternalMapper.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * 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.apache.lucene.analysis.standard.StandardAnalyzer; -import org.elasticsearch.common.collect.Iterators; -import org.elasticsearch.index.analysis.AnalyzerScope; -import org.elasticsearch.index.analysis.IndexAnalyzers; -import org.elasticsearch.index.analysis.NamedAnalyzer; -import org.elasticsearch.index.query.SearchExecutionContext; -import org.elasticsearch.script.ScriptCompiler; - -import java.io.IOException; -import java.nio.charset.Charset; -import java.util.Arrays; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -/** - * This mapper add a new sub fields - * .bin Binary type - * .bool Boolean type - * .point GeoPoint type - * .shape GeoShape type - */ -public class ExternalMapper extends FieldMapper { - - public static class Names { - public static final String FIELD_BIN = "bin"; - public static final String FIELD_BOOL = "bool"; - public static final String FIELD_POINT = "point"; - public static final String FIELD_SHAPE = "shape"; - } - - private static final IndexAnalyzers INDEX_ANALYZERS = new IndexAnalyzers( - Map.of("default", new NamedAnalyzer("default", AnalyzerScope.INDEX, new StandardAnalyzer())), - Map.of(), - Map.of() - ); - - public static class Builder extends FieldMapper.Builder { - - private final BinaryFieldMapper.Builder binBuilder = new BinaryFieldMapper.Builder(Names.FIELD_BIN); - private final BooleanFieldMapper.Builder boolBuilder = new BooleanFieldMapper.Builder(Names.FIELD_BOOL, ScriptCompiler.NONE); - private final Mapper.Builder stringBuilder; - private final String generatedValue; - private final String mapperName; - - public Builder(String name, String generatedValue, String mapperName) { - super(name); - this.stringBuilder = new TextFieldMapper.Builder(name, INDEX_ANALYZERS).store(false); - this.generatedValue = generatedValue; - this.mapperName = mapperName; - } - - @Override - protected List> getParameters() { - return Collections.emptyList(); - } - - @Override - public ExternalMapper build(ContentPath contentPath) { - contentPath.add(name); - BinaryFieldMapper binMapper = binBuilder.build(contentPath); - BooleanFieldMapper boolMapper = boolBuilder.build(contentPath); - FieldMapper stringMapper = (FieldMapper)stringBuilder.build(contentPath); - contentPath.remove(); - - return new ExternalMapper(name, buildFullName(contentPath), generatedValue, mapperName, binMapper, boolMapper, - stringMapper, multiFieldsBuilder.build(this, contentPath), copyTo.build()); - } - } - - public static TypeParser parser(String mapperName, String generatedValue) { - return new TypeParser((n, c) -> new Builder(n, generatedValue, mapperName)); - } - - static class ExternalFieldType extends TermBasedFieldType { - - private ExternalFieldType(String name, boolean indexed, boolean stored, boolean hasDocValues) { - super(name, indexed, stored, hasDocValues, TextSearchInfo.SIMPLE_MATCH_ONLY, Collections.emptyMap()); - } - - @Override - public String typeName() { - return "faketype"; - } - - @Override - public ValueFetcher valueFetcher(SearchExecutionContext context, String format) { - return SourceValueFetcher.identity(name(), context, format); - } - } - - private final String generatedValue; - private final String mapperName; - - private final BinaryFieldMapper binMapper; - private final BooleanFieldMapper boolMapper; - private final FieldMapper stringMapper; - - public ExternalMapper(String simpleName, String contextName, - String generatedValue, String mapperName, - BinaryFieldMapper binMapper, BooleanFieldMapper boolMapper, - FieldMapper stringMapper, - MultiFields multiFields, CopyTo copyTo) { - super(simpleName, new ExternalFieldType(contextName, true, true, false), multiFields, copyTo); - this.generatedValue = generatedValue; - this.mapperName = mapperName; - this.binMapper = binMapper; - this.boolMapper = boolMapper; - this.stringMapper = stringMapper; - } - - @Override - public void parse(ParseContext context) throws IOException { - byte[] bytes = "Hello world".getBytes(Charset.defaultCharset()); - binMapper.parse(context.createExternalValueContext(bytes)); - - boolMapper.parse(context.createExternalValueContext(true)); - - context = context.createExternalValueContext(generatedValue); - - // Let's add a Original String - stringMapper.parse(context); - - multiFields.parse(this, context); - } - - @Override - protected void parseCreateField(ParseContext context) { - throw new UnsupportedOperationException(); - } - - @Override - public Iterator iterator() { - return Iterators.concat(super.iterator(), Arrays.asList(binMapper, boolMapper, stringMapper).iterator()); - } - - @Override - public FieldMapper.Builder getMergeBuilder() { - return new Builder(simpleName(), generatedValue, mapperName); - } - - @Override - protected String contentType() { - return mapperName; - } -} diff --git a/server/src/test/java/org/elasticsearch/index/mapper/ExternalMapperPlugin.java b/server/src/test/java/org/elasticsearch/index/mapper/ExternalMapperPlugin.java deleted file mode 100644 index 2c3005cde4843..0000000000000 --- a/server/src/test/java/org/elasticsearch/index/mapper/ExternalMapperPlugin.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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.plugins.MapperPlugin; -import org.elasticsearch.plugins.Plugin; - -import java.util.Collections; -import java.util.Map; - -public class ExternalMapperPlugin extends Plugin implements MapperPlugin { - - public static final String EXTERNAL = "external"; - public static final String EXTERNAL_BIS = "external_bis"; - public static final String EXTERNAL_UPPER = "external_upper"; - - @Override - public Map getMappers() { - return Map.of( - EXTERNAL, ExternalMapper.parser(EXTERNAL, "foo"), - EXTERNAL_BIS, ExternalMapper.parser(EXTERNAL_BIS, "bar"), - EXTERNAL_UPPER, ExternalMapper.parser(EXTERNAL_UPPER, "FOO BAR"), - FakeStringFieldMapper.CONTENT_TYPE, FakeStringFieldMapper.PARSER); - } - - @Override - public Map getMetadataMappers() { - return Collections.singletonMap(ExternalMetadataMapper.CONTENT_TYPE, ExternalMetadataMapper.PARSER); - } - -} diff --git a/server/src/test/java/org/elasticsearch/index/mapper/FakeStringFieldMapper.java b/server/src/test/java/org/elasticsearch/index/mapper/FakeStringFieldMapper.java index 3c44b6991389a..8f2d055af2be9 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/FakeStringFieldMapper.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/FakeStringFieldMapper.java @@ -76,12 +76,7 @@ protected FakeStringFieldMapper(MappedFieldType mappedFieldType, @Override protected void parseCreateField(ParseContext context) throws IOException { - String value; - if (context.externalValueSet()) { - value = context.externalValue().toString(); - } else { - value = context.parser().textOrNull(); - } + String value = context.parser().textOrNull(); if (value == null) { return; diff --git a/x-pack/plugin/analytics/src/main/java/org/elasticsearch/xpack/analytics/mapper/HistogramFieldMapper.java b/x-pack/plugin/analytics/src/main/java/org/elasticsearch/xpack/analytics/mapper/HistogramFieldMapper.java index 0d4a637a82e86..94655d3385831 100644 --- a/x-pack/plugin/analytics/src/main/java/org/elasticsearch/xpack/analytics/mapper/HistogramFieldMapper.java +++ b/x-pack/plugin/analytics/src/main/java/org/elasticsearch/xpack/analytics/mapper/HistogramFieldMapper.java @@ -92,7 +92,7 @@ public HistogramFieldMapper build(ContentPath contentPath) { } public static final TypeParser PARSER - = new TypeParser((n, c) -> new Builder(n, IGNORE_MALFORMED_SETTING.get(c.getSettings()))); + = new TypeParser((n, c) -> new Builder(n, IGNORE_MALFORMED_SETTING.get(c.getSettings())), notInMultiFields(CONTENT_TYPE)); private final Explicit ignoreMalformed; private final boolean ignoreMalformedByDefault; @@ -226,9 +226,6 @@ public Query termQuery(Object value, SearchExecutionContext context) { @Override public void parse(ParseContext context) throws IOException { - if (context.externalValueSet()) { - throw new IllegalArgumentException("Field [" + name() + "] of type [" + typeName() + "] can't be used in multi-fields"); - } context.path().add(simpleName()); XContentParser.Token token; XContentSubParser subParser = null; diff --git a/x-pack/plugin/analytics/src/test/java/org/elasticsearch/xpack/analytics/mapper/HistogramFieldMapperTests.java b/x-pack/plugin/analytics/src/test/java/org/elasticsearch/xpack/analytics/mapper/HistogramFieldMapperTests.java index 3cad261a4c75d..f620791baf7a8 100644 --- a/x-pack/plugin/analytics/src/test/java/org/elasticsearch/xpack/analytics/mapper/HistogramFieldMapperTests.java +++ b/x-pack/plugin/analytics/src/test/java/org/elasticsearch/xpack/analytics/mapper/HistogramFieldMapperTests.java @@ -305,4 +305,16 @@ protected Object generateRandomInputValue(MappedFieldType ft) { assumeFalse("Test implemented in a follow up", true); return null; } + + public void testCannotBeUsedInMultifields() { + Exception e = expectThrows(MapperParsingException.class, () -> createMapperService(fieldMapping(b -> { + b.field("type", "keyword"); + b.startObject("fields"); + b.startObject("hist"); + b.field("type", "histogram"); + b.endObject(); + b.endObject(); + }))); + assertThat(e.getMessage(), containsString("Field [hist] of type [histogram] can't be used in multifields")); + } } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/watcher/support/xcontent/WatcherXContentParser.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/watcher/support/xcontent/WatcherXContentParser.java index e9fc385d0789c..f6affb9b19d79 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/watcher/support/xcontent/WatcherXContentParser.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/watcher/support/xcontent/WatcherXContentParser.java @@ -7,24 +7,15 @@ package org.elasticsearch.xpack.core.watcher.support.xcontent; import org.elasticsearch.ElasticsearchParseException; -import org.elasticsearch.common.CheckedFunction; import org.elasticsearch.common.Nullable; -import org.elasticsearch.common.RestApiVersion; -import org.elasticsearch.common.xcontent.DeprecationHandler; -import org.elasticsearch.common.xcontent.NamedXContentRegistry; -import org.elasticsearch.common.xcontent.XContentLocation; +import org.elasticsearch.common.xcontent.FilterXContentParser; import org.elasticsearch.common.xcontent.XContentParser; -import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.xpack.core.watcher.common.secret.Secret; import org.elasticsearch.xpack.core.watcher.crypto.CryptoService; import java.io.IOException; -import java.nio.CharBuffer; import java.time.Clock; import java.time.ZonedDateTime; -import java.util.List; -import java.util.Map; -import java.util.function.Supplier; /** * A xcontent parser that is used by watcher. This is a special parser that is @@ -35,7 +26,7 @@ * {@link Secret}s are encrypted values that are stored in memory and are decrypted * on demand when needed. */ -public class WatcherXContentParser implements XContentParser { +public class WatcherXContentParser extends FilterXContentParser { public static final String REDACTED_PASSWORD = "::es_redacted::"; @@ -68,233 +59,16 @@ public static Secret secretOrNull(XContentParser parser) throws IOException { } private final ZonedDateTime parseTime; - private final XContentParser parser; @Nullable private final CryptoService cryptoService; private final boolean allowRedactedPasswords; public WatcherXContentParser(XContentParser parser, ZonedDateTime parseTime, @Nullable CryptoService cryptoService, boolean allowRedactedPasswords) { + super(parser); this.parseTime = parseTime; - this.parser = parser; this.cryptoService = cryptoService; this.allowRedactedPasswords = allowRedactedPasswords; } public ZonedDateTime getParseDateTime() { return parseTime; } - - @Override - public XContentType contentType() { - return parser.contentType(); - } - - @Override - public Token nextToken() throws IOException { - return parser.nextToken(); - } - - @Override - public void skipChildren() throws IOException { - parser.skipChildren(); - } - - @Override - public Token currentToken() { - return parser.currentToken(); - } - - @Override - public String currentName() throws IOException { - return parser.currentName(); - } - - @Override - public Map map() throws IOException { - return parser.map(); - } - - @Override - public Map mapOrdered() throws IOException { - return parser.mapOrdered(); - } - - @Override - public Map mapStrings() throws IOException { - return parser.mapStrings(); - } - - @Override - public Map map( - Supplier> mapFactory, CheckedFunction mapValueParser) throws IOException { - return parser.map(mapFactory, mapValueParser); - } - - @Override - public List list() throws IOException { - return parser.list(); - } - - @Override - public List listOrderedMap() throws IOException { - return parser.listOrderedMap(); - } - - @Override - public String text() throws IOException { - return parser.text(); - } - - @Override - public String textOrNull() throws IOException { - return parser.textOrNull(); - } - - @Override - public CharBuffer charBufferOrNull() throws IOException { - return parser.charBufferOrNull(); - } - - @Override - public CharBuffer charBuffer() throws IOException { - return parser.charBuffer(); - } - - @Override - public Object objectText() throws IOException { - return parser.objectText(); - } - - @Override - public Object objectBytes() throws IOException { - return parser.objectBytes(); - } - - @Override - public boolean hasTextCharacters() { - return parser.hasTextCharacters(); - } - - @Override - public char[] textCharacters() throws IOException { - return parser.textCharacters(); - } - - @Override - public int textLength() throws IOException { - return parser.textLength(); - } - - @Override - public int textOffset() throws IOException { - return parser.textOffset(); - } - - @Override - public Number numberValue() throws IOException { - return parser.numberValue(); - } - - @Override - public NumberType numberType() throws IOException { - return parser.numberType(); - } - - @Override - public short shortValue(boolean coerce) throws IOException { - return parser.shortValue(coerce); - } - - @Override - public int intValue(boolean coerce) throws IOException { - return parser.intValue(coerce); - } - - @Override - public long longValue(boolean coerce) throws IOException { - return parser.longValue(coerce); - } - - @Override - public float floatValue(boolean coerce) throws IOException { - return parser.floatValue(coerce); - } - - @Override - public double doubleValue(boolean coerce) throws IOException { - return parser.doubleValue(coerce); - } - - @Override - public short shortValue() throws IOException { - return parser.shortValue(); - } - - @Override - public int intValue() throws IOException { - return parser.intValue(); - } - - @Override - public long longValue() throws IOException { - return parser.longValue(); - } - - @Override - public float floatValue() throws IOException { - return parser.floatValue(); - } - - @Override - public double doubleValue() throws IOException { - return parser.doubleValue(); - } - - @Override - public boolean isBooleanValue() throws IOException { - return parser.isBooleanValue(); - } - - @Override - public boolean booleanValue() throws IOException { - return parser.booleanValue(); - } - - @Override - public byte[] binaryValue() throws IOException { - return parser.binaryValue(); - } - - @Override - public XContentLocation getTokenLocation() { - return parser.getTokenLocation(); - } - - @Override - public T namedObject(Class categoryClass, String name, Object context) throws IOException { - return parser.namedObject(categoryClass, name, context); - } - - @Override - public NamedXContentRegistry getXContentRegistry() { - return parser.getXContentRegistry(); - } - - @Override - public boolean isClosed() { - return parser.isClosed(); - } - - @Override - public void close() throws IOException { - parser.close(); - } - - @Override - public RestApiVersion getRestApiVersion() { - return RestApiVersion.current(); - } - - @Override - public DeprecationHandler getDeprecationHandler() { - return parser.getDeprecationHandler(); - } } diff --git a/x-pack/plugin/mapper-aggregate-metric/src/main/java/org/elasticsearch/xpack/aggregatemetric/mapper/AggregateDoubleMetricFieldMapper.java b/x-pack/plugin/mapper-aggregate-metric/src/main/java/org/elasticsearch/xpack/aggregatemetric/mapper/AggregateDoubleMetricFieldMapper.java index 1602a381530c7..88692aac3c129 100644 --- a/x-pack/plugin/mapper-aggregate-metric/src/main/java/org/elasticsearch/xpack/aggregatemetric/mapper/AggregateDoubleMetricFieldMapper.java +++ b/x-pack/plugin/mapper-aggregate-metric/src/main/java/org/elasticsearch/xpack/aggregatemetric/mapper/AggregateDoubleMetricFieldMapper.java @@ -225,7 +225,8 @@ public AggregateDoubleMetricFieldMapper build(ContentPath context) { } public static final FieldMapper.TypeParser PARSER = new TypeParser( - (n, c) -> new Builder(n, IGNORE_MALFORMED_SETTING.get(c.getSettings())) + (n, c) -> new Builder(n, IGNORE_MALFORMED_SETTING.get(c.getSettings())), + notInMultiFields(CONTENT_TYPE) ); public static final class AggregateDoubleMetricFieldType extends SimpleMappedFieldType { @@ -511,9 +512,6 @@ public Iterator iterator() { @Override protected void parseCreateField(ParseContext context) throws IOException { - if (context.externalValueSet()) { - throw new IllegalArgumentException("Field [" + name() + "] of type [" + typeName() + "] can't be used in multi-fields"); - } context.path().add(simpleName()); XContentParser.Token token; diff --git a/x-pack/plugin/mapper-aggregate-metric/src/test/java/org/elasticsearch/xpack/aggregatemetric/mapper/AggregateDoubleMetricFieldMapperTests.java b/x-pack/plugin/mapper-aggregate-metric/src/test/java/org/elasticsearch/xpack/aggregatemetric/mapper/AggregateDoubleMetricFieldMapperTests.java index 32919a284d33e..1fae26787d5a3 100644 --- a/x-pack/plugin/mapper-aggregate-metric/src/test/java/org/elasticsearch/xpack/aggregatemetric/mapper/AggregateDoubleMetricFieldMapperTests.java +++ b/x-pack/plugin/mapper-aggregate-metric/src/test/java/org/elasticsearch/xpack/aggregatemetric/mapper/AggregateDoubleMetricFieldMapperTests.java @@ -534,4 +534,16 @@ protected Object generateRandomInputValue(MappedFieldType ft) { assumeFalse("Test implemented in a follow up", true); return null; } + + public void testCannotBeUsedInMultifields() { + Exception e = expectThrows(MapperParsingException.class, () -> createMapperService(fieldMapping(b -> { + b.field("type", "keyword"); + b.startObject("fields"); + b.startObject("metric"); + minimalMapping(b); + b.endObject(); + b.endObject(); + }))); + assertThat(e.getMessage(), containsString("Field [metric] of type [aggregate_metric_double] can't be used in multifields")); + } } diff --git a/x-pack/plugin/mapper-constant-keyword/src/main/java/org/elasticsearch/xpack/constantkeyword/mapper/ConstantKeywordFieldMapper.java b/x-pack/plugin/mapper-constant-keyword/src/main/java/org/elasticsearch/xpack/constantkeyword/mapper/ConstantKeywordFieldMapper.java index a884e9b767c1f..973ab6d2c0744 100644 --- a/x-pack/plugin/mapper-constant-keyword/src/main/java/org/elasticsearch/xpack/constantkeyword/mapper/ConstantKeywordFieldMapper.java +++ b/x-pack/plugin/mapper-constant-keyword/src/main/java/org/elasticsearch/xpack/constantkeyword/mapper/ConstantKeywordFieldMapper.java @@ -233,13 +233,8 @@ public ConstantKeywordFieldType fieldType() { @Override protected void parseCreateField(ParseContext context) throws IOException { - String value; - if (context.externalValueSet()) { - value = context.externalValue().toString(); - } else { - XContentParser parser = context.parser(); - value = parser.textOrNull(); - } + XContentParser parser = context.parser(); + final String value = parser.textOrNull(); if (value == null) { throw new IllegalArgumentException("[constant_keyword] field [" + name() + "] doesn't accept [null] values"); diff --git a/x-pack/plugin/mapper-unsigned-long/src/main/java/org/elasticsearch/xpack/unsignedlong/UnsignedLongFieldMapper.java b/x-pack/plugin/mapper-unsigned-long/src/main/java/org/elasticsearch/xpack/unsignedlong/UnsignedLongFieldMapper.java index b4e36facb22b8..27dd960069c62 100644 --- a/x-pack/plugin/mapper-unsigned-long/src/main/java/org/elasticsearch/xpack/unsignedlong/UnsignedLongFieldMapper.java +++ b/x-pack/plugin/mapper-unsigned-long/src/main/java/org/elasticsearch/xpack/unsignedlong/UnsignedLongFieldMapper.java @@ -443,9 +443,7 @@ protected String contentType() { protected void parseCreateField(ParseContext context) throws IOException { XContentParser parser = context.parser(); Long numericValue; - if (context.externalValueSet()) { - numericValue = parseUnsignedLong(context.externalValue()); - } else if (parser.currentToken() == XContentParser.Token.VALUE_NULL) { + if (parser.currentToken() == XContentParser.Token.VALUE_NULL) { numericValue = null; } else if (parser.currentToken() == XContentParser.Token.VALUE_STRING && parser.textLength() == 0) { numericValue = null; diff --git a/x-pack/plugin/mapper-version/src/main/java/org/elasticsearch/xpack/versionfield/VersionStringFieldMapper.java b/x-pack/plugin/mapper-version/src/main/java/org/elasticsearch/xpack/versionfield/VersionStringFieldMapper.java index 88b163d33f6ba..f5d576fd8be8a 100644 --- a/x-pack/plugin/mapper-version/src/main/java/org/elasticsearch/xpack/versionfield/VersionStringFieldMapper.java +++ b/x-pack/plugin/mapper-version/src/main/java/org/elasticsearch/xpack/versionfield/VersionStringFieldMapper.java @@ -343,15 +343,11 @@ protected String contentType() { @Override protected void parseCreateField(ParseContext context) throws IOException { String versionString; - if (context.externalValueSet()) { - versionString = context.externalValue().toString(); + XContentParser parser = context.parser(); + if (parser.currentToken() == XContentParser.Token.VALUE_NULL) { + return; } else { - XContentParser parser = context.parser(); - if (parser.currentToken() == XContentParser.Token.VALUE_NULL) { - return; - } else { - versionString = parser.textOrNull(); - } + versionString = parser.textOrNull(); } if (versionString == null) { diff --git a/x-pack/plugin/vectors/src/main/java/org/elasticsearch/xpack/vectors/mapper/DenseVectorFieldMapper.java b/x-pack/plugin/vectors/src/main/java/org/elasticsearch/xpack/vectors/mapper/DenseVectorFieldMapper.java index d4ae6cf4093fc..a5d77993937b3 100644 --- a/x-pack/plugin/vectors/src/main/java/org/elasticsearch/xpack/vectors/mapper/DenseVectorFieldMapper.java +++ b/x-pack/plugin/vectors/src/main/java/org/elasticsearch/xpack/vectors/mapper/DenseVectorFieldMapper.java @@ -90,7 +90,8 @@ public DenseVectorFieldMapper build(ContentPath contentPath) { } } - public static final TypeParser PARSER = new TypeParser((n, c) -> new Builder(n, c.indexVersionCreated())); + public static final TypeParser PARSER + = new TypeParser((n, c) -> new Builder(n, c.indexVersionCreated()), notInMultiFields(CONTENT_TYPE)); public static final class DenseVectorFieldType extends MappedFieldType { private final int dims; @@ -169,9 +170,6 @@ public boolean parsesArrayValue() { @Override public void parse(ParseContext context) throws IOException { - if (context.externalValueSet()) { - throw new IllegalArgumentException("Field [" + name() + "] of type [" + typeName() + "] can't be used in multi-fields"); - } int dims = fieldType().dims(); //number of vector dimensions // encode array of floats as array of integers and store into buf diff --git a/x-pack/plugin/vectors/src/test/java/org/elasticsearch/xpack/vectors/mapper/DenseVectorFieldMapperTests.java b/x-pack/plugin/vectors/src/test/java/org/elasticsearch/xpack/vectors/mapper/DenseVectorFieldMapperTests.java index 89df6fa47c3df..930015f1fb298 100644 --- a/x-pack/plugin/vectors/src/test/java/org/elasticsearch/xpack/vectors/mapper/DenseVectorFieldMapperTests.java +++ b/x-pack/plugin/vectors/src/test/java/org/elasticsearch/xpack/vectors/mapper/DenseVectorFieldMapperTests.java @@ -165,4 +165,16 @@ protected Object generateRandomInputValue(MappedFieldType ft) { protected boolean allowsNullValues() { return false; // TODO should this allow null values? } + + public void testCannotBeUsedInMultifields() { + Exception e = expectThrows(MapperParsingException.class, () -> createMapperService(fieldMapping(b -> { + b.field("type", "keyword"); + b.startObject("fields"); + b.startObject("vectors"); + minimalMapping(b); + b.endObject(); + b.endObject(); + }))); + assertThat(e.getMessage(), containsString("Field [vectors] of type [dense_vector] can't be used in multifields")); + } } diff --git a/x-pack/plugin/wildcard/src/main/java/org/elasticsearch/xpack/wildcard/mapper/WildcardFieldMapper.java b/x-pack/plugin/wildcard/src/main/java/org/elasticsearch/xpack/wildcard/mapper/WildcardFieldMapper.java index 09f95a6b96397..1b67df94e4791 100644 --- a/x-pack/plugin/wildcard/src/main/java/org/elasticsearch/xpack/wildcard/mapper/WildcardFieldMapper.java +++ b/x-pack/plugin/wildcard/src/main/java/org/elasticsearch/xpack/wildcard/mapper/WildcardFieldMapper.java @@ -955,15 +955,11 @@ public WildcardFieldType fieldType() { @Override protected void parseCreateField(ParseContext context) throws IOException { final String value; - if (context.externalValueSet()) { - value = context.externalValue().toString(); + XContentParser parser = context.parser(); + if (parser.currentToken() == XContentParser.Token.VALUE_NULL) { + value = nullValue; } else { - XContentParser parser = context.parser(); - if (parser.currentToken() == XContentParser.Token.VALUE_NULL) { - value = nullValue; - } else { - value = parser.textOrNull(); - } + value = parser.textOrNull(); } ParseContext.Document parseDoc = context.doc();