From ed14aaefb155cc1fa16da7b156cb01237c2452c9 Mon Sep 17 00:00:00 2001 From: pgomulka Date: Wed, 7 Oct 2020 16:40:48 +0200 Subject: [PATCH 1/8] media type registry with singleton instance --- .../common/xcontent/MediaTypeParser.java | 64 ++++++++++--------- .../common/xcontent/MediaTypeRegistry.java | 54 ++++++++++++++++ .../common/xcontent/XContentType.java | 2 +- .../common/xcontent/MediaTypeParserTests.java | 28 +++++++- .../compat/CompatibleVersionPlugin.java | 9 ++- .../xpack/sql/plugin/SqlMediaTypeParser.java | 11 +++- 6 files changed, 129 insertions(+), 39 deletions(-) create mode 100644 libs/x-content/src/main/java/org/elasticsearch/common/xcontent/MediaTypeRegistry.java diff --git a/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/MediaTypeParser.java b/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/MediaTypeParser.java index 62a3f3fd915d0..a2dacb96011aa 100644 --- a/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/MediaTypeParser.java +++ b/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/MediaTypeParser.java @@ -25,27 +25,22 @@ import java.util.regex.Pattern; public class MediaTypeParser { - private final Map formatToMediaType; - private final Map typeWithSubtypeToMediaType; - private final Map> parametersMap; - - public MediaTypeParser(Map formatToMediaType, Map typeWithSubtypeToMediaType, - Map> parametersMap) { - this.formatToMediaType = Map.copyOf(formatToMediaType); - this.typeWithSubtypeToMediaType = Map.copyOf(typeWithSubtypeToMediaType); - this.parametersMap = Map.copyOf(parametersMap); - } + private MediaTypeRegistry mediaTypeRegistry; + public MediaTypeParser(MediaTypeRegistry mediaTypeRegistry) { + this.mediaTypeRegistry = mediaTypeRegistry; + } + @SuppressWarnings("unchecked") public T fromMediaType(String mediaType) { ParsedMediaType parsedMediaType = parseMediaType(mediaType); - return parsedMediaType != null ? parsedMediaType.getMediaType() : null; + return parsedMediaType != null ? (T)parsedMediaType.getMediaType() : null; } - + @SuppressWarnings("unchecked") public T fromFormat(String format) { if (format == null) { return null; } - return formatToMediaType.get(format.toLowerCase(Locale.ROOT)); + return (T)mediaTypeRegistry.formatToMediaType(format.toLowerCase(Locale.ROOT)); } /** @@ -65,7 +60,7 @@ public ParsedMediaType parseMediaType(String headerValue) { String type = typeSubtype[0]; String subtype = typeSubtype[1]; String typeWithSubtype = type + "/" + subtype; - T xContentType = typeWithSubtypeToMediaType.get(typeWithSubtype); + MediaType xContentType = mediaTypeRegistry.typeWithSubtypeToMediaType(typeWithSubtype); if (xContentType != null) { Map parameters = new HashMap<>(); for (int i = 1; i < split.length; i++) { @@ -90,8 +85,8 @@ public ParsedMediaType parseMediaType(String headerValue) { } private boolean isValidParameter(String typeWithSubtype, String parameterName, String parameterValue) { - if (parametersMap.containsKey(typeWithSubtype)) { - Map parameters = parametersMap.get(typeWithSubtype); + if (mediaTypeRegistry.parametersFor(typeWithSubtype) != null) { + Map parameters = mediaTypeRegistry.parametersFor(typeWithSubtype); if (parameters.containsKey(parameterName)) { Pattern regex = parameters.get(parameterName); return regex.matcher(parameterValue).matches(); @@ -104,19 +99,32 @@ private boolean hasSpaces(String s) { return s.trim().equals(s) == false; } + private static final String COMPATIBLE_WITH_PARAMETER_NAME = "compatible-with"; + + public Byte parseVersion(String mediaType) { + ParsedMediaType parsedMediaType = parseMediaType(mediaType); + if (parsedMediaType != null) { + String version = parsedMediaType + .getParameters() + .get(COMPATIBLE_WITH_PARAMETER_NAME); + return version != null ? Byte.parseByte(version) : null; + } + return null; + } + /** * A media type object that contains all the information provided on a Content-Type or Accept header */ public class ParsedMediaType { private final Map parameters; - private final T mediaType; + private final MediaType mediaType; - public ParsedMediaType(T mediaType, Map parameters) { + public ParsedMediaType(MediaType mediaType, Map parameters) { this.parameters = parameters; this.mediaType = mediaType; } - public T getMediaType() { + public MediaType getMediaType() { return mediaType; } @@ -126,11 +134,11 @@ public Map getParameters() { } public static class Builder { - private final Map formatToMediaType = new HashMap<>(); - private final Map typeWithSubtypeToMediaType = new HashMap<>(); + private final Map formatToMediaType = new HashMap<>(); + private final Map typeWithSubtypeToMediaType = new HashMap<>(); private final Map> parametersMap = new HashMap<>(); - public Builder withMediaTypeAndParams(String alternativeMediaType, T mediaType, Map paramNameAndValueRegex) { + public Builder withMediaTypeAndParams(String alternativeMediaType, MediaType mediaType, Map paramNameAndValueRegex) { typeWithSubtypeToMediaType.put(alternativeMediaType.toLowerCase(Locale.ROOT), mediaType); formatToMediaType.put(mediaType.format(), mediaType); @@ -146,15 +154,9 @@ public Builder withMediaTypeAndParams(String alternativeMediaType, T mediaTyp return this; } - public Builder copyFromMediaTypeParser(MediaTypeParser mediaTypeParser) { - formatToMediaType.putAll(mediaTypeParser.formatToMediaType); - typeWithSubtypeToMediaType.putAll(mediaTypeParser.typeWithSubtypeToMediaType); - parametersMap.putAll(mediaTypeParser.parametersMap); - return this; - } - - public MediaTypeParser build() { - return new MediaTypeParser<>(formatToMediaType, typeWithSubtypeToMediaType, parametersMap); + public MediaTypeParser build(MediaTypeRegistry mediaTypeRegistry) { + mediaTypeRegistry.register(formatToMediaType, typeWithSubtypeToMediaType, parametersMap); + return new MediaTypeParser(mediaTypeRegistry); } } } diff --git a/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/MediaTypeRegistry.java b/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/MediaTypeRegistry.java new file mode 100644 index 0000000000000..d07484426004d --- /dev/null +++ b/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/MediaTypeRegistry.java @@ -0,0 +1,54 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.common.xcontent; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; + +public class MediaTypeRegistry { + private static final MediaTypeRegistry INSTANCE = new MediaTypeRegistry(); + private Map formatToMediaType = new ConcurrentHashMap<>(); + private Map typeWithSubtypeToMediaType = new ConcurrentHashMap<>(); + private Map> parametersMap= new ConcurrentHashMap<>(); + + public static MediaTypeRegistry getInstance() { + return INSTANCE; + } + + public void register(Map formatToMediaType, Map typeWithSubtypeToMediaType, Map> parametersMap) { + this.formatToMediaType.putAll(formatToMediaType); + this.typeWithSubtypeToMediaType.putAll(typeWithSubtypeToMediaType); + this.parametersMap.putAll(parametersMap); + } + + + public MediaType formatToMediaType(String format) { + return formatToMediaType.get(format); + } + + public MediaType typeWithSubtypeToMediaType(String typeWithSubtype) { + return typeWithSubtypeToMediaType.get(typeWithSubtype); + } + + public Map parametersFor(String typeWithSubtype) { + return parametersMap.get(typeWithSubtype); + } +} diff --git a/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/XContentType.java b/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/XContentType.java index 076a20bad006a..d70a45ace0bca 100644 --- a/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/XContentType.java +++ b/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/XContentType.java @@ -133,7 +133,7 @@ public XContent xContent() { Map.of(COMPATIBLE_WITH_PARAMETER_NAME, VERSION_PATTERN, "charset", "UTF-8")) .withMediaTypeAndParams("application/vnd.elasticsearch+x-ndjson", JSON, Map.of(COMPATIBLE_WITH_PARAMETER_NAME, VERSION_PATTERN, "charset", "UTF-8")) - .build(); + .build(MediaTypeRegistry.getInstance()); /** * Accepts a format string, which is most of the time is equivalent to {@link XContentType#subtype()} diff --git a/libs/x-content/src/test/java/org/elasticsearch/common/xcontent/MediaTypeParserTests.java b/libs/x-content/src/test/java/org/elasticsearch/common/xcontent/MediaTypeParserTests.java index 08ca08a3d2240..b9137adf9c2ea 100644 --- a/libs/x-content/src/test/java/org/elasticsearch/common/xcontent/MediaTypeParserTests.java +++ b/libs/x-content/src/test/java/org/elasticsearch/common/xcontent/MediaTypeParserTests.java @@ -29,12 +29,12 @@ import static org.hamcrest.Matchers.nullValue; public class MediaTypeParserTests extends ESTestCase { - - MediaTypeParser mediaTypeParser = new MediaTypeParser.Builder() + MediaTypeRegistry mediaTypeRegistry = new MediaTypeRegistry(); + MediaTypeParser mediaTypeParser = new MediaTypeParser.Builder() .withMediaTypeAndParams("application/vnd.elasticsearch+json", XContentType.JSON, Map.of("compatible-with", "\\d+", "charset", "UTF-8")) - .build(); + .build(mediaTypeRegistry); public void testJsonWithParameters() throws Exception { String mediaType = "application/vnd.elasticsearch+json"; @@ -74,4 +74,26 @@ public void testInvalidParameters() { assertThat(mediaTypeParser.parseMediaType(mediaType + "; key=") , is(nullValue())); } + + public void testVersionParsing() { + byte version = (byte) Math.abs(randomByte()); + assertThat(mediaTypeParser.parseVersion("application/vnd.elasticsearch+json;compatible-with=" + version), + equalTo(version)); + assertThat(mediaTypeParser.parseVersion("application/json"), + nullValue()); + + + assertThat(mediaTypeParser.parseVersion("APPLICATION/VND.ELASTICSEARCH+JSON;COMPATIBLE-WITH=" + version), + equalTo(version)); + assertThat(mediaTypeParser.parseVersion("APPLICATION/JSON"), + nullValue()); + + assertThat(mediaTypeParser.parseVersion("application/json;compatible-with=" + version + ".0"), + is(nullValue())); + } + + public void testUnrecognizedParameter() { + assertThat(mediaTypeParser.parseVersion("application/json; sth=123"), + is(nullValue())); } + } diff --git a/x-pack/plugin/rest-compatibility/src/main/java/org/elasticsearch/compat/CompatibleVersionPlugin.java b/x-pack/plugin/rest-compatibility/src/main/java/org/elasticsearch/compat/CompatibleVersionPlugin.java index 2d4be753839eb..64f22a33a9d5d 100644 --- a/x-pack/plugin/rest-compatibility/src/main/java/org/elasticsearch/compat/CompatibleVersionPlugin.java +++ b/x-pack/plugin/rest-compatibility/src/main/java/org/elasticsearch/compat/CompatibleVersionPlugin.java @@ -8,6 +8,9 @@ import org.elasticsearch.ElasticsearchStatusException; import org.elasticsearch.Version; import org.elasticsearch.common.Nullable; +import org.elasticsearch.common.xcontent.MediaType; +import org.elasticsearch.common.xcontent.MediaTypeParser; +import org.elasticsearch.common.xcontent.MediaTypeRegistry; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.plugins.Plugin; import org.elasticsearch.plugins.RestCompatibilityPlugin; @@ -15,11 +18,13 @@ public class CompatibleVersionPlugin extends Plugin implements RestCompatibilityPlugin { + MediaTypeParser mediaTypeParser = new MediaTypeParser.Builder<>() + .build(MediaTypeRegistry.getInstance()); @Override public Version getCompatibleVersion(@Nullable String acceptHeader, @Nullable String contentTypeHeader, boolean hasContent) { - Byte aVersion = XContentType.parseVersion(acceptHeader); + Byte aVersion = mediaTypeParser.parseVersion(acceptHeader); byte acceptVersion = aVersion == null ? Version.CURRENT.major : Integer.valueOf(aVersion).byteValue(); - Byte cVersion = XContentType.parseVersion(contentTypeHeader); + Byte cVersion = mediaTypeParser.parseVersion(contentTypeHeader); byte contentTypeVersion = cVersion == null ? Version.CURRENT.major : Integer.valueOf(cVersion).byteValue(); // accept version must be current or prior diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/SqlMediaTypeParser.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/SqlMediaTypeParser.java index 189dc137b654c..33160a9506503 100644 --- a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/SqlMediaTypeParser.java +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/SqlMediaTypeParser.java @@ -8,6 +8,7 @@ import org.elasticsearch.common.xcontent.MediaType; import org.elasticsearch.common.xcontent.MediaTypeParser; +import org.elasticsearch.common.xcontent.MediaTypeRegistry; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.rest.RestRequest; import org.elasticsearch.xpack.sql.action.SqlQueryRequest; @@ -19,7 +20,6 @@ public class SqlMediaTypeParser { private static final MediaTypeParser parser = new MediaTypeParser.Builder<>() - .copyFromMediaTypeParser(XContentType.mediaTypeParser) .withMediaTypeAndParams(TextFormat.PLAIN_TEXT.typeWithSubtype(), TextFormat.PLAIN_TEXT, Map.of("header", "present|absent", "charset", "utf-8")) .withMediaTypeAndParams(TextFormat.CSV.typeWithSubtype(), TextFormat.CSV, @@ -27,7 +27,14 @@ public class SqlMediaTypeParser { "delimiter", ".+"))// more detailed parsing is in TextFormat.CSV#delimiter .withMediaTypeAndParams(TextFormat.TSV.typeWithSubtype(), TextFormat.TSV, Map.of("header", "present|absent", "charset", "utf-8")) - .build(); + .withMediaTypeAndParams("text/vnd.elasticsearch+plain", TextFormat.PLAIN_TEXT, + Map.of("header", "present|absent", "charset", "utf-8", "compatible-with", "\\d+")) + .withMediaTypeAndParams("text/vnd.elasticsearch+csv", TextFormat.CSV, + Map.of("header", "present|absent", "charset", "utf-8", + "delimiter", ".+", "compatible-with", "\\d+"))// more detailed parsing is in TextFormat.CSV#delimiter + .withMediaTypeAndParams("text/vnd.elasticsearch+tsv", TextFormat.TSV, + Map.of("header", "present|absent", "charset", "utf-8", "compatible-with", "\\d+")) + .build(MediaTypeRegistry.getInstance()); /* * Since we support {@link TextFormat} and From 1b0c1d2b0e5e0f2604a0e803aee4c0698d15ec4d Mon Sep 17 00:00:00 2001 From: pgomulka Date: Mon, 12 Oct 2020 17:01:31 +0200 Subject: [PATCH 2/8] pull supported media types, but push down registry --- .../common/xcontent/MediaType.java | 10 ++- .../common/xcontent/MediaTypeDefinition.java | 68 +++++++++++++++ .../common/xcontent/MediaTypeParser.java | 79 ++++++----------- .../common/xcontent/MediaTypeRegistry.java | 85 +++++++++++++++++++ .../common/xcontent/XContentType.java | 49 ++++++----- .../java/org/elasticsearch/node/Node.java | 18 ++++ .../elasticsearch/plugins/ActionPlugin.java | 4 + .../plugins/MediaTypeRegistryPlugin.java | 27 ++++++ .../plugins/RestCompatibilityPlugin.java | 2 + .../compat/CompatibleVersionPlugin.java | 18 +++- .../xpack/sql/plugin/RestSqlQueryAction.java | 9 +- .../xpack/sql/plugin/SqlMediaTypeParser.java | 24 +++--- .../xpack/sql/plugin/SqlPlugin.java | 55 +++++++++++- .../xpack/sql/plugin/TextFormat.java | 21 ++++- .../sql/plugin/SqlMediaTypeParserTests.java | 8 +- 15 files changed, 382 insertions(+), 95 deletions(-) create mode 100644 libs/x-content/src/main/java/org/elasticsearch/common/xcontent/MediaTypeDefinition.java create mode 100644 libs/x-content/src/main/java/org/elasticsearch/common/xcontent/MediaTypeRegistry.java create mode 100644 server/src/main/java/org/elasticsearch/plugins/MediaTypeRegistryPlugin.java diff --git a/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/MediaType.java b/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/MediaType.java index 2d61120288706..0d2cc5fccb043 100644 --- a/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/MediaType.java +++ b/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/MediaType.java @@ -19,6 +19,10 @@ package org.elasticsearch.common.xcontent; +import java.util.Collections; +import java.util.Map; +import java.util.regex.Pattern; + /** * Abstracts a Media Type and a format parameter. * Media types are used as values on Content-Type and Accept headers @@ -46,7 +50,11 @@ public interface MediaType { /** * returns a string representation of a media type. */ - default String typeWithSubtype(){ + default String typeWithSubtype() { return type() + "/" + subtype(); } + + default Map validatedParameters() { + return Collections.emptyMap(); + } } diff --git a/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/MediaTypeDefinition.java b/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/MediaTypeDefinition.java new file mode 100644 index 0000000000000..6fddf5de1224f --- /dev/null +++ b/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/MediaTypeDefinition.java @@ -0,0 +1,68 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.common.xcontent; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.regex.Pattern; + +public class MediaTypeDefinition { + private final String typeWithSubtype; + private final MediaType mediaType; + private final String format; + private final Map parameters; + + public MediaTypeDefinition(String typeWithSubtype, MediaType mediaType, String format, Map parameters) { + + this.typeWithSubtype = typeWithSubtype; + this.mediaType = mediaType; + this.format = format; + Map parametersForMediaType = new HashMap<>(parameters.size()); + for (Map.Entry params : parameters.entrySet()) { + String parameterName = params.getKey().toLowerCase(Locale.ROOT); + String parameterRegex = params.getValue(); + Pattern pattern = Pattern.compile(parameterRegex, Pattern.CASE_INSENSITIVE); + parametersForMediaType.put(parameterName, pattern); + } + this.parameters = parametersForMediaType; + } + + + public static MediaTypeDefinition of(String typeWithSubtype, MediaType mediaType, String format, Map parameters) { + return new MediaTypeDefinition(typeWithSubtype, mediaType, format, parameters); + } + + public String getTypeWithSubtype() { + return typeWithSubtype; + } + + public MediaType getMediaType() { + return mediaType; + } + + public String getFormat() { + return format; + } + + public Map getParameters() { + return parameters; + } +} diff --git a/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/MediaTypeParser.java b/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/MediaTypeParser.java index 62a3f3fd915d0..feee9d10bce7f 100644 --- a/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/MediaTypeParser.java +++ b/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/MediaTypeParser.java @@ -1,3 +1,4 @@ + /* * Licensed to Elasticsearch under one or more contributor * license agreements. See the NOTICE file distributed with @@ -25,27 +26,22 @@ import java.util.regex.Pattern; public class MediaTypeParser { - private final Map formatToMediaType; - private final Map typeWithSubtypeToMediaType; - private final Map> parametersMap; - - public MediaTypeParser(Map formatToMediaType, Map typeWithSubtypeToMediaType, - Map> parametersMap) { - this.formatToMediaType = Map.copyOf(formatToMediaType); - this.typeWithSubtypeToMediaType = Map.copyOf(typeWithSubtypeToMediaType); - this.parametersMap = Map.copyOf(parametersMap); - } + private MediaTypeRegistry mediaTypeRegistry; + public MediaTypeParser(MediaTypeRegistry mediaTypeRegistry) { + this.mediaTypeRegistry = mediaTypeRegistry; + } + @SuppressWarnings("unchecked") public T fromMediaType(String mediaType) { ParsedMediaType parsedMediaType = parseMediaType(mediaType); - return parsedMediaType != null ? parsedMediaType.getMediaType() : null; + return parsedMediaType != null ? (T)parsedMediaType.getMediaType() : null; } - + @SuppressWarnings("unchecked") public T fromFormat(String format) { if (format == null) { return null; } - return formatToMediaType.get(format.toLowerCase(Locale.ROOT)); + return (T)mediaTypeRegistry.formatToMediaType(format.toLowerCase(Locale.ROOT)); } /** @@ -65,7 +61,7 @@ public ParsedMediaType parseMediaType(String headerValue) { String type = typeSubtype[0]; String subtype = typeSubtype[1]; String typeWithSubtype = type + "/" + subtype; - T xContentType = typeWithSubtypeToMediaType.get(typeWithSubtype); + MediaType xContentType = mediaTypeRegistry.typeWithSubtypeToMediaType(typeWithSubtype); if (xContentType != null) { Map parameters = new HashMap<>(); for (int i = 1; i < split.length; i++) { @@ -90,8 +86,8 @@ public ParsedMediaType parseMediaType(String headerValue) { } private boolean isValidParameter(String typeWithSubtype, String parameterName, String parameterValue) { - if (parametersMap.containsKey(typeWithSubtype)) { - Map parameters = parametersMap.get(typeWithSubtype); + if (mediaTypeRegistry.parametersFor(typeWithSubtype) != null) { + Map parameters = mediaTypeRegistry.parametersFor(typeWithSubtype); if (parameters.containsKey(parameterName)) { Pattern regex = parameters.get(parameterName); return regex.matcher(parameterValue).matches(); @@ -104,19 +100,32 @@ private boolean hasSpaces(String s) { return s.trim().equals(s) == false; } + private static final String COMPATIBLE_WITH_PARAMETER_NAME = "compatible-with"; + + public Byte parseVersion(String mediaType) { + ParsedMediaType parsedMediaType = parseMediaType(mediaType); + if (parsedMediaType != null) { + String version = parsedMediaType + .getParameters() + .get(COMPATIBLE_WITH_PARAMETER_NAME); + return version != null ? Byte.parseByte(version) : null; + } + return null; + } + /** * A media type object that contains all the information provided on a Content-Type or Accept header */ public class ParsedMediaType { private final Map parameters; - private final T mediaType; + private final MediaType mediaType; - public ParsedMediaType(T mediaType, Map parameters) { + public ParsedMediaType(MediaType mediaType, Map parameters) { this.parameters = parameters; this.mediaType = mediaType; } - public T getMediaType() { + public MediaType getMediaType() { return mediaType; } @@ -125,36 +134,4 @@ public Map getParameters() { } } - public static class Builder { - private final Map formatToMediaType = new HashMap<>(); - private final Map typeWithSubtypeToMediaType = new HashMap<>(); - private final Map> parametersMap = new HashMap<>(); - - public Builder withMediaTypeAndParams(String alternativeMediaType, T mediaType, Map paramNameAndValueRegex) { - typeWithSubtypeToMediaType.put(alternativeMediaType.toLowerCase(Locale.ROOT), mediaType); - formatToMediaType.put(mediaType.format(), mediaType); - - Map parametersForMediaType = new HashMap<>(paramNameAndValueRegex.size()); - for (Map.Entry params : paramNameAndValueRegex.entrySet()) { - String parameterName = params.getKey().toLowerCase(Locale.ROOT); - String parameterRegex = params.getValue(); - Pattern pattern = Pattern.compile(parameterRegex, Pattern.CASE_INSENSITIVE); - parametersForMediaType.put(parameterName, pattern); - } - parametersMap.put(alternativeMediaType, parametersForMediaType); - - return this; - } - - public Builder copyFromMediaTypeParser(MediaTypeParser mediaTypeParser) { - formatToMediaType.putAll(mediaTypeParser.formatToMediaType); - typeWithSubtypeToMediaType.putAll(mediaTypeParser.typeWithSubtypeToMediaType); - parametersMap.putAll(mediaTypeParser.parametersMap); - return this; - } - - public MediaTypeParser build() { - return new MediaTypeParser<>(formatToMediaType, typeWithSubtypeToMediaType, parametersMap); - } - } } diff --git a/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/MediaTypeRegistry.java b/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/MediaTypeRegistry.java new file mode 100644 index 0000000000000..3750a4a0e11b5 --- /dev/null +++ b/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/MediaTypeRegistry.java @@ -0,0 +1,85 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.common.xcontent; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; + +public class MediaTypeRegistry { + + private Map formatToMediaType = new ConcurrentHashMap<>(); + private Map typeWithSubtypeToMediaType = new ConcurrentHashMap<>(); + private Map> parametersMap = new ConcurrentHashMap<>(); + + public MediaTypeRegistry register(Map formatToMediaType, Map typeWithSubtypeToMediaType, Map> parametersMap) { + this.formatToMediaType.putAll(formatToMediaType); + this.typeWithSubtypeToMediaType.putAll(typeWithSubtypeToMediaType); + this.parametersMap.putAll(parametersMap); + return this; + } + + public MediaType formatToMediaType(String format) { + return formatToMediaType.get(format); + } + + public MediaType typeWithSubtypeToMediaType(String typeWithSubtype) { + return typeWithSubtypeToMediaType.get(typeWithSubtype); + } + + public Map parametersFor(String typeWithSubtype) { + return parametersMap.get(typeWithSubtype); + } + + public MediaTypeRegistry register(String alternativeMediaType, MediaType mediaType, Map paramNameAndValueRegex) { + typeWithSubtypeToMediaType.put(alternativeMediaType.toLowerCase(Locale.ROOT), mediaType); + formatToMediaType.put(mediaType.format(), mediaType); + + Map parametersForMediaType = new HashMap<>(paramNameAndValueRegex.size()); + for (Map.Entry params : paramNameAndValueRegex.entrySet()) { + String parameterName = params.getKey().toLowerCase(Locale.ROOT); + String parameterRegex = params.getValue(); + Pattern pattern = Pattern.compile(parameterRegex, Pattern.CASE_INSENSITIVE); + parametersForMediaType.put(parameterName, pattern); + } + parametersMap.put(alternativeMediaType, parametersForMediaType); + return this; + } + + public MediaTypeRegistry register(Collection mediaTypes) { + for (MediaTypeDefinition mediaTypeDefinition : mediaTypes) { + this.typeWithSubtypeToMediaType.put(mediaTypeDefinition.getTypeWithSubtype(), mediaTypeDefinition.getMediaType()); + this.parametersMap.put(mediaTypeDefinition.getTypeWithSubtype(), mediaTypeDefinition.getParameters()); + if(mediaTypeDefinition.getFormat() != null){ + this.formatToMediaType.put(mediaTypeDefinition.getFormat(), mediaTypeDefinition.getMediaType()); + } + } + return this; + } + + public void add(MediaTypeRegistry xContentTypeRegistry) { + formatToMediaType.putAll(xContentTypeRegistry.formatToMediaType); + typeWithSubtypeToMediaType.putAll(xContentTypeRegistry.typeWithSubtypeToMediaType); + parametersMap.putAll(xContentTypeRegistry.parametersMap); + } +} diff --git a/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/XContentType.java b/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/XContentType.java index 076a20bad006a..2c8a7695c5d38 100644 --- a/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/XContentType.java +++ b/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/XContentType.java @@ -114,26 +114,35 @@ public XContent xContent() { } }; - private static final String COMPATIBLE_WITH_PARAMETER_NAME = "compatible-with"; - private static final String VERSION_PATTERN = "\\d+"; - public static final MediaTypeParser mediaTypeParser = new MediaTypeParser.Builder() - .withMediaTypeAndParams("application/smile", SMILE, Collections.emptyMap()) - .withMediaTypeAndParams("application/cbor", CBOR, Collections.emptyMap()) - .withMediaTypeAndParams("application/json", JSON, Map.of("charset", "UTF-8")) - .withMediaTypeAndParams("application/yaml", YAML, Map.of("charset", "UTF-8")) - .withMediaTypeAndParams("application/*", JSON, Map.of("charset", "UTF-8")) - .withMediaTypeAndParams("application/x-ndjson", JSON, Map.of("charset", "UTF-8")) - .withMediaTypeAndParams("application/vnd.elasticsearch+json", JSON, - Map.of(COMPATIBLE_WITH_PARAMETER_NAME, VERSION_PATTERN, "charset", "UTF-8")) - .withMediaTypeAndParams("application/vnd.elasticsearch+smile", SMILE, - Map.of(COMPATIBLE_WITH_PARAMETER_NAME, VERSION_PATTERN, "charset", "UTF-8")) - .withMediaTypeAndParams("application/vnd.elasticsearch+yaml", YAML, - Map.of(COMPATIBLE_WITH_PARAMETER_NAME, VERSION_PATTERN, "charset", "UTF-8")) - .withMediaTypeAndParams("application/vnd.elasticsearch+cbor", CBOR, - Map.of(COMPATIBLE_WITH_PARAMETER_NAME, VERSION_PATTERN, "charset", "UTF-8")) - .withMediaTypeAndParams("application/vnd.elasticsearch+x-ndjson", JSON, - Map.of(COMPATIBLE_WITH_PARAMETER_NAME, VERSION_PATTERN, "charset", "UTF-8")) - .build(); + public static final String COMPATIBLE_WITH_PARAMETER_NAME = "compatible-with"; + public static final String VERSION_PATTERN = "\\d+"; + public static MediaTypeParser mediaTypeParser; + + public static void addMediaTypesToRegistry(MediaTypeRegistry global) { + MediaTypeRegistry xContentTypeRegistry = new MediaTypeRegistry() + .register("application/smile", SMILE, Collections.emptyMap()) + .register("application/cbor", CBOR, Collections.emptyMap()) + .register("application/json", JSON, Map.of("charset", "UTF-8")) + .register("application/yaml", YAML, Map.of("charset", "UTF-8")) + .register("application/*", JSON, Map.of("charset", "UTF-8")) + .register("application/x-ndjson", JSON, Map.of("charset", "UTF-8")) + .register("application/vnd.elasticsearch+json", JSON, + Map.of(COMPATIBLE_WITH_PARAMETER_NAME, VERSION_PATTERN, "charset", "UTF-8")) + .register("application/vnd.elasticsearch+smile", SMILE, + Map.of(COMPATIBLE_WITH_PARAMETER_NAME, VERSION_PATTERN, "charset", "UTF-8")) + .register("application/vnd.elasticsearch+yaml", YAML, + Map.of(COMPATIBLE_WITH_PARAMETER_NAME, VERSION_PATTERN, "charset", "UTF-8")) + .register("application/vnd.elasticsearch+cbor", CBOR, + Map.of(COMPATIBLE_WITH_PARAMETER_NAME, VERSION_PATTERN, "charset", "UTF-8")) + .register("application/vnd.elasticsearch+x-ndjson", JSON, + Map.of(COMPATIBLE_WITH_PARAMETER_NAME, VERSION_PATTERN, "charset", "UTF-8")); + global.add(xContentTypeRegistry); + mediaTypeParser = new MediaTypeParser<>(xContentTypeRegistry); + } + + public static MediaTypeParser getMediaTypeParser() { + return mediaTypeParser; + } /** * Accepts a format string, which is most of the time is equivalent to {@link XContentType#subtype()} diff --git a/server/src/main/java/org/elasticsearch/node/Node.java b/server/src/main/java/org/elasticsearch/node/Node.java index 15ca8e4f66bbd..f3bbcd27dc1f6 100644 --- a/server/src/main/java/org/elasticsearch/node/Node.java +++ b/server/src/main/java/org/elasticsearch/node/Node.java @@ -87,7 +87,10 @@ import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.common.util.PageCacheRecycler; +import org.elasticsearch.common.xcontent.MediaTypeDefinition; +import org.elasticsearch.common.xcontent.MediaTypeRegistry; import org.elasticsearch.common.xcontent.NamedXContentRegistry; +import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.core.internal.io.IOUtils; import org.elasticsearch.discovery.Discovery; import org.elasticsearch.discovery.DiscoveryModule; @@ -137,6 +140,7 @@ import org.elasticsearch.plugins.IndexStorePlugin; import org.elasticsearch.plugins.IngestPlugin; import org.elasticsearch.plugins.MapperPlugin; +import org.elasticsearch.plugins.MediaTypeRegistryPlugin; import org.elasticsearch.plugins.MetadataUpgrader; import org.elasticsearch.plugins.NetworkPlugin; import org.elasticsearch.plugins.PersistentTaskPlugin; @@ -330,6 +334,20 @@ protected Node(final Environment initialEnvironment, .collect(Collectors.toSet()); DiscoveryNode.setAdditionalRoles(additionalRoles); + Set mediaTypes = pluginsService.filterPlugins(ActionPlugin.class) + .stream() + .map(ActionPlugin::getAdditionalMediaTypes) + .flatMap(Collection::stream) + .collect(Collectors.toSet()); + + MediaTypeRegistry globalMediaTypeRegistry = new MediaTypeRegistry(); + globalMediaTypeRegistry.register(mediaTypes); + XContentType.addMediaTypesToRegistry(globalMediaTypeRegistry); + + // passes down to SQL and CompatibleVersion plugins + pluginsService.filterPlugins(MediaTypeRegistryPlugin.class) + .forEach(plugin -> plugin.setGlobalMediaTypeRegistry(globalMediaTypeRegistry)); + /* * Create the environment based on the finalized view of the settings. This is to ensure that components get the same setting * values, no matter they ask for them from. diff --git a/server/src/main/java/org/elasticsearch/plugins/ActionPlugin.java b/server/src/main/java/org/elasticsearch/plugins/ActionPlugin.java index ef9861f266222..c73e35375aade 100644 --- a/server/src/main/java/org/elasticsearch/plugins/ActionPlugin.java +++ b/server/src/main/java/org/elasticsearch/plugins/ActionPlugin.java @@ -34,6 +34,7 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.SettingsFilter; import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.common.xcontent.MediaTypeDefinition; import org.elasticsearch.rest.RestController; import org.elasticsearch.rest.RestHandler; import org.elasticsearch.rest.RestHeaderDefinition; @@ -181,4 +182,7 @@ default Collection> in return Collections.emptyList(); } + default Collection getAdditionalMediaTypes(){ + return Collections.emptyList(); + } } diff --git a/server/src/main/java/org/elasticsearch/plugins/MediaTypeRegistryPlugin.java b/server/src/main/java/org/elasticsearch/plugins/MediaTypeRegistryPlugin.java new file mode 100644 index 0000000000000..62b907375ea23 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/plugins/MediaTypeRegistryPlugin.java @@ -0,0 +1,27 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.plugins; + +import org.elasticsearch.common.xcontent.MediaTypeRegistry; + +public interface MediaTypeRegistryPlugin { + void setGlobalMediaTypeRegistry(MediaTypeRegistry globalMediaTypeRegistry); + +} diff --git a/server/src/main/java/org/elasticsearch/plugins/RestCompatibilityPlugin.java b/server/src/main/java/org/elasticsearch/plugins/RestCompatibilityPlugin.java index 9fd73a24ee87b..0346602af8277 100644 --- a/server/src/main/java/org/elasticsearch/plugins/RestCompatibilityPlugin.java +++ b/server/src/main/java/org/elasticsearch/plugins/RestCompatibilityPlugin.java @@ -21,6 +21,7 @@ import org.elasticsearch.Version; import org.elasticsearch.common.Nullable; +import org.elasticsearch.common.xcontent.MediaTypeRegistry; /** @@ -36,4 +37,5 @@ public interface RestCompatibilityPlugin { * @return a requested Compatible API Version */ Version getCompatibleVersion(@Nullable String acceptHeader, @Nullable String contentTypeHeader, boolean hasContent); + } diff --git a/x-pack/plugin/rest-compatibility/src/main/java/org/elasticsearch/compat/CompatibleVersionPlugin.java b/x-pack/plugin/rest-compatibility/src/main/java/org/elasticsearch/compat/CompatibleVersionPlugin.java index 2d4be753839eb..108899d6d1575 100644 --- a/x-pack/plugin/rest-compatibility/src/main/java/org/elasticsearch/compat/CompatibleVersionPlugin.java +++ b/x-pack/plugin/rest-compatibility/src/main/java/org/elasticsearch/compat/CompatibleVersionPlugin.java @@ -8,18 +8,23 @@ import org.elasticsearch.ElasticsearchStatusException; import org.elasticsearch.Version; import org.elasticsearch.common.Nullable; -import org.elasticsearch.common.xcontent.XContentType; +import org.elasticsearch.common.xcontent.MediaType; +import org.elasticsearch.common.xcontent.MediaTypeParser; +import org.elasticsearch.common.xcontent.MediaTypeRegistry; +import org.elasticsearch.plugins.MediaTypeRegistryPlugin; import org.elasticsearch.plugins.Plugin; import org.elasticsearch.plugins.RestCompatibilityPlugin; import org.elasticsearch.rest.RestStatus; -public class CompatibleVersionPlugin extends Plugin implements RestCompatibilityPlugin { +public class CompatibleVersionPlugin extends Plugin implements RestCompatibilityPlugin, MediaTypeRegistryPlugin { + + private MediaTypeParser mediaTypeParser; @Override public Version getCompatibleVersion(@Nullable String acceptHeader, @Nullable String contentTypeHeader, boolean hasContent) { - Byte aVersion = XContentType.parseVersion(acceptHeader); + Byte aVersion = mediaTypeParser.parseVersion(acceptHeader); byte acceptVersion = aVersion == null ? Version.CURRENT.major : Integer.valueOf(aVersion).byteValue(); - Byte cVersion = XContentType.parseVersion(contentTypeHeader); + Byte cVersion = mediaTypeParser.parseVersion(contentTypeHeader); byte contentTypeVersion = cVersion == null ? Version.CURRENT.major : Integer.valueOf(cVersion).byteValue(); // accept version must be current or prior @@ -72,4 +77,9 @@ public Version getCompatibleVersion(@Nullable String acceptHeader, @Nullable Str return Version.CURRENT; } + + @Override + public void setGlobalMediaTypeRegistry(MediaTypeRegistry globalMediaTypeRegistry) { + this.mediaTypeParser = new MediaTypeParser<>(globalMediaTypeRegistry); + } } diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/RestSqlQueryAction.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/RestSqlQueryAction.java index 763e460170cf0..d89a97bbb730a 100644 --- a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/RestSqlQueryAction.java +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/RestSqlQueryAction.java @@ -8,6 +8,7 @@ import org.elasticsearch.client.node.NodeClient; import org.elasticsearch.common.xcontent.MediaType; +import org.elasticsearch.common.xcontent.MediaTypeParser; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentType; @@ -34,8 +35,12 @@ public class RestSqlQueryAction extends BaseRestHandler { - private final SqlMediaTypeParser sqlMediaTypeParser = new SqlMediaTypeParser(); MediaType responseMediaType; + private final SqlMediaTypeParser sqlMediaTypeParser; + + public RestSqlQueryAction(MediaTypeParser mediaTypeParser) { + this.sqlMediaTypeParser = new SqlMediaTypeParser(mediaTypeParser); + } @Override public List routes() { @@ -84,6 +89,8 @@ public RestResponse buildResponse(SqlQueryResponse response) throws Exception { }); } + //we should override + diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/SqlMediaTypeParser.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/SqlMediaTypeParser.java index 189dc137b654c..5ccbc65866159 100644 --- a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/SqlMediaTypeParser.java +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/SqlMediaTypeParser.java @@ -13,21 +13,17 @@ import org.elasticsearch.xpack.sql.action.SqlQueryRequest; import org.elasticsearch.xpack.sql.proto.Mode; -import java.util.Map; import static org.elasticsearch.xpack.sql.proto.Protocol.URL_PARAM_FORMAT; public class SqlMediaTypeParser { - private static final MediaTypeParser parser = new MediaTypeParser.Builder<>() - .copyFromMediaTypeParser(XContentType.mediaTypeParser) - .withMediaTypeAndParams(TextFormat.PLAIN_TEXT.typeWithSubtype(), TextFormat.PLAIN_TEXT, - Map.of("header", "present|absent", "charset", "utf-8")) - .withMediaTypeAndParams(TextFormat.CSV.typeWithSubtype(), TextFormat.CSV, - Map.of("header", "present|absent", "charset", "utf-8", - "delimiter", ".+"))// more detailed parsing is in TextFormat.CSV#delimiter - .withMediaTypeAndParams(TextFormat.TSV.typeWithSubtype(), TextFormat.TSV, - Map.of("header", "present|absent", "charset", "utf-8")) - .build(); + private MediaTypeParser mediaTypeParser; + + public SqlMediaTypeParser(MediaTypeParser mediaTypeParser) { + //sql media types are already registered via plugin api + this.mediaTypeParser = mediaTypeParser; + } + /* * Since we support {@link TextFormat} and @@ -48,19 +44,19 @@ public MediaType getMediaType(RestRequest request, SqlQueryRequest sqlRequest) { // enforce CBOR response for drivers and CLI (unless instructed differently through the config param) return XContentType.CBOR; } else if (request.hasParam(URL_PARAM_FORMAT)) { - return validateColumnarRequest(sqlRequest.columnar(), parser.fromFormat(request.param(URL_PARAM_FORMAT))); + return validateColumnarRequest(sqlRequest.columnar(), mediaTypeParser.fromFormat(request.param(URL_PARAM_FORMAT))); } if (request.getHeaders().containsKey("Accept")) { String accept = request.header("Accept"); // */* means "I don't care" which we should treat like not specifying the header if ("*/*".equals(accept) == false) { - return validateColumnarRequest(sqlRequest.columnar(), parser.fromMediaType(accept)); + return validateColumnarRequest(sqlRequest.columnar(), mediaTypeParser.fromMediaType(accept)); } } String contentType = request.header("Content-Type"); assert contentType != null : "The Content-Type header is required"; - return validateColumnarRequest(sqlRequest.columnar(), parser.fromMediaType(contentType)); + return validateColumnarRequest(sqlRequest.columnar(), mediaTypeParser.fromMediaType(contentType)); } private static MediaType validateColumnarRequest(boolean requestIsColumnar, MediaType fromMediaType) { diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/SqlPlugin.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/SqlPlugin.java index 6f490f892b712..57f85ed349293 100644 --- a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/SqlPlugin.java +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/SqlPlugin.java @@ -16,12 +16,18 @@ import org.elasticsearch.common.settings.IndexScopedSettings; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.SettingsFilter; +import org.elasticsearch.common.xcontent.MediaType; +import org.elasticsearch.common.xcontent.MediaTypeDefinition; +import org.elasticsearch.common.xcontent.MediaTypeParser; +import org.elasticsearch.common.xcontent.MediaTypeRegistry; import org.elasticsearch.common.xcontent.NamedXContentRegistry; +import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.env.Environment; import org.elasticsearch.env.NodeEnvironment; import org.elasticsearch.license.LicenseUtils; import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.plugins.ActionPlugin; +import org.elasticsearch.plugins.MediaTypeRegistryPlugin; import org.elasticsearch.plugins.Plugin; import org.elasticsearch.repositories.RepositoriesService; import org.elasticsearch.rest.RestController; @@ -42,12 +48,14 @@ import org.elasticsearch.xpack.sql.execution.PlanExecutor; import org.elasticsearch.xpack.sql.type.SqlDataTypeRegistry; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.Map; import java.util.function.Supplier; -public class SqlPlugin extends Plugin implements ActionPlugin { +public class SqlPlugin extends Plugin implements ActionPlugin, MediaTypeRegistryPlugin { private final SqlLicenseChecker sqlLicenseChecker = new SqlLicenseChecker( (mode) -> { @@ -74,6 +82,7 @@ public class SqlPlugin extends Plugin implements ActionPlugin { } } ); + private MediaTypeParser mediaTypeParser; public SqlPlugin(Settings settings) { } @@ -106,7 +115,7 @@ public List getRestHandlers(Settings settings, RestController restC SettingsFilter settingsFilter, IndexNameExpressionResolver indexNameExpressionResolver, Supplier nodesInCluster) { - return Arrays.asList(new RestSqlQueryAction(), + return Arrays.asList(new RestSqlQueryAction(mediaTypeParser), new RestSqlTranslateAction(), new RestSqlClearCursorAction(), new RestSqlStatsAction()); @@ -124,4 +133,46 @@ public List getRestHandlers(Settings settings, RestController restC usageAction, infoAction); } + + @Override + public Collection getAdditionalMediaTypes() { + List mediaTypeDefinitions = new ArrayList<>(); + mediaTypeDefinitions.add(MediaTypeDefinition.of(TextFormat.PLAIN_TEXT.typeWithSubtype(), + TextFormat.PLAIN_TEXT, + TextFormat.PLAIN_TEXT.format(), + Map.of("header", "present|absent", "charset", "utf-8"))); + mediaTypeDefinitions.add(MediaTypeDefinition.of(TextFormat.CSV.typeWithSubtype(), + TextFormat.CSV, + TextFormat.CSV.format(), + Map.of("header", "present|absent", "charset", "utf-8", + "delimiter", ".+"))); + mediaTypeDefinitions.add(MediaTypeDefinition.of(TextFormat.TSV.typeWithSubtype(), + TextFormat.TSV, + TextFormat.TSV.format(), + Map.of("header", "present|absent", "charset", "utf-8"))); + + mediaTypeDefinitions.add(MediaTypeDefinition.of("text/vnd.elasticsearch+plain", + TextFormat.PLAIN_TEXT, + null, + Map.of("header", "present|absent", "charset", "utf-8", + XContentType.COMPATIBLE_WITH_PARAMETER_NAME, XContentType.VERSION_PATTERN))); + mediaTypeDefinitions.add(MediaTypeDefinition.of("text/vnd.elasticsearch+csv", + TextFormat.CSV, + null, + Map.of("header", "present|absent", "charset", "utf-8", + "delimiter", ".+", XContentType.COMPATIBLE_WITH_PARAMETER_NAME, XContentType.VERSION_PATTERN))); + mediaTypeDefinitions.add(MediaTypeDefinition.of("text/vnd.elasticsearch+tsv", + TextFormat.TSV, + null, + Map.of("header", "present|absent", "charset", "utf-8", + XContentType.COMPATIBLE_WITH_PARAMETER_NAME, XContentType.VERSION_PATTERN))); + + return mediaTypeDefinitions; + } + + + @Override + public void setGlobalMediaTypeRegistry(MediaTypeRegistry globalMediaTypeRegistry) { + this.mediaTypeParser = new MediaTypeParser<>(globalMediaTypeRegistry); + } } diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/TextFormat.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/TextFormat.java index d397d2316959c..71fd4babc5701 100644 --- a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/TextFormat.java +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/TextFormat.java @@ -24,8 +24,10 @@ import java.time.ZonedDateTime; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Objects; import java.util.function.Function; +import java.util.regex.Pattern; import static org.elasticsearch.xpack.sql.action.BasicFormatter.FormatOption.TEXT; import static org.elasticsearch.xpack.sql.proto.Protocol.URL_PARAM_DELIMITER; @@ -107,7 +109,11 @@ public String subtype() { return "plain"; } - + @Override + public Map validatedParameters() { + return Map.of("header", Pattern.compile("present|absent", Pattern.CASE_INSENSITIVE), + "charset", Pattern.compile("utf-8", Pattern.CASE_INSENSITIVE)); + } }, /** @@ -227,6 +233,13 @@ boolean hasHeader(RestRequest request) { public String subtype() { return "csv"; } + + @Override + public Map validatedParameters() { + return Map.of("header", Pattern.compile("present|absent", Pattern.CASE_INSENSITIVE), + "charset", Pattern.compile("utf-8", Pattern.CASE_INSENSITIVE), + "delimiter", Pattern.compile(".+", Pattern.CASE_INSENSITIVE)); + } }, TSV() { @@ -281,6 +294,12 @@ String maybeEscape(String value, Character __) { public String subtype() { return "tab-separated-values"; } + + @Override + public Map validatedParameters() { + return Map.of("header", Pattern.compile("present|absent", Pattern.CASE_INSENSITIVE), + "charset", Pattern.compile("utf-8", Pattern.CASE_INSENSITIVE)); + } }; private static final String FORMAT_TEXT = "txt"; diff --git a/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/plugin/SqlMediaTypeParserTests.java b/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/plugin/SqlMediaTypeParserTests.java index 0459b777b15f9..194f153610dad 100644 --- a/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/plugin/SqlMediaTypeParserTests.java +++ b/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/plugin/SqlMediaTypeParserTests.java @@ -6,8 +6,11 @@ package org.elasticsearch.xpack.sql.plugin; +import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.xcontent.MediaType; +import org.elasticsearch.common.xcontent.MediaTypeParser; +import org.elasticsearch.common.xcontent.MediaTypeRegistry; import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.rest.RestRequest; import org.elasticsearch.test.ESTestCase; @@ -27,7 +30,10 @@ import static org.hamcrest.CoreMatchers.nullValue; public class SqlMediaTypeParserTests extends ESTestCase { - SqlMediaTypeParser parser = new SqlMediaTypeParser(); + SqlMediaTypeParser parser = new SqlMediaTypeParser(new MediaTypeParser( + new MediaTypeRegistry() + .register(new SqlPlugin(Settings.EMPTY).getAdditionalMediaTypes()) + /*.register(XContentType)*/)); public void testPlainTextDetection() { MediaType text = parser.getMediaType(reqWithAccept("text/plain"), createTestInstance(false, Mode.PLAIN, false)); From 7bdf64fbbbeed88faf3e9c4ff68a9889d7146128 Mon Sep 17 00:00:00 2001 From: pgomulka Date: Mon, 12 Oct 2020 17:18:12 +0200 Subject: [PATCH 3/8] cleanup --- .../common/xcontent/MediaTypeRegistry.java | 3 +- .../common/xcontent/XContentType.java | 45 ++++++++++--------- .../java/org/elasticsearch/node/Node.java | 8 ++-- 3 files changed, 29 insertions(+), 27 deletions(-) diff --git a/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/MediaTypeRegistry.java b/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/MediaTypeRegistry.java index 3750a4a0e11b5..c634d638a3090 100644 --- a/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/MediaTypeRegistry.java +++ b/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/MediaTypeRegistry.java @@ -77,9 +77,10 @@ public MediaTypeRegistry register(Collection mediaTypes) { return this; } - public void add(MediaTypeRegistry xContentTypeRegistry) { + public MediaTypeRegistry register(MediaTypeRegistry xContentTypeRegistry) { formatToMediaType.putAll(xContentTypeRegistry.formatToMediaType); typeWithSubtypeToMediaType.putAll(xContentTypeRegistry.typeWithSubtypeToMediaType); parametersMap.putAll(xContentTypeRegistry.parametersMap); + return this; } } diff --git a/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/XContentType.java b/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/XContentType.java index 2c8a7695c5d38..8b514178fdd36 100644 --- a/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/XContentType.java +++ b/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/XContentType.java @@ -116,28 +116,29 @@ public XContent xContent() { public static final String COMPATIBLE_WITH_PARAMETER_NAME = "compatible-with"; public static final String VERSION_PATTERN = "\\d+"; - public static MediaTypeParser mediaTypeParser; - - public static void addMediaTypesToRegistry(MediaTypeRegistry global) { - MediaTypeRegistry xContentTypeRegistry = new MediaTypeRegistry() - .register("application/smile", SMILE, Collections.emptyMap()) - .register("application/cbor", CBOR, Collections.emptyMap()) - .register("application/json", JSON, Map.of("charset", "UTF-8")) - .register("application/yaml", YAML, Map.of("charset", "UTF-8")) - .register("application/*", JSON, Map.of("charset", "UTF-8")) - .register("application/x-ndjson", JSON, Map.of("charset", "UTF-8")) - .register("application/vnd.elasticsearch+json", JSON, - Map.of(COMPATIBLE_WITH_PARAMETER_NAME, VERSION_PATTERN, "charset", "UTF-8")) - .register("application/vnd.elasticsearch+smile", SMILE, - Map.of(COMPATIBLE_WITH_PARAMETER_NAME, VERSION_PATTERN, "charset", "UTF-8")) - .register("application/vnd.elasticsearch+yaml", YAML, - Map.of(COMPATIBLE_WITH_PARAMETER_NAME, VERSION_PATTERN, "charset", "UTF-8")) - .register("application/vnd.elasticsearch+cbor", CBOR, - Map.of(COMPATIBLE_WITH_PARAMETER_NAME, VERSION_PATTERN, "charset", "UTF-8")) - .register("application/vnd.elasticsearch+x-ndjson", JSON, - Map.of(COMPATIBLE_WITH_PARAMETER_NAME, VERSION_PATTERN, "charset", "UTF-8")); - global.add(xContentTypeRegistry); - mediaTypeParser = new MediaTypeParser<>(xContentTypeRegistry); + + private static final MediaTypeRegistry mediaTypeRegistry = new MediaTypeRegistry() + .register("application/smile", SMILE, Collections.emptyMap()) + .register("application/cbor", CBOR, Collections.emptyMap()) + .register("application/json", JSON, Map.of("charset", "UTF-8")) + .register("application/yaml", YAML, Map.of("charset", "UTF-8")) + .register("application/*", JSON, Map.of("charset", "UTF-8")) + .register("application/x-ndjson", JSON, Map.of("charset", "UTF-8")) + .register("application/vnd.elasticsearch+json", JSON, + Map.of(COMPATIBLE_WITH_PARAMETER_NAME, VERSION_PATTERN, "charset", "UTF-8")) + .register("application/vnd.elasticsearch+smile", SMILE, + Map.of(COMPATIBLE_WITH_PARAMETER_NAME, VERSION_PATTERN, "charset", "UTF-8")) + .register("application/vnd.elasticsearch+yaml", YAML, + Map.of(COMPATIBLE_WITH_PARAMETER_NAME, VERSION_PATTERN, "charset", "UTF-8")) + .register("application/vnd.elasticsearch+cbor", CBOR, + Map.of(COMPATIBLE_WITH_PARAMETER_NAME, VERSION_PATTERN, "charset", "UTF-8")) + .register("application/vnd.elasticsearch+x-ndjson", JSON, + Map.of(COMPATIBLE_WITH_PARAMETER_NAME, VERSION_PATTERN, "charset", "UTF-8")); + + private static MediaTypeParser mediaTypeParser = new MediaTypeParser<>(mediaTypeRegistry); + + public static MediaTypeRegistry getMediaTypeRegistry() { + return mediaTypeRegistry; } public static MediaTypeParser getMediaTypeParser() { diff --git a/server/src/main/java/org/elasticsearch/node/Node.java b/server/src/main/java/org/elasticsearch/node/Node.java index f3bbcd27dc1f6..f5324f78d2d89 100644 --- a/server/src/main/java/org/elasticsearch/node/Node.java +++ b/server/src/main/java/org/elasticsearch/node/Node.java @@ -334,15 +334,15 @@ protected Node(final Environment initialEnvironment, .collect(Collectors.toSet()); DiscoveryNode.setAdditionalRoles(additionalRoles); - Set mediaTypes = pluginsService.filterPlugins(ActionPlugin.class) + Set mediaTypesFromPlugins = pluginsService.filterPlugins(ActionPlugin.class) .stream() .map(ActionPlugin::getAdditionalMediaTypes) .flatMap(Collection::stream) .collect(Collectors.toSet()); - MediaTypeRegistry globalMediaTypeRegistry = new MediaTypeRegistry(); - globalMediaTypeRegistry.register(mediaTypes); - XContentType.addMediaTypesToRegistry(globalMediaTypeRegistry); + MediaTypeRegistry globalMediaTypeRegistry = new MediaTypeRegistry() + .register(mediaTypesFromPlugins) + .register(XContentType.getMediaTypeRegistry()); // passes down to SQL and CompatibleVersion plugins pluginsService.filterPlugins(MediaTypeRegistryPlugin.class) From ff93fd256743f5c06cdfb4c0563617ce9b806ad0 Mon Sep 17 00:00:00 2001 From: pgomulka Date: Tue, 13 Oct 2020 10:21:26 +0200 Subject: [PATCH 4/8] remove mediatypedefinition --- .../common/xcontent/MediaTypeDefinition.java | 68 ------------------- .../common/xcontent/MediaTypeRegistry.java | 33 ++++++--- .../common/xcontent/XContentType.java | 4 -- .../java/org/elasticsearch/node/Node.java | 6 +- .../elasticsearch/plugins/ActionPlugin.java | 6 +- .../xpack/sql/plugin/RestSqlQueryAction.java | 5 +- .../xpack/sql/plugin/SqlMediaTypeParser.java | 5 +- .../xpack/sql/plugin/SqlPlugin.java | 39 +++++------ .../sql/plugin/SqlMediaTypeParserTests.java | 5 +- 9 files changed, 54 insertions(+), 117 deletions(-) delete mode 100644 libs/x-content/src/main/java/org/elasticsearch/common/xcontent/MediaTypeDefinition.java diff --git a/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/MediaTypeDefinition.java b/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/MediaTypeDefinition.java deleted file mode 100644 index 6fddf5de1224f..0000000000000 --- a/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/MediaTypeDefinition.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Licensed to Elasticsearch under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.elasticsearch.common.xcontent; - -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; -import java.util.regex.Pattern; - -public class MediaTypeDefinition { - private final String typeWithSubtype; - private final MediaType mediaType; - private final String format; - private final Map parameters; - - public MediaTypeDefinition(String typeWithSubtype, MediaType mediaType, String format, Map parameters) { - - this.typeWithSubtype = typeWithSubtype; - this.mediaType = mediaType; - this.format = format; - Map parametersForMediaType = new HashMap<>(parameters.size()); - for (Map.Entry params : parameters.entrySet()) { - String parameterName = params.getKey().toLowerCase(Locale.ROOT); - String parameterRegex = params.getValue(); - Pattern pattern = Pattern.compile(parameterRegex, Pattern.CASE_INSENSITIVE); - parametersForMediaType.put(parameterName, pattern); - } - this.parameters = parametersForMediaType; - } - - - public static MediaTypeDefinition of(String typeWithSubtype, MediaType mediaType, String format, Map parameters) { - return new MediaTypeDefinition(typeWithSubtype, mediaType, format, parameters); - } - - public String getTypeWithSubtype() { - return typeWithSubtype; - } - - public MediaType getMediaType() { - return mediaType; - } - - public String getFormat() { - return format; - } - - public Map getParameters() { - return parameters; - } -} diff --git a/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/MediaTypeRegistry.java b/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/MediaTypeRegistry.java index c634d638a3090..913d4d677b274 100644 --- a/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/MediaTypeRegistry.java +++ b/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/MediaTypeRegistry.java @@ -39,6 +39,21 @@ public MediaTypeRegistry register(Map formatToM return this; } + public MediaTypeRegistry register(String typeWithSubtype, T mediaType, String format, Map parametersMap) { + if (format != null) { + this.formatToMediaType.put(format, mediaType); + } + this.typeWithSubtypeToMediaType.put(typeWithSubtype,mediaType); + Map parametersForMediaType = new HashMap<>(parametersMap.size()); + for (Map.Entry params : parametersMap.entrySet()) { + String parameterName = params.getKey().toLowerCase(Locale.ROOT); + String parameterRegex = params.getValue(); + Pattern pattern = Pattern.compile(parameterRegex, Pattern.CASE_INSENSITIVE); + parametersForMediaType.put(parameterName, pattern); + } + this.parametersMap.put(typeWithSubtype,parametersForMediaType); + return this; + } public MediaType formatToMediaType(String format) { return formatToMediaType.get(format); } @@ -66,21 +81,17 @@ public MediaTypeRegistry register(String alternativeMediaType, MediaType mediaTy return this; } - public MediaTypeRegistry register(Collection mediaTypes) { - for (MediaTypeDefinition mediaTypeDefinition : mediaTypes) { - this.typeWithSubtypeToMediaType.put(mediaTypeDefinition.getTypeWithSubtype(), mediaTypeDefinition.getMediaType()); - this.parametersMap.put(mediaTypeDefinition.getTypeWithSubtype(), mediaTypeDefinition.getParameters()); - if(mediaTypeDefinition.getFormat() != null){ - this.formatToMediaType.put(mediaTypeDefinition.getFormat(), mediaTypeDefinition.getMediaType()); - } - } - return this; - } - public MediaTypeRegistry register(MediaTypeRegistry xContentTypeRegistry) { formatToMediaType.putAll(xContentTypeRegistry.formatToMediaType); typeWithSubtypeToMediaType.putAll(xContentTypeRegistry.typeWithSubtypeToMediaType); parametersMap.putAll(xContentTypeRegistry.parametersMap); return this; } + public MediaTypeRegistry register(Collection mediaTypeRegistries ) { + for (MediaTypeRegistry mediaTypeRegistry : mediaTypeRegistries) { + register(mediaTypeRegistry); + } + return this; + } + } diff --git a/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/XContentType.java b/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/XContentType.java index 8b514178fdd36..be8d9189f6cd4 100644 --- a/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/XContentType.java +++ b/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/XContentType.java @@ -141,10 +141,6 @@ public static MediaTypeRegistry getMediaTypeRegistry() { return mediaTypeRegistry; } - public static MediaTypeParser getMediaTypeParser() { - return mediaTypeParser; - } - /** * Accepts a format string, which is most of the time is equivalent to {@link XContentType#subtype()} * and attempts to match the value to an {@link XContentType}. diff --git a/server/src/main/java/org/elasticsearch/node/Node.java b/server/src/main/java/org/elasticsearch/node/Node.java index f5324f78d2d89..cebeba75599c6 100644 --- a/server/src/main/java/org/elasticsearch/node/Node.java +++ b/server/src/main/java/org/elasticsearch/node/Node.java @@ -87,7 +87,6 @@ import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.common.util.PageCacheRecycler; -import org.elasticsearch.common.xcontent.MediaTypeDefinition; import org.elasticsearch.common.xcontent.MediaTypeRegistry; import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.common.xcontent.XContentType; @@ -334,11 +333,10 @@ protected Node(final Environment initialEnvironment, .collect(Collectors.toSet()); DiscoveryNode.setAdditionalRoles(additionalRoles); - Set mediaTypesFromPlugins = pluginsService.filterPlugins(ActionPlugin.class) + Collection mediaTypesFromPlugins = pluginsService.filterPlugins(ActionPlugin.class) .stream() .map(ActionPlugin::getAdditionalMediaTypes) - .flatMap(Collection::stream) - .collect(Collectors.toSet()); + .collect(toList()); MediaTypeRegistry globalMediaTypeRegistry = new MediaTypeRegistry() .register(mediaTypesFromPlugins) diff --git a/server/src/main/java/org/elasticsearch/plugins/ActionPlugin.java b/server/src/main/java/org/elasticsearch/plugins/ActionPlugin.java index c73e35375aade..266a0e7550be7 100644 --- a/server/src/main/java/org/elasticsearch/plugins/ActionPlugin.java +++ b/server/src/main/java/org/elasticsearch/plugins/ActionPlugin.java @@ -34,7 +34,7 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.SettingsFilter; import org.elasticsearch.common.util.concurrent.ThreadContext; -import org.elasticsearch.common.xcontent.MediaTypeDefinition; +import org.elasticsearch.common.xcontent.MediaTypeRegistry; import org.elasticsearch.rest.RestController; import org.elasticsearch.rest.RestHandler; import org.elasticsearch.rest.RestHeaderDefinition; @@ -182,7 +182,7 @@ default Collection> in return Collections.emptyList(); } - default Collection getAdditionalMediaTypes(){ - return Collections.emptyList(); + default MediaTypeRegistry getAdditionalMediaTypes(){ + return new MediaTypeRegistry(); } } diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/RestSqlQueryAction.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/RestSqlQueryAction.java index d89a97bbb730a..64495ed816eb0 100644 --- a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/RestSqlQueryAction.java +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/RestSqlQueryAction.java @@ -9,6 +9,7 @@ import org.elasticsearch.client.node.NodeClient; import org.elasticsearch.common.xcontent.MediaType; import org.elasticsearch.common.xcontent.MediaTypeParser; +import org.elasticsearch.common.xcontent.MediaTypeRegistry; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentType; @@ -38,8 +39,8 @@ public class RestSqlQueryAction extends BaseRestHandler { MediaType responseMediaType; private final SqlMediaTypeParser sqlMediaTypeParser; - public RestSqlQueryAction(MediaTypeParser mediaTypeParser) { - this.sqlMediaTypeParser = new SqlMediaTypeParser(mediaTypeParser); + public RestSqlQueryAction(MediaTypeRegistry globalMediaTypeRegistry) { + this.sqlMediaTypeParser = new SqlMediaTypeParser(globalMediaTypeRegistry); } @Override diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/SqlMediaTypeParser.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/SqlMediaTypeParser.java index 5ccbc65866159..30e28fb4a35bb 100644 --- a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/SqlMediaTypeParser.java +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/SqlMediaTypeParser.java @@ -8,6 +8,7 @@ import org.elasticsearch.common.xcontent.MediaType; import org.elasticsearch.common.xcontent.MediaTypeParser; +import org.elasticsearch.common.xcontent.MediaTypeRegistry; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.rest.RestRequest; import org.elasticsearch.xpack.sql.action.SqlQueryRequest; @@ -19,9 +20,9 @@ public class SqlMediaTypeParser { private MediaTypeParser mediaTypeParser; - public SqlMediaTypeParser(MediaTypeParser mediaTypeParser) { + public SqlMediaTypeParser(MediaTypeRegistry globalMediaTypeRegistry) { //sql media types are already registered via plugin api - this.mediaTypeParser = mediaTypeParser; + this.mediaTypeParser = new MediaTypeParser<>(globalMediaTypeRegistry); } diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/SqlPlugin.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/SqlPlugin.java index 57f85ed349293..f06d368843f23 100644 --- a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/SqlPlugin.java +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/SqlPlugin.java @@ -16,9 +16,6 @@ import org.elasticsearch.common.settings.IndexScopedSettings; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.SettingsFilter; -import org.elasticsearch.common.xcontent.MediaType; -import org.elasticsearch.common.xcontent.MediaTypeDefinition; -import org.elasticsearch.common.xcontent.MediaTypeParser; import org.elasticsearch.common.xcontent.MediaTypeRegistry; import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.common.xcontent.XContentType; @@ -82,7 +79,7 @@ public class SqlPlugin extends Plugin implements ActionPlugin, MediaTypeRegistry } } ); - private MediaTypeParser mediaTypeParser; + private MediaTypeRegistry globalMediaTypeRegistry; public SqlPlugin(Settings settings) { } @@ -115,7 +112,7 @@ public List getRestHandlers(Settings settings, RestController restC SettingsFilter settingsFilter, IndexNameExpressionResolver indexNameExpressionResolver, Supplier nodesInCluster) { - return Arrays.asList(new RestSqlQueryAction(mediaTypeParser), + return Arrays.asList(new RestSqlQueryAction(globalMediaTypeRegistry), new RestSqlTranslateAction(), new RestSqlClearCursorAction(), new RestSqlStatsAction()); @@ -135,44 +132,44 @@ public List getRestHandlers(Settings settings, RestController restC } @Override - public Collection getAdditionalMediaTypes() { - List mediaTypeDefinitions = new ArrayList<>(); - mediaTypeDefinitions.add(MediaTypeDefinition.of(TextFormat.PLAIN_TEXT.typeWithSubtype(), + public MediaTypeRegistry getAdditionalMediaTypes() { + MediaTypeRegistry mediaTypeRegistry = new MediaTypeRegistry(); + mediaTypeRegistry.register(TextFormat.PLAIN_TEXT.typeWithSubtype(), TextFormat.PLAIN_TEXT, TextFormat.PLAIN_TEXT.format(), - Map.of("header", "present|absent", "charset", "utf-8"))); - mediaTypeDefinitions.add(MediaTypeDefinition.of(TextFormat.CSV.typeWithSubtype(), + Map.of("header", "present|absent", "charset", "utf-8")) + .register(TextFormat.CSV.typeWithSubtype(), TextFormat.CSV, TextFormat.CSV.format(), Map.of("header", "present|absent", "charset", "utf-8", - "delimiter", ".+"))); - mediaTypeDefinitions.add(MediaTypeDefinition.of(TextFormat.TSV.typeWithSubtype(), + "delimiter", ".+")) + .register(TextFormat.TSV.typeWithSubtype(), TextFormat.TSV, TextFormat.TSV.format(), - Map.of("header", "present|absent", "charset", "utf-8"))); + Map.of("header", "present|absent", "charset", "utf-8")) - mediaTypeDefinitions.add(MediaTypeDefinition.of("text/vnd.elasticsearch+plain", + .register("text/vnd.elasticsearch+plain", TextFormat.PLAIN_TEXT, null, Map.of("header", "present|absent", "charset", "utf-8", - XContentType.COMPATIBLE_WITH_PARAMETER_NAME, XContentType.VERSION_PATTERN))); - mediaTypeDefinitions.add(MediaTypeDefinition.of("text/vnd.elasticsearch+csv", + XContentType.COMPATIBLE_WITH_PARAMETER_NAME, XContentType.VERSION_PATTERN)) + .register("text/vnd.elasticsearch+csv", TextFormat.CSV, null, Map.of("header", "present|absent", "charset", "utf-8", - "delimiter", ".+", XContentType.COMPATIBLE_WITH_PARAMETER_NAME, XContentType.VERSION_PATTERN))); - mediaTypeDefinitions.add(MediaTypeDefinition.of("text/vnd.elasticsearch+tsv", + "delimiter", ".+", XContentType.COMPATIBLE_WITH_PARAMETER_NAME, XContentType.VERSION_PATTERN)) + .register("text/vnd.elasticsearch+tsv", TextFormat.TSV, null, Map.of("header", "present|absent", "charset", "utf-8", - XContentType.COMPATIBLE_WITH_PARAMETER_NAME, XContentType.VERSION_PATTERN))); + XContentType.COMPATIBLE_WITH_PARAMETER_NAME, XContentType.VERSION_PATTERN)); - return mediaTypeDefinitions; + return mediaTypeRegistry; } @Override public void setGlobalMediaTypeRegistry(MediaTypeRegistry globalMediaTypeRegistry) { - this.mediaTypeParser = new MediaTypeParser<>(globalMediaTypeRegistry); + this.globalMediaTypeRegistry = globalMediaTypeRegistry; } } diff --git a/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/plugin/SqlMediaTypeParserTests.java b/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/plugin/SqlMediaTypeParserTests.java index 194f153610dad..fd3e49801655f 100644 --- a/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/plugin/SqlMediaTypeParserTests.java +++ b/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/plugin/SqlMediaTypeParserTests.java @@ -12,6 +12,7 @@ import org.elasticsearch.common.xcontent.MediaTypeParser; import org.elasticsearch.common.xcontent.MediaTypeRegistry; import org.elasticsearch.common.xcontent.NamedXContentRegistry; +import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.rest.RestRequest; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.rest.FakeRestRequest; @@ -30,10 +31,10 @@ import static org.hamcrest.CoreMatchers.nullValue; public class SqlMediaTypeParserTests extends ESTestCase { - SqlMediaTypeParser parser = new SqlMediaTypeParser(new MediaTypeParser( + SqlMediaTypeParser parser = new SqlMediaTypeParser( new MediaTypeRegistry() .register(new SqlPlugin(Settings.EMPTY).getAdditionalMediaTypes()) - /*.register(XContentType)*/)); + .register(XContentType.getMediaTypeRegistry())); public void testPlainTextDetection() { MediaType text = parser.getMediaType(reqWithAccept("text/plain"), createTestInstance(false, Mode.PLAIN, false)); From f5c60ff67b567a9be9655e9fa15f05685d5989ab Mon Sep 17 00:00:00 2001 From: pgomulka Date: Thu, 15 Oct 2020 11:42:56 +0200 Subject: [PATCH 5/8] passing a mediatyperegistry to plugin, but keeping the signature the same --- .../java/org/elasticsearch/node/Node.java | 36 ++++++++++--------- .../plugins/RestCompatibilityPlugin.java | 2 +- .../org/elasticsearch/node/NodeTests.java | 12 ++++--- .../rest/RestControllerTests.java | 2 +- .../compat/CompatibleVersionPlugin.java | 8 +++-- .../compat/CompatibleVersionPluginTests.java | 4 ++- 6 files changed, 38 insertions(+), 26 deletions(-) diff --git a/server/src/main/java/org/elasticsearch/node/Node.java b/server/src/main/java/org/elasticsearch/node/Node.java index cebeba75599c6..dd605ff9ef366 100644 --- a/server/src/main/java/org/elasticsearch/node/Node.java +++ b/server/src/main/java/org/elasticsearch/node/Node.java @@ -139,7 +139,6 @@ import org.elasticsearch.plugins.IndexStorePlugin; import org.elasticsearch.plugins.IngestPlugin; import org.elasticsearch.plugins.MapperPlugin; -import org.elasticsearch.plugins.MediaTypeRegistryPlugin; import org.elasticsearch.plugins.MetadataUpgrader; import org.elasticsearch.plugins.NetworkPlugin; import org.elasticsearch.plugins.PersistentTaskPlugin; @@ -333,18 +332,6 @@ protected Node(final Environment initialEnvironment, .collect(Collectors.toSet()); DiscoveryNode.setAdditionalRoles(additionalRoles); - Collection mediaTypesFromPlugins = pluginsService.filterPlugins(ActionPlugin.class) - .stream() - .map(ActionPlugin::getAdditionalMediaTypes) - .collect(toList()); - - MediaTypeRegistry globalMediaTypeRegistry = new MediaTypeRegistry() - .register(mediaTypesFromPlugins) - .register(XContentType.getMediaTypeRegistry()); - - // passes down to SQL and CompatibleVersion plugins - pluginsService.filterPlugins(MediaTypeRegistryPlugin.class) - .forEach(plugin -> plugin.setGlobalMediaTypeRegistry(globalMediaTypeRegistry)); /* * Create the environment based on the finalized view of the settings. This is to ensure that components get the same setting @@ -545,10 +532,25 @@ protected Node(final Environment initialEnvironment, repositoriesServiceReference::get).stream()) .collect(Collectors.toList()); + Collection mediaTypesFromPlugins = pluginsService.filterPlugins(ActionPlugin.class) + .stream() + .map(ActionPlugin::getAdditionalMediaTypes) + .collect(toList()); + + MediaTypeRegistry globalMediaTypeRegistry = new MediaTypeRegistry() + .register(mediaTypesFromPlugins) + .register(XContentType.getMediaTypeRegistry()); + + // passes down to SQL and CompatibleVersion plugins +// pluginsService.filterPlugins(MediaTypeRegistryPlugin.class) +// .forEach(plugin -> plugin.setGlobalMediaTypeRegistry(globalMediaTypeRegistry)); + + CompatibleVersion restCompatibleFunction = getRestCompatibleFunction(globalMediaTypeRegistry); + ActionModule actionModule = new ActionModule(settings, clusterModule.getIndexNameExpressionResolver(), settingsModule.getIndexScopedSettings(), settingsModule.getClusterSettings(), settingsModule.getSettingsFilter(), threadPool, pluginsService.filterPlugins(ActionPlugin.class), client, circuitBreakerService, usageService, - systemIndices, getRestCompatibleFunction()); + systemIndices, restCompatibleFunction); modules.add(actionModule); final RestController restController = actionModule.getRestController(); @@ -726,14 +728,16 @@ protected Node(final Environment initialEnvironment, /** * @return A function that can be used to determine the requested REST compatible version * package scope for testing + * @param globalMediaTypeRegistry */ - CompatibleVersion getRestCompatibleFunction() { + CompatibleVersion getRestCompatibleFunction(MediaTypeRegistry globalMediaTypeRegistry) { List restCompatibilityPlugins = pluginsService.filterPlugins(RestCompatibilityPlugin.class); final CompatibleVersion compatibleVersion; if (restCompatibilityPlugins.size() > 1) { throw new IllegalStateException("Only one RestCompatibilityPlugin is allowed"); } else if (restCompatibilityPlugins.size() == 1) { - compatibleVersion = restCompatibilityPlugins.get(0)::getCompatibleVersion; + compatibleVersion = (acceptHeader, contentTypeHeader, hasContent) -> + restCompatibilityPlugins.get(0).getCompatibleVersion(acceptHeader, contentTypeHeader, hasContent, globalMediaTypeRegistry); } else { compatibleVersion = CompatibleVersion.CURRENT_VERSION; } diff --git a/server/src/main/java/org/elasticsearch/plugins/RestCompatibilityPlugin.java b/server/src/main/java/org/elasticsearch/plugins/RestCompatibilityPlugin.java index 0346602af8277..cea394036d6e2 100644 --- a/server/src/main/java/org/elasticsearch/plugins/RestCompatibilityPlugin.java +++ b/server/src/main/java/org/elasticsearch/plugins/RestCompatibilityPlugin.java @@ -36,6 +36,6 @@ public interface RestCompatibilityPlugin { * @param hasContent - a flag indicating if a request has content * @return a requested Compatible API Version */ - Version getCompatibleVersion(@Nullable String acceptHeader, @Nullable String contentTypeHeader, boolean hasContent); + Version getCompatibleVersion(@Nullable String acceptHeader, @Nullable String contentTypeHeader, boolean hasContent, MediaTypeRegistry mediaTypeRegistry); } diff --git a/server/src/test/java/org/elasticsearch/node/NodeTests.java b/server/src/test/java/org/elasticsearch/node/NodeTests.java index 3741b172653a1..e6e60c6d5bdd4 100644 --- a/server/src/test/java/org/elasticsearch/node/NodeTests.java +++ b/server/src/test/java/org/elasticsearch/node/NodeTests.java @@ -28,6 +28,8 @@ import org.elasticsearch.common.network.NetworkModule; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.transport.BoundTransportAddress; +import org.elasticsearch.common.xcontent.MediaTypeRegistry; +import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.env.Environment; import org.elasticsearch.index.IndexService; import org.elasticsearch.index.engine.Engine.Searcher; @@ -345,14 +347,16 @@ public void setCircuitBreaker(CircuitBreaker circuitBreaker) { public static class TestRestCompatibility1 extends Plugin implements RestCompatibilityPlugin { @Override - public Version getCompatibleVersion(String acceptHeader, String contentTypeHeader, boolean hasContent) { + public Version getCompatibleVersion(String acceptHeader, String contentTypeHeader, boolean hasContent, + MediaTypeRegistry mediaTypeRegistry) { return Version.CURRENT.previousMajor(); } } public static class TestRestCompatibility2 extends Plugin implements RestCompatibilityPlugin { @Override - public Version getCompatibleVersion(String acceptHeader, String contentTypeHeader, boolean hasContent) { + public Version getCompatibleVersion(String acceptHeader, String contentTypeHeader, boolean hasContent, + MediaTypeRegistry mediaTypeRegistry) { return null; } } @@ -376,7 +380,7 @@ public void testCorrectUsageOfRestCompatibilityPlugin() throws IOException { plugins.add(TestRestCompatibility1.class); try (Node node = new MockNode(settings.build(), plugins)) { - CompatibleVersion restCompatibleFunction = node.getRestCompatibleFunction(); + CompatibleVersion restCompatibleFunction = node.getRestCompatibleFunction(XContentType.getMediaTypeRegistry()); assertThat(restCompatibleFunction.get("", "", false), equalTo(Version.CURRENT.previousMajor())); } } @@ -389,7 +393,7 @@ public void testDefaultingRestCompatibilityPlugin() throws IOException { List> plugins = basePlugins(); try (Node node = new MockNode(settings.build(), plugins)) { - CompatibleVersion restCompatibleFunction = node.getRestCompatibleFunction(); + CompatibleVersion restCompatibleFunction = node.getRestCompatibleFunction(XContentType.getMediaTypeRegistry()); assertThat(restCompatibleFunction.get("", "", false), equalTo(Version.CURRENT)); } } diff --git a/server/src/test/java/org/elasticsearch/rest/RestControllerTests.java b/server/src/test/java/org/elasticsearch/rest/RestControllerTests.java index 2dd6d00b43e88..4346600692091 100644 --- a/server/src/test/java/org/elasticsearch/rest/RestControllerTests.java +++ b/server/src/test/java/org/elasticsearch/rest/RestControllerTests.java @@ -98,7 +98,7 @@ public void setup() { HttpServerTransport httpServerTransport = new TestHttpServerTransport(); client = new NoOpNodeClient(this.getTestName()); restController = new RestController(Collections.emptySet(), null, client, circuitBreakerService, usageService, - , CompatibleVersion.CURRENT_VERSION); + CompatibleVersion.CURRENT_VERSION); restController.registerHandler(RestRequest.Method.GET, "/", (request, channel, client) -> channel.sendResponse( new BytesRestResponse(RestStatus.OK, BytesRestResponse.TEXT_CONTENT_TYPE, BytesArray.EMPTY))); diff --git a/x-pack/plugin/rest-compatibility/src/main/java/org/elasticsearch/compat/CompatibleVersionPlugin.java b/x-pack/plugin/rest-compatibility/src/main/java/org/elasticsearch/compat/CompatibleVersionPlugin.java index 108899d6d1575..4f0c2426018cb 100644 --- a/x-pack/plugin/rest-compatibility/src/main/java/org/elasticsearch/compat/CompatibleVersionPlugin.java +++ b/x-pack/plugin/rest-compatibility/src/main/java/org/elasticsearch/compat/CompatibleVersionPlugin.java @@ -18,10 +18,12 @@ public class CompatibleVersionPlugin extends Plugin implements RestCompatibilityPlugin, MediaTypeRegistryPlugin { - private MediaTypeParser mediaTypeParser; +// private MediaTypeParser mediaTypeParser; @Override - public Version getCompatibleVersion(@Nullable String acceptHeader, @Nullable String contentTypeHeader, boolean hasContent) { + public Version getCompatibleVersion(@Nullable String acceptHeader, @Nullable String contentTypeHeader, boolean hasContent, + MediaTypeRegistry mediaTypeRegistry) { + MediaTypeParser mediaTypeParser = new MediaTypeParser<>(mediaTypeRegistry); Byte aVersion = mediaTypeParser.parseVersion(acceptHeader); byte acceptVersion = aVersion == null ? Version.CURRENT.major : Integer.valueOf(aVersion).byteValue(); Byte cVersion = mediaTypeParser.parseVersion(contentTypeHeader); @@ -80,6 +82,6 @@ public Version getCompatibleVersion(@Nullable String acceptHeader, @Nullable Str @Override public void setGlobalMediaTypeRegistry(MediaTypeRegistry globalMediaTypeRegistry) { - this.mediaTypeParser = new MediaTypeParser<>(globalMediaTypeRegistry); +// this.mediaTypeParser = new MediaTypeParser<>(globalMediaTypeRegistry); } } diff --git a/x-pack/plugin/rest-compatibility/src/test/java/org/elasticsearch/compat/CompatibleVersionPluginTests.java b/x-pack/plugin/rest-compatibility/src/test/java/org/elasticsearch/compat/CompatibleVersionPluginTests.java index 32f792ffc2a92..334a13dd62647 100644 --- a/x-pack/plugin/rest-compatibility/src/test/java/org/elasticsearch/compat/CompatibleVersionPluginTests.java +++ b/x-pack/plugin/rest-compatibility/src/test/java/org/elasticsearch/compat/CompatibleVersionPluginTests.java @@ -8,6 +8,7 @@ import org.elasticsearch.ElasticsearchStatusException; import org.elasticsearch.Version; +import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.hamcrest.ElasticsearchMatchers; import org.hamcrest.Matcher; @@ -206,7 +207,8 @@ private String mediaType(String version) { } private Version requestWith(String accept, String contentType, String body) { - return compatibleVersionPlugin.getCompatibleVersion(accept, contentType, body.isEmpty() == false); + return compatibleVersionPlugin.getCompatibleVersion(accept, contentType, body.isEmpty() == false, + XContentType.getMediaTypeRegistry()); } } From 5ad227ad20ded0a92d6f1b5fa10baa12042d0bbc Mon Sep 17 00:00:00 2001 From: pgomulka Date: Thu, 15 Oct 2020 17:34:06 +0200 Subject: [PATCH 6/8] do not use global media type registry in sql --- .../java/org/elasticsearch/node/Node.java | 1 - .../plugins/MediaTypeRegistryPlugin.java | 27 ------------------- .../compat/CompatibleVersionPlugin.java | 7 +---- .../xpack/sql/plugin/RestSqlQueryAction.java | 6 ++--- .../xpack/sql/plugin/SqlMediaTypeParser.java | 10 ++++--- .../xpack/sql/plugin/SqlPlugin.java | 12 ++------- 6 files changed, 12 insertions(+), 51 deletions(-) delete mode 100644 server/src/main/java/org/elasticsearch/plugins/MediaTypeRegistryPlugin.java diff --git a/server/src/main/java/org/elasticsearch/node/Node.java b/server/src/main/java/org/elasticsearch/node/Node.java index dd605ff9ef366..4cf13f117d62c 100644 --- a/server/src/main/java/org/elasticsearch/node/Node.java +++ b/server/src/main/java/org/elasticsearch/node/Node.java @@ -728,7 +728,6 @@ protected Node(final Environment initialEnvironment, /** * @return A function that can be used to determine the requested REST compatible version * package scope for testing - * @param globalMediaTypeRegistry */ CompatibleVersion getRestCompatibleFunction(MediaTypeRegistry globalMediaTypeRegistry) { List restCompatibilityPlugins = pluginsService.filterPlugins(RestCompatibilityPlugin.class); diff --git a/server/src/main/java/org/elasticsearch/plugins/MediaTypeRegistryPlugin.java b/server/src/main/java/org/elasticsearch/plugins/MediaTypeRegistryPlugin.java deleted file mode 100644 index 62b907375ea23..0000000000000 --- a/server/src/main/java/org/elasticsearch/plugins/MediaTypeRegistryPlugin.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Licensed to Elasticsearch under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.elasticsearch.plugins; - -import org.elasticsearch.common.xcontent.MediaTypeRegistry; - -public interface MediaTypeRegistryPlugin { - void setGlobalMediaTypeRegistry(MediaTypeRegistry globalMediaTypeRegistry); - -} diff --git a/x-pack/plugin/rest-compatibility/src/main/java/org/elasticsearch/compat/CompatibleVersionPlugin.java b/x-pack/plugin/rest-compatibility/src/main/java/org/elasticsearch/compat/CompatibleVersionPlugin.java index 4f0c2426018cb..c8a34db445f1e 100644 --- a/x-pack/plugin/rest-compatibility/src/main/java/org/elasticsearch/compat/CompatibleVersionPlugin.java +++ b/x-pack/plugin/rest-compatibility/src/main/java/org/elasticsearch/compat/CompatibleVersionPlugin.java @@ -11,12 +11,11 @@ import org.elasticsearch.common.xcontent.MediaType; import org.elasticsearch.common.xcontent.MediaTypeParser; import org.elasticsearch.common.xcontent.MediaTypeRegistry; -import org.elasticsearch.plugins.MediaTypeRegistryPlugin; import org.elasticsearch.plugins.Plugin; import org.elasticsearch.plugins.RestCompatibilityPlugin; import org.elasticsearch.rest.RestStatus; -public class CompatibleVersionPlugin extends Plugin implements RestCompatibilityPlugin, MediaTypeRegistryPlugin { +public class CompatibleVersionPlugin extends Plugin implements RestCompatibilityPlugin { // private MediaTypeParser mediaTypeParser; @@ -80,8 +79,4 @@ public Version getCompatibleVersion(@Nullable String acceptHeader, @Nullable Str return Version.CURRENT; } - @Override - public void setGlobalMediaTypeRegistry(MediaTypeRegistry globalMediaTypeRegistry) { -// this.mediaTypeParser = new MediaTypeParser<>(globalMediaTypeRegistry); - } } diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/RestSqlQueryAction.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/RestSqlQueryAction.java index 64495ed816eb0..431651e7b1412 100644 --- a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/RestSqlQueryAction.java +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/RestSqlQueryAction.java @@ -37,10 +37,10 @@ public class RestSqlQueryAction extends BaseRestHandler { MediaType responseMediaType; - private final SqlMediaTypeParser sqlMediaTypeParser; + private final SqlMediaTypeParser sqlMediaTypeParser ; - public RestSqlQueryAction(MediaTypeRegistry globalMediaTypeRegistry) { - this.sqlMediaTypeParser = new SqlMediaTypeParser(globalMediaTypeRegistry); + public RestSqlQueryAction(MediaTypeRegistry additionalMediaTypes) { + sqlMediaTypeParser = new SqlMediaTypeParser(additionalMediaTypes); } @Override diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/SqlMediaTypeParser.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/SqlMediaTypeParser.java index 30e28fb4a35bb..7f4fc14d8f353 100644 --- a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/SqlMediaTypeParser.java +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/SqlMediaTypeParser.java @@ -18,11 +18,13 @@ import static org.elasticsearch.xpack.sql.proto.Protocol.URL_PARAM_FORMAT; public class SqlMediaTypeParser { - private MediaTypeParser mediaTypeParser; + private MediaTypeParser mediaTypeParser ; - public SqlMediaTypeParser(MediaTypeRegistry globalMediaTypeRegistry) { - //sql media types are already registered via plugin api - this.mediaTypeParser = new MediaTypeParser<>(globalMediaTypeRegistry); + public SqlMediaTypeParser(MediaTypeRegistry additionalMediaTypes) { + MediaTypeRegistry register = new MediaTypeRegistry() + .register(XContentType.getMediaTypeRegistry()) + .register(additionalMediaTypes); + mediaTypeParser = new MediaTypeParser<>(register); } diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/SqlPlugin.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/SqlPlugin.java index f06d368843f23..24b6973fd298e 100644 --- a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/SqlPlugin.java +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/SqlPlugin.java @@ -24,7 +24,6 @@ import org.elasticsearch.license.LicenseUtils; import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.plugins.ActionPlugin; -import org.elasticsearch.plugins.MediaTypeRegistryPlugin; import org.elasticsearch.plugins.Plugin; import org.elasticsearch.repositories.RepositoriesService; import org.elasticsearch.rest.RestController; @@ -45,14 +44,13 @@ import org.elasticsearch.xpack.sql.execution.PlanExecutor; import org.elasticsearch.xpack.sql.type.SqlDataTypeRegistry; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.function.Supplier; -public class SqlPlugin extends Plugin implements ActionPlugin, MediaTypeRegistryPlugin { +public class SqlPlugin extends Plugin implements ActionPlugin { private final SqlLicenseChecker sqlLicenseChecker = new SqlLicenseChecker( (mode) -> { @@ -79,7 +77,6 @@ public class SqlPlugin extends Plugin implements ActionPlugin, MediaTypeRegistry } } ); - private MediaTypeRegistry globalMediaTypeRegistry; public SqlPlugin(Settings settings) { } @@ -112,7 +109,7 @@ public List getRestHandlers(Settings settings, RestController restC SettingsFilter settingsFilter, IndexNameExpressionResolver indexNameExpressionResolver, Supplier nodesInCluster) { - return Arrays.asList(new RestSqlQueryAction(globalMediaTypeRegistry), + return Arrays.asList(new RestSqlQueryAction(getAdditionalMediaTypes()), new RestSqlTranslateAction(), new RestSqlClearCursorAction(), new RestSqlStatsAction()); @@ -167,9 +164,4 @@ public MediaTypeRegistry getAdditionalMediaTypes() { return mediaTypeRegistry; } - - @Override - public void setGlobalMediaTypeRegistry(MediaTypeRegistry globalMediaTypeRegistry) { - this.globalMediaTypeRegistry = globalMediaTypeRegistry; - } } From b3f9f673bd278e4cd8178cc38f8baf3083386c63 Mon Sep 17 00:00:00 2001 From: jaymode Date: Fri, 16 Oct 2020 13:45:41 -0600 Subject: [PATCH 7/8] hacking my way to parsing values early on --- .../common/xcontent/MediaTypeDefinition.java | 65 +++++++++++ .../common/xcontent/MediaTypeParser.java | 12 +- .../common/xcontent/MediaTypeRegistry.java | 81 +++++--------- .../common/xcontent/XContentType.java | 50 ++++----- .../common/xcontent/MediaTypeParserTests.java | 8 +- .../netty4/Netty4HttpServerTransport.java | 5 +- .../elasticsearch/transport/Netty4Plugin.java | 14 ++- .../http/netty4/Netty4BadRequestTests.java | 5 +- .../Netty4HttpServerPipeliningTests.java | 6 +- .../Netty4HttpServerTransportTests.java | 21 ++-- .../http/nio/NioHttpServerTransport.java | 6 +- .../transport/nio/NioTransportPlugin.java | 14 ++- .../http/nio/NioHttpServerTransportTests.java | 20 ++-- .../elasticsearch/action/ActionModule.java | 4 +- .../common/network/NetworkModule.java | 6 +- .../http/AbstractHttpServerTransport.java | 17 ++- .../java/org/elasticsearch/node/Node.java | 24 ++-- .../elasticsearch/plugins/ActionPlugin.java | 8 +- .../elasticsearch/plugins/NetworkPlugin.java | 21 ++-- .../plugins/RestCompatibilityPlugin.java | 4 +- .../elasticsearch/rest/CompatibleVersion.java | 3 +- .../elasticsearch/rest/RestController.java | 17 +-- .../org/elasticsearch/rest/RestRequest.java | 104 ++++++++++++------ .../common/network/NetworkModuleTests.java | 34 ++++-- .../AbstractHttpServerTransportTests.java | 8 +- .../http/DefaultRestChannelTests.java | 22 ++-- .../org/elasticsearch/node/NodeTests.java | 17 ++- .../rest/RestControllerTests.java | 6 +- .../elasticsearch/rest/RestRequestTests.java | 8 +- .../test/rest/FakeRestRequest.java | 5 +- .../core/LocalStateCompositeXPackPlugin.java | 15 ++- .../compat/CompatibleVersionPlugin.java | 73 ++++++++---- .../compat/CompatibleVersionPluginTests.java | 84 ++++++++++++-- .../xpack/security/Security.java | 16 ++- .../SecurityNetty4HttpServerTransport.java | 6 +- .../nio/SecurityNioHttpServerTransport.java | 5 +- ...ecurityNetty4HttpServerTransportTests.java | 27 +++-- .../SecurityNioHttpServerTransportTests.java | 19 ++-- .../xpack/sql/plugin/RestSqlQueryAction.java | 62 ++++++++--- .../xpack/sql/plugin/SqlMediaTypeParser.java | 73 ------------ .../xpack/sql/plugin/SqlPlugin.java | 51 +++------ .../sql/plugin/SqlMediaTypeParserTests.java | 100 ----------------- 42 files changed, 625 insertions(+), 521 deletions(-) create mode 100644 libs/x-content/src/main/java/org/elasticsearch/common/xcontent/MediaTypeDefinition.java delete mode 100644 x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/SqlMediaTypeParser.java delete mode 100644 x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/plugin/SqlMediaTypeParserTests.java diff --git a/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/MediaTypeDefinition.java b/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/MediaTypeDefinition.java new file mode 100644 index 0000000000000..f28329eaeb136 --- /dev/null +++ b/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/MediaTypeDefinition.java @@ -0,0 +1,65 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.common.xcontent; + +import org.elasticsearch.common.Nullable; + +import java.util.Map; +import java.util.Objects; + +public class MediaTypeDefinition { + + private final String format; + private final String mediaTypeString; + private final Map mediaTypeParameters; + private final MediaType mediaType; + + public MediaTypeDefinition(String mediaTypeString, MediaType mediaType, Map mediaTypeParameters) { + this(null, mediaTypeString, mediaType, mediaTypeParameters); + } + + public MediaTypeDefinition(MediaType mediaType, Map mediaTypeParameters) { + this(mediaType.format(), mediaType.typeWithSubtype(), mediaType, mediaTypeParameters); + } + + private MediaTypeDefinition(String format, String mediaTypeString, MediaType mediaType, Map mediaTypeParameters) { + this.format = format; + this.mediaTypeString = Objects.requireNonNull(mediaTypeString); + this.mediaType = Objects.requireNonNull(mediaType); + this.mediaTypeParameters = Objects.requireNonNull(mediaTypeParameters); + } + + @Nullable + public String format() { + return format; + } + + public String getMediaTypeString() { + return mediaTypeString; + } + + public Map getMediaTypeParameters() { + return mediaTypeParameters; + } + + public MediaType getMediaType() { + return mediaType; + } +} diff --git a/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/MediaTypeParser.java b/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/MediaTypeParser.java index feee9d10bce7f..dec6223b927b1 100644 --- a/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/MediaTypeParser.java +++ b/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/MediaTypeParser.java @@ -26,22 +26,24 @@ import java.util.regex.Pattern; public class MediaTypeParser { - private MediaTypeRegistry mediaTypeRegistry; + private final MediaTypeRegistry mediaTypeRegistry; public MediaTypeParser(MediaTypeRegistry mediaTypeRegistry) { this.mediaTypeRegistry = mediaTypeRegistry; } + @SuppressWarnings("unchecked") public T fromMediaType(String mediaType) { ParsedMediaType parsedMediaType = parseMediaType(mediaType); - return parsedMediaType != null ? (T)parsedMediaType.getMediaType() : null; + return parsedMediaType != null ? (T) parsedMediaType.getMediaType() : null; } + @SuppressWarnings("unchecked") public T fromFormat(String format) { if (format == null) { return null; } - return (T)mediaTypeRegistry.formatToMediaType(format.toLowerCase(Locale.ROOT)); + return (T) mediaTypeRegistry.formatToMediaType(format.toLowerCase(Locale.ROOT)); } /** @@ -102,7 +104,7 @@ private boolean hasSpaces(String s) { private static final String COMPATIBLE_WITH_PARAMETER_NAME = "compatible-with"; - public Byte parseVersion(String mediaType) { + public Byte parseVersion(String mediaType) { ParsedMediaType parsedMediaType = parseMediaType(mediaType); if (parsedMediaType != null) { String version = parsedMediaType @@ -116,7 +118,7 @@ public Byte parseVersion(String mediaType) { /** * A media type object that contains all the information provided on a Content-Type or Accept header */ - public class ParsedMediaType { + public static class ParsedMediaType { private final Map parameters; private final MediaType mediaType; diff --git a/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/MediaTypeRegistry.java b/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/MediaTypeRegistry.java index 913d4d677b274..c6ef7ca85b74d 100644 --- a/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/MediaTypeRegistry.java +++ b/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/MediaTypeRegistry.java @@ -19,41 +19,44 @@ package org.elasticsearch.common.xcontent; -import java.util.Collection; import java.util.HashMap; +import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; +import java.util.Map.Entry; import java.util.regex.Pattern; public class MediaTypeRegistry { - private Map formatToMediaType = new ConcurrentHashMap<>(); - private Map typeWithSubtypeToMediaType = new ConcurrentHashMap<>(); - private Map> parametersMap = new ConcurrentHashMap<>(); + private final Map formatToMediaType; + private final Map typeWithSubtypeToMediaType; + private final Map> parametersMap; - public MediaTypeRegistry register(Map formatToMediaType, Map typeWithSubtypeToMediaType, Map> parametersMap) { - this.formatToMediaType.putAll(formatToMediaType); - this.typeWithSubtypeToMediaType.putAll(typeWithSubtypeToMediaType); - this.parametersMap.putAll(parametersMap); - return this; - } + public MediaTypeRegistry(List definitions) { + Map formatToMediaType = new HashMap<>(); + Map typeWithSubtypeToMediaType = new HashMap<>(); + Map> parametersMap = new HashMap<>(); + definitions.forEach(definition -> { + if (definition.format() != null) { + formatToMediaType.put(definition.format(), definition.getMediaType()); + } + typeWithSubtypeToMediaType.put(definition.getMediaTypeString(), definition.getMediaType()); + Map uncompiledParams = definition.getMediaTypeParameters(); + Map parametersForMediaType = new HashMap<>(uncompiledParams.size()); + for (Entry params : uncompiledParams.entrySet()) { + String parameterName = params.getKey().toLowerCase(Locale.ROOT); + String parameterRegex = params.getValue(); + Pattern pattern = Pattern.compile(parameterRegex, Pattern.CASE_INSENSITIVE); + parametersForMediaType.put(parameterName, pattern); + } + parametersMap.put(definition.getMediaTypeString(), parametersForMediaType); + }); - public MediaTypeRegistry register(String typeWithSubtype, T mediaType, String format, Map parametersMap) { - if (format != null) { - this.formatToMediaType.put(format, mediaType); - } - this.typeWithSubtypeToMediaType.put(typeWithSubtype,mediaType); - Map parametersForMediaType = new HashMap<>(parametersMap.size()); - for (Map.Entry params : parametersMap.entrySet()) { - String parameterName = params.getKey().toLowerCase(Locale.ROOT); - String parameterRegex = params.getValue(); - Pattern pattern = Pattern.compile(parameterRegex, Pattern.CASE_INSENSITIVE); - parametersForMediaType.put(parameterName, pattern); - } - this.parametersMap.put(typeWithSubtype,parametersForMediaType); - return this; + this.formatToMediaType = Map.copyOf(formatToMediaType); + this.typeWithSubtypeToMediaType = Map.copyOf(typeWithSubtypeToMediaType); + this.parametersMap = Map.copyOf(parametersMap); } + public MediaType formatToMediaType(String format) { return formatToMediaType.get(format); } @@ -66,32 +69,4 @@ public Map parametersFor(String typeWithSubtype) { return parametersMap.get(typeWithSubtype); } - public MediaTypeRegistry register(String alternativeMediaType, MediaType mediaType, Map paramNameAndValueRegex) { - typeWithSubtypeToMediaType.put(alternativeMediaType.toLowerCase(Locale.ROOT), mediaType); - formatToMediaType.put(mediaType.format(), mediaType); - - Map parametersForMediaType = new HashMap<>(paramNameAndValueRegex.size()); - for (Map.Entry params : paramNameAndValueRegex.entrySet()) { - String parameterName = params.getKey().toLowerCase(Locale.ROOT); - String parameterRegex = params.getValue(); - Pattern pattern = Pattern.compile(parameterRegex, Pattern.CASE_INSENSITIVE); - parametersForMediaType.put(parameterName, pattern); - } - parametersMap.put(alternativeMediaType, parametersForMediaType); - return this; - } - - public MediaTypeRegistry register(MediaTypeRegistry xContentTypeRegistry) { - formatToMediaType.putAll(xContentTypeRegistry.formatToMediaType); - typeWithSubtypeToMediaType.putAll(xContentTypeRegistry.typeWithSubtypeToMediaType); - parametersMap.putAll(xContentTypeRegistry.parametersMap); - return this; - } - public MediaTypeRegistry register(Collection mediaTypeRegistries ) { - for (MediaTypeRegistry mediaTypeRegistry : mediaTypeRegistries) { - register(mediaTypeRegistry); - } - return this; - } - } diff --git a/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/XContentType.java b/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/XContentType.java index be8d9189f6cd4..e8f68dc44f5bf 100644 --- a/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/XContentType.java +++ b/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/XContentType.java @@ -24,7 +24,7 @@ import org.elasticsearch.common.xcontent.smile.SmileXContent; import org.elasticsearch.common.xcontent.yaml.YamlXContent; -import java.util.Collections; +import java.util.List; import java.util.Map; /** @@ -117,29 +117,23 @@ public XContent xContent() { public static final String COMPATIBLE_WITH_PARAMETER_NAME = "compatible-with"; public static final String VERSION_PATTERN = "\\d+"; - private static final MediaTypeRegistry mediaTypeRegistry = new MediaTypeRegistry() - .register("application/smile", SMILE, Collections.emptyMap()) - .register("application/cbor", CBOR, Collections.emptyMap()) - .register("application/json", JSON, Map.of("charset", "UTF-8")) - .register("application/yaml", YAML, Map.of("charset", "UTF-8")) - .register("application/*", JSON, Map.of("charset", "UTF-8")) - .register("application/x-ndjson", JSON, Map.of("charset", "UTF-8")) - .register("application/vnd.elasticsearch+json", JSON, + public static final List MEDIA_TYPE_DEFINITIONS = List.of( + new MediaTypeDefinition(SMILE, Map.of()), + new MediaTypeDefinition(CBOR, Map.of()), + new MediaTypeDefinition(JSON, Map.of("charset", "UTF-8")), + new MediaTypeDefinition(YAML, Map.of("charset", "UTF-8")), + new MediaTypeDefinition("application/x-ndjson", JSON, Map.of("charset", "UTF-8")), + new MediaTypeDefinition("application/vnd.elasticsearch+json", JSON, + Map.of(COMPATIBLE_WITH_PARAMETER_NAME, VERSION_PATTERN, "charset", "UTF-8")), + new MediaTypeDefinition("application/vnd.elasticsearch+smile", SMILE, + Map.of(COMPATIBLE_WITH_PARAMETER_NAME, VERSION_PATTERN, "charset", "UTF-8")), + new MediaTypeDefinition("application/vnd.elasticsearch+yaml", YAML, + Map.of(COMPATIBLE_WITH_PARAMETER_NAME, VERSION_PATTERN, "charset", "UTF-8")), + new MediaTypeDefinition("application/vnd.elasticsearch+cbor", CBOR, + Map.of(COMPATIBLE_WITH_PARAMETER_NAME, VERSION_PATTERN, "charset", "UTF-8")), + new MediaTypeDefinition("application/vnd.elasticsearch+x-ndjson", JSON, Map.of(COMPATIBLE_WITH_PARAMETER_NAME, VERSION_PATTERN, "charset", "UTF-8")) - .register("application/vnd.elasticsearch+smile", SMILE, - Map.of(COMPATIBLE_WITH_PARAMETER_NAME, VERSION_PATTERN, "charset", "UTF-8")) - .register("application/vnd.elasticsearch+yaml", YAML, - Map.of(COMPATIBLE_WITH_PARAMETER_NAME, VERSION_PATTERN, "charset", "UTF-8")) - .register("application/vnd.elasticsearch+cbor", CBOR, - Map.of(COMPATIBLE_WITH_PARAMETER_NAME, VERSION_PATTERN, "charset", "UTF-8")) - .register("application/vnd.elasticsearch+x-ndjson", JSON, - Map.of(COMPATIBLE_WITH_PARAMETER_NAME, VERSION_PATTERN, "charset", "UTF-8")); - - private static MediaTypeParser mediaTypeParser = new MediaTypeParser<>(mediaTypeRegistry); - - public static MediaTypeRegistry getMediaTypeRegistry() { - return mediaTypeRegistry; - } + ); /** * Accepts a format string, which is most of the time is equivalent to {@link XContentType#subtype()} @@ -148,7 +142,7 @@ public static MediaTypeRegistry getMediaTypeRegistry() { * This method will return {@code null} if no match is found */ public static XContentType fromFormat(String mediaType) { - return mediaTypeParser.fromFormat(mediaType); + return null; // TODO } /** @@ -158,23 +152,23 @@ public static XContentType fromFormat(String mediaType) { * This method will return {@code null} if no match is found */ public static XContentType fromMediaType(String mediaTypeHeaderValue) { - return mediaTypeParser.fromMediaType(mediaTypeHeaderValue); + return null; // TODO } - private int index; + private final int index; XContentType(int index) { this.index = index; } public static Byte parseVersion(String mediaType) { - MediaTypeParser.ParsedMediaType parsedMediaType = mediaTypeParser.parseMediaType(mediaType); + /*MediaTypeParser.ParsedMediaType parsedMediaType = mediaTypeParser.parseMediaType(mediaType); if (parsedMediaType != null) { String version = parsedMediaType .getParameters() .get(COMPATIBLE_WITH_PARAMETER_NAME); return version != null ? Byte.parseByte(version) : null; - } + }*/ return null; } diff --git a/libs/x-content/src/test/java/org/elasticsearch/common/xcontent/MediaTypeParserTests.java b/libs/x-content/src/test/java/org/elasticsearch/common/xcontent/MediaTypeParserTests.java index b9137adf9c2ea..9f38dfc593f7b 100644 --- a/libs/x-content/src/test/java/org/elasticsearch/common/xcontent/MediaTypeParserTests.java +++ b/libs/x-content/src/test/java/org/elasticsearch/common/xcontent/MediaTypeParserTests.java @@ -29,12 +29,8 @@ import static org.hamcrest.Matchers.nullValue; public class MediaTypeParserTests extends ESTestCase { - MediaTypeRegistry mediaTypeRegistry = new MediaTypeRegistry(); - MediaTypeParser mediaTypeParser = new MediaTypeParser.Builder() - .withMediaTypeAndParams("application/vnd.elasticsearch+json", - XContentType.JSON, Map.of("compatible-with", "\\d+", - "charset", "UTF-8")) - .build(mediaTypeRegistry); + MediaTypeRegistry mediaTypeRegistry = new MediaTypeRegistry(XContentType.MEDIA_TYPE_DEFINITIONS); + MediaTypeParser mediaTypeParser = new MediaTypeParser<>(mediaTypeRegistry); public void testJsonWithParameters() throws Exception { String mediaType = "application/vnd.elasticsearch+json"; diff --git a/modules/transport-netty4/src/main/java/org/elasticsearch/http/netty4/Netty4HttpServerTransport.java b/modules/transport-netty4/src/main/java/org/elasticsearch/http/netty4/Netty4HttpServerTransport.java index b0010a31375c8..2fc177d5361c4 100644 --- a/modules/transport-netty4/src/main/java/org/elasticsearch/http/netty4/Netty4HttpServerTransport.java +++ b/modules/transport-netty4/src/main/java/org/elasticsearch/http/netty4/Netty4HttpServerTransport.java @@ -51,6 +51,7 @@ import org.elasticsearch.common.unit.ByteSizeValue; import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.common.util.concurrent.EsExecutors; +import org.elasticsearch.common.xcontent.MediaTypeParser; import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.core.internal.io.IOUtils; import org.elasticsearch.core.internal.net.NetUtils; @@ -147,8 +148,8 @@ public class Netty4HttpServerTransport extends AbstractHttpServerTransport { public Netty4HttpServerTransport(Settings settings, NetworkService networkService, BigArrays bigArrays, ThreadPool threadPool, NamedXContentRegistry xContentRegistry, Dispatcher dispatcher, ClusterSettings clusterSettings, - SharedGroupFactory sharedGroupFactory) { - super(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher, clusterSettings); + SharedGroupFactory sharedGroupFactory, MediaTypeParser mediaTypeParser) { + super(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher, clusterSettings, mediaTypeParser); Netty4Utils.setAvailableProcessors(EsExecutors.NODE_PROCESSORS_SETTING.get(settings)); NettyAllocator.logAllocatorDescriptionIfNeeded(); this.sharedGroupFactory = sharedGroupFactory; diff --git a/modules/transport-netty4/src/main/java/org/elasticsearch/transport/Netty4Plugin.java b/modules/transport-netty4/src/main/java/org/elasticsearch/transport/Netty4Plugin.java index 1428eb7b17113..8e537582efdb1 100644 --- a/modules/transport-netty4/src/main/java/org/elasticsearch/transport/Netty4Plugin.java +++ b/modules/transport-netty4/src/main/java/org/elasticsearch/transport/Netty4Plugin.java @@ -29,8 +29,10 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.common.util.PageCacheRecycler; +import org.elasticsearch.common.xcontent.MediaTypeParser; import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.http.HttpServerTransport; +import org.elasticsearch.http.HttpServerTransport.Dispatcher; import org.elasticsearch.http.netty4.Netty4HttpServerTransport; import org.elasticsearch.indices.breaker.CircuitBreakerService; import org.elasticsearch.plugins.NetworkPlugin; @@ -84,16 +86,20 @@ public Map> getTransports(Settings settings, ThreadP } @Override - public Map> getHttpTransports(Settings settings, ThreadPool threadPool, BigArrays bigArrays, + public Map> getHttpTransports(Settings settings, + ThreadPool threadPool, + BigArrays bigArrays, PageCacheRecycler pageCacheRecycler, CircuitBreakerService circuitBreakerService, NamedXContentRegistry xContentRegistry, NetworkService networkService, - HttpServerTransport.Dispatcher dispatcher, - ClusterSettings clusterSettings) { + Dispatcher dispatcher, + ClusterSettings clusterSettings, + MediaTypeParser mediaTypeParser + ) { return Collections.singletonMap(NETTY_HTTP_TRANSPORT_NAME, () -> new Netty4HttpServerTransport(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher, - clusterSettings, getSharedGroupFactory(settings))); + clusterSettings, getSharedGroupFactory(settings), mediaTypeParser)); } private SharedGroupFactory getSharedGroupFactory(Settings settings) { diff --git a/modules/transport-netty4/src/test/java/org/elasticsearch/http/netty4/Netty4BadRequestTests.java b/modules/transport-netty4/src/test/java/org/elasticsearch/http/netty4/Netty4BadRequestTests.java index abd918f706bc7..2f0515c7fcc22 100644 --- a/modules/transport-netty4/src/test/java/org/elasticsearch/http/netty4/Netty4BadRequestTests.java +++ b/modules/transport-netty4/src/test/java/org/elasticsearch/http/netty4/Netty4BadRequestTests.java @@ -29,6 +29,9 @@ import org.elasticsearch.common.util.MockBigArrays; import org.elasticsearch.common.util.MockPageCacheRecycler; import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.common.xcontent.MediaTypeParser; +import org.elasticsearch.common.xcontent.MediaTypeRegistry; +import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.http.HttpServerTransport; import org.elasticsearch.http.HttpTransportSettings; import org.elasticsearch.indices.breaker.NoneCircuitBreakerService; @@ -91,7 +94,7 @@ public void dispatchBadRequest(RestChannel channel, ThreadContext threadContext, Settings settings = Settings.builder().put(HttpTransportSettings.SETTING_HTTP_PORT.getKey(), getPortRange()).build(); try (HttpServerTransport httpServerTransport = new Netty4HttpServerTransport(settings, networkService, bigArrays, threadPool, xContentRegistry(), dispatcher, new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS), - new SharedGroupFactory(Settings.EMPTY))) { + new SharedGroupFactory(Settings.EMPTY), new MediaTypeParser<>(new MediaTypeRegistry(XContentType.MEDIA_TYPE_DEFINITIONS)))) { httpServerTransport.start(); final TransportAddress transportAddress = randomFrom(httpServerTransport.boundAddress().boundAddresses()); diff --git a/modules/transport-netty4/src/test/java/org/elasticsearch/http/netty4/Netty4HttpServerPipeliningTests.java b/modules/transport-netty4/src/test/java/org/elasticsearch/http/netty4/Netty4HttpServerPipeliningTests.java index a873293ab5b9f..0e9d49e64fcc4 100644 --- a/modules/transport-netty4/src/test/java/org/elasticsearch/http/netty4/Netty4HttpServerPipeliningTests.java +++ b/modules/transport-netty4/src/test/java/org/elasticsearch/http/netty4/Netty4HttpServerPipeliningTests.java @@ -35,6 +35,9 @@ import org.elasticsearch.common.transport.TransportAddress; import org.elasticsearch.common.util.MockBigArrays; import org.elasticsearch.common.util.MockPageCacheRecycler; +import org.elasticsearch.common.xcontent.MediaTypeParser; +import org.elasticsearch.common.xcontent.MediaTypeRegistry; +import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.http.HttpPipelinedRequest; import org.elasticsearch.http.HttpResponse; import org.elasticsearch.http.HttpServerTransport; @@ -120,7 +123,8 @@ class CustomNettyHttpServerTransport extends Netty4HttpServerTransport { Netty4HttpServerPipeliningTests.this.bigArrays, Netty4HttpServerPipeliningTests.this.threadPool, xContentRegistry(), new NullDispatcher(), new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS), - new SharedGroupFactory(settings)); + new SharedGroupFactory(settings), + new MediaTypeParser<>(new MediaTypeRegistry(XContentType.MEDIA_TYPE_DEFINITIONS))); } @Override diff --git a/modules/transport-netty4/src/test/java/org/elasticsearch/http/netty4/Netty4HttpServerTransportTests.java b/modules/transport-netty4/src/test/java/org/elasticsearch/http/netty4/Netty4HttpServerTransportTests.java index e2ce3a6878849..b6c53a948ade0 100644 --- a/modules/transport-netty4/src/test/java/org/elasticsearch/http/netty4/Netty4HttpServerTransportTests.java +++ b/modules/transport-netty4/src/test/java/org/elasticsearch/http/netty4/Netty4HttpServerTransportTests.java @@ -57,6 +57,9 @@ import org.elasticsearch.common.util.MockBigArrays; import org.elasticsearch.common.util.MockPageCacheRecycler; import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.common.xcontent.MediaTypeParser; +import org.elasticsearch.common.xcontent.MediaTypeRegistry; +import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.http.BindHttpException; import org.elasticsearch.http.CorsHandler; import org.elasticsearch.http.HttpServerTransport; @@ -70,8 +73,8 @@ import org.elasticsearch.test.rest.FakeRestRequest; import org.elasticsearch.threadpool.TestThreadPool; import org.elasticsearch.threadpool.ThreadPool; -import org.elasticsearch.transport.SharedGroupFactory; import org.elasticsearch.transport.NettyAllocator; +import org.elasticsearch.transport.SharedGroupFactory; import org.junit.After; import org.junit.Before; @@ -101,6 +104,7 @@ public class Netty4HttpServerTransportTests extends ESTestCase { private ThreadPool threadPool; private MockBigArrays bigArrays; private ClusterSettings clusterSettings; + private MediaTypeParser mediaTypeParser; @Before public void setup() throws Exception { @@ -108,6 +112,7 @@ public void setup() throws Exception { threadPool = new TestThreadPool("test"); bigArrays = new MockBigArrays(new MockPageCacheRecycler(Settings.EMPTY), new NoneCircuitBreakerService()); clusterSettings = new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS); + mediaTypeParser = new MediaTypeParser<>(new MediaTypeRegistry(XContentType.MEDIA_TYPE_DEFINITIONS)); } @After @@ -174,7 +179,7 @@ public void dispatchBadRequest(RestChannel channel, ThreadContext threadContext, } }; try (Netty4HttpServerTransport transport = new Netty4HttpServerTransport(settings, networkService, bigArrays, threadPool, - xContentRegistry(), dispatcher, clusterSettings, new SharedGroupFactory(settings))) { + xContentRegistry(), dispatcher, clusterSettings, new SharedGroupFactory(settings), mediaTypeParser)) { transport.start(); final TransportAddress remoteAddress = randomFrom(transport.boundAddress().boundAddresses()); try (Netty4HttpClient client = new Netty4HttpClient()) { @@ -208,7 +213,7 @@ public void dispatchBadRequest(RestChannel channel, ThreadContext threadContext, public void testBindUnavailableAddress() { Settings initialSettings = createSettings(); try (Netty4HttpServerTransport transport = new Netty4HttpServerTransport(initialSettings, networkService, bigArrays, threadPool, - xContentRegistry(), new NullDispatcher(), clusterSettings, new SharedGroupFactory(Settings.EMPTY))) { + xContentRegistry(), new NullDispatcher(), clusterSettings, new SharedGroupFactory(Settings.EMPTY), mediaTypeParser)) { transport.start(); TransportAddress remoteAddress = randomFrom(transport.boundAddress().boundAddresses()); Settings settings = Settings.builder() @@ -216,7 +221,7 @@ public void testBindUnavailableAddress() { .put("network.host", remoteAddress.getAddress()) .build(); try (Netty4HttpServerTransport otherTransport = new Netty4HttpServerTransport(settings, networkService, bigArrays, threadPool, - xContentRegistry(), new NullDispatcher(), clusterSettings, new SharedGroupFactory(settings))) { + xContentRegistry(), new NullDispatcher(), clusterSettings, new SharedGroupFactory(settings), mediaTypeParser)) { BindHttpException bindHttpException = expectThrows(BindHttpException.class, otherTransport::start); assertEquals( "Failed to bind to " + NetworkAddress.format(remoteAddress.address()), @@ -262,7 +267,7 @@ public void dispatchBadRequest(final RestChannel channel, final ThreadContext th try (Netty4HttpServerTransport transport = new Netty4HttpServerTransport( settings, networkService, bigArrays, threadPool, xContentRegistry(), dispatcher, clusterSettings, - new SharedGroupFactory(settings))) { + new SharedGroupFactory(settings), mediaTypeParser)) { transport.start(); final TransportAddress remoteAddress = randomFrom(transport.boundAddress().boundAddresses()); @@ -312,7 +317,7 @@ public void dispatchBadRequest(final RestChannel channel, final ThreadContext th try (Netty4HttpServerTransport transport = new Netty4HttpServerTransport( Settings.EMPTY, networkService, bigArrays, threadPool, xContentRegistry(), dispatcher, clusterSettings, - new SharedGroupFactory(Settings.EMPTY))) { + new SharedGroupFactory(Settings.EMPTY), mediaTypeParser)) { transport.start(); final TransportAddress remoteAddress = randomFrom(transport.boundAddress().boundAddresses()); @@ -372,7 +377,7 @@ public void dispatchBadRequest(final RestChannel channel, try (Netty4HttpServerTransport transport = new Netty4HttpServerTransport(settings, networkService, bigArrays, threadPool, xContentRegistry(), dispatcher, new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS), - new SharedGroupFactory(settings))) { + new SharedGroupFactory(settings), mediaTypeParser)) { transport.start(); final TransportAddress remoteAddress = randomFrom(transport.boundAddress().boundAddresses()); @@ -435,7 +440,7 @@ public void dispatchBadRequest(final RestChannel channel, NioEventLoopGroup group = new NioEventLoopGroup(); try (Netty4HttpServerTransport transport = new Netty4HttpServerTransport(settings, networkService, bigArrays, threadPool, xContentRegistry(), dispatcher, new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS), - new SharedGroupFactory(settings))) { + new SharedGroupFactory(settings), mediaTypeParser)) { transport.start(); final TransportAddress remoteAddress = randomFrom(transport.boundAddress().boundAddresses()); diff --git a/plugins/transport-nio/src/main/java/org/elasticsearch/http/nio/NioHttpServerTransport.java b/plugins/transport-nio/src/main/java/org/elasticsearch/http/nio/NioHttpServerTransport.java index f10d9538d9e34..816d50ea3da1d 100644 --- a/plugins/transport-nio/src/main/java/org/elasticsearch/http/nio/NioHttpServerTransport.java +++ b/plugins/transport-nio/src/main/java/org/elasticsearch/http/nio/NioHttpServerTransport.java @@ -30,6 +30,7 @@ import org.elasticsearch.common.unit.ByteSizeValue; import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.common.util.PageCacheRecycler; +import org.elasticsearch.common.xcontent.MediaTypeParser; import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.http.AbstractHttpServerTransport; import org.elasticsearch.http.HttpChannel; @@ -86,8 +87,9 @@ public class NioHttpServerTransport extends AbstractHttpServerTransport { public NioHttpServerTransport(Settings settings, NetworkService networkService, BigArrays bigArrays, PageCacheRecycler pageCacheRecycler, ThreadPool threadPool, NamedXContentRegistry xContentRegistry, - Dispatcher dispatcher, NioGroupFactory nioGroupFactory, ClusterSettings clusterSettings) { - super(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher, clusterSettings); + Dispatcher dispatcher, NioGroupFactory nioGroupFactory, ClusterSettings clusterSettings, + MediaTypeParser mediaTypeParser) { + super(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher, clusterSettings, mediaTypeParser); this.pageAllocator = new PageAllocator(pageCacheRecycler); this.nioGroupFactory = nioGroupFactory; diff --git a/plugins/transport-nio/src/main/java/org/elasticsearch/transport/nio/NioTransportPlugin.java b/plugins/transport-nio/src/main/java/org/elasticsearch/transport/nio/NioTransportPlugin.java index 1da90e35ba7f4..bcf2811385b99 100644 --- a/plugins/transport-nio/src/main/java/org/elasticsearch/transport/nio/NioTransportPlugin.java +++ b/plugins/transport-nio/src/main/java/org/elasticsearch/transport/nio/NioTransportPlugin.java @@ -31,8 +31,10 @@ import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.common.util.PageCacheRecycler; import org.elasticsearch.common.util.concurrent.EsExecutors; +import org.elasticsearch.common.xcontent.MediaTypeParser; import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.http.HttpServerTransport; +import org.elasticsearch.http.HttpServerTransport.Dispatcher; import org.elasticsearch.http.nio.NioHttpServerTransport; import org.elasticsearch.indices.breaker.CircuitBreakerService; import org.elasticsearch.plugins.NetworkPlugin; @@ -82,16 +84,20 @@ public Map> getTransports(Settings settings, ThreadP } @Override - public Map> getHttpTransports(Settings settings, ThreadPool threadPool, BigArrays bigArrays, + public Map> getHttpTransports(Settings settings, + ThreadPool threadPool, + BigArrays bigArrays, PageCacheRecycler pageCacheRecycler, CircuitBreakerService circuitBreakerService, NamedXContentRegistry xContentRegistry, NetworkService networkService, - HttpServerTransport.Dispatcher dispatcher, - ClusterSettings clusterSettings) { + Dispatcher dispatcher, + ClusterSettings clusterSettings, + MediaTypeParser mediaTypeParser + ) { return Collections.singletonMap(NIO_HTTP_TRANSPORT_NAME, () -> new NioHttpServerTransport(settings, networkService, bigArrays, pageCacheRecycler, threadPool, xContentRegistry, - dispatcher, getNioGroupFactory(settings), clusterSettings)); + dispatcher, getNioGroupFactory(settings), clusterSettings, mediaTypeParser)); } private synchronized NioGroupFactory getNioGroupFactory(Settings settings) { diff --git a/plugins/transport-nio/src/test/java/org/elasticsearch/http/nio/NioHttpServerTransportTests.java b/plugins/transport-nio/src/test/java/org/elasticsearch/http/nio/NioHttpServerTransportTests.java index d76864bfb5be2..f7e61abc61157 100644 --- a/plugins/transport-nio/src/test/java/org/elasticsearch/http/nio/NioHttpServerTransportTests.java +++ b/plugins/transport-nio/src/test/java/org/elasticsearch/http/nio/NioHttpServerTransportTests.java @@ -45,6 +45,9 @@ import org.elasticsearch.common.util.MockBigArrays; import org.elasticsearch.common.util.MockPageCacheRecycler; import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.common.xcontent.MediaTypeParser; +import org.elasticsearch.common.xcontent.MediaTypeRegistry; +import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.http.BindHttpException; import org.elasticsearch.http.CorsHandler; import org.elasticsearch.http.HttpServerTransport; @@ -89,6 +92,7 @@ public class NioHttpServerTransportTests extends ESTestCase { private ThreadPool threadPool; private MockBigArrays bigArrays; private MockPageCacheRecycler pageRecycler; + private MediaTypeParser mediaTypeParser; @Before public void setup() throws Exception { @@ -96,6 +100,7 @@ public void setup() throws Exception { threadPool = new TestThreadPool("test"); pageRecycler = new MockPageCacheRecycler(Settings.EMPTY); bigArrays = new MockBigArrays(pageRecycler, new NoneCircuitBreakerService()); + mediaTypeParser = new MediaTypeParser<>(new MediaTypeRegistry(XContentType.MEDIA_TYPE_DEFINITIONS)); } @After @@ -162,7 +167,7 @@ public void dispatchBadRequest(RestChannel channel, ThreadContext threadContext, }; try (NioHttpServerTransport transport = new NioHttpServerTransport(settings, networkService, bigArrays, pageRecycler, threadPool, xContentRegistry(), dispatcher, new NioGroupFactory(settings, logger), - new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS))) { + new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS), mediaTypeParser)) { transport.start(); final TransportAddress remoteAddress = randomFrom(transport.boundAddress().boundAddresses()); try (NioHttpClient client = new NioHttpClient()) { @@ -197,7 +202,7 @@ public void testBindUnavailableAddress() { final Settings initialSettings = createSettings(); try (NioHttpServerTransport transport = new NioHttpServerTransport(initialSettings, networkService, bigArrays, pageRecycler, threadPool, xContentRegistry(), new NullDispatcher(), new NioGroupFactory(Settings.EMPTY, logger), - new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS))) { + new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS), mediaTypeParser)) { transport.start(); TransportAddress remoteAddress = randomFrom(transport.boundAddress().boundAddresses()); Settings settings = Settings.builder() @@ -206,7 +211,7 @@ threadPool, xContentRegistry(), new NullDispatcher(), new NioGroupFactory(Settin .build(); try (NioHttpServerTransport otherTransport = new NioHttpServerTransport(settings, networkService, bigArrays, pageRecycler, threadPool, xContentRegistry(), new NullDispatcher(), new NioGroupFactory(Settings.EMPTY, logger), - new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS))) { + new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS), mediaTypeParser)) { BindHttpException bindHttpException = expectThrows(BindHttpException.class, () -> otherTransport.start()); assertEquals( "Failed to bind to " + NetworkAddress.format(remoteAddress.address()), @@ -243,7 +248,7 @@ public void dispatchBadRequest(final RestChannel channel, try (NioHttpServerTransport transport = new NioHttpServerTransport(settings, networkService, bigArrays, pageRecycler, threadPool, xContentRegistry(), dispatcher, new NioGroupFactory(settings, logger), - new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS))) { + new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS), mediaTypeParser)) { transport.start(); final TransportAddress remoteAddress = randomFrom(transport.boundAddress().boundAddresses()); @@ -305,7 +310,8 @@ public void dispatchBadRequest(final RestChannel channel, final ThreadContext th try (NioHttpServerTransport transport = new NioHttpServerTransport( Settings.EMPTY, networkService, bigArrays, pageRecycler, threadPool, xContentRegistry(), dispatcher, - new NioGroupFactory(Settings.EMPTY, logger), new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS))) { + new NioGroupFactory(Settings.EMPTY, logger), new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS), + mediaTypeParser)) { transport.start(); final TransportAddress remoteAddress = randomFrom(transport.boundAddress().boundAddresses()); @@ -361,7 +367,7 @@ public void dispatchBadRequest(final RestChannel channel, final ThreadContext th try (NioHttpServerTransport transport = new NioHttpServerTransport(settings, networkService, bigArrays, pageRecycler, threadPool, xContentRegistry(), dispatcher, new NioGroupFactory(settings, logger), - new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS))) { + new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS), mediaTypeParser)) { transport.start(); final TransportAddress remoteAddress = randomFrom(transport.boundAddress().boundAddresses()); @@ -411,7 +417,7 @@ public void dispatchBadRequest(final RestChannel channel, try (NioHttpServerTransport transport = new NioHttpServerTransport(settings, networkService, bigArrays, pageRecycler, threadPool, xContentRegistry(), dispatcher, new NioGroupFactory(settings, logger), - new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS))) { + new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS), mediaTypeParser)) { transport.start(); final TransportAddress remoteAddress = randomFrom(transport.boundAddress().boundAddresses()); diff --git a/server/src/main/java/org/elasticsearch/action/ActionModule.java b/server/src/main/java/org/elasticsearch/action/ActionModule.java index 3956b29d13b78..eb06509293c94 100644 --- a/server/src/main/java/org/elasticsearch/action/ActionModule.java +++ b/server/src/main/java/org/elasticsearch/action/ActionModule.java @@ -246,6 +246,7 @@ import org.elasticsearch.common.settings.IndexScopedSettings; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.SettingsFilter; +import org.elasticsearch.common.xcontent.MediaTypeRegistry; import org.elasticsearch.gateway.TransportNodesListGatewayMetaState; import org.elasticsearch.gateway.TransportNodesListGatewayStartedShards; import org.elasticsearch.index.seqno.GlobalCheckpointSyncAction; @@ -461,7 +462,8 @@ public ActionModule(Settings settings, IndexNameExpressionResolver indexNameExpr indicesAliasesRequestRequestValidators = new RequestValidators<>( actionPlugins.stream().flatMap(p -> p.indicesAliasesRequestValidators().stream()).collect(Collectors.toList())); - restController = new RestController(headers, restWrapper, nodeClient, circuitBreakerService, usageService, compatibleVersion); + restController = + new RestController(headers, restWrapper, nodeClient, circuitBreakerService, usageService, compatibleVersion); } public Map> getActions() { diff --git a/server/src/main/java/org/elasticsearch/common/network/NetworkModule.java b/server/src/main/java/org/elasticsearch/common/network/NetworkModule.java index 870d63ed5e57c..2fe581996291f 100644 --- a/server/src/main/java/org/elasticsearch/common/network/NetworkModule.java +++ b/server/src/main/java/org/elasticsearch/common/network/NetworkModule.java @@ -36,6 +36,7 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.common.util.PageCacheRecycler; +import org.elasticsearch.common.xcontent.MediaTypeParser; import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.http.HttpServerTransport; @@ -114,11 +115,12 @@ public NetworkModule(Settings settings, List plugins, ThreadPool NamedWriteableRegistry namedWriteableRegistry, NamedXContentRegistry xContentRegistry, NetworkService networkService, HttpServerTransport.Dispatcher dispatcher, - ClusterSettings clusterSettings) { + ClusterSettings clusterSettings, MediaTypeParser mediaTypeParser) { this.settings = settings; for (NetworkPlugin plugin : plugins) { Map> httpTransportFactory = plugin.getHttpTransports(settings, threadPool, bigArrays, - pageCacheRecycler, circuitBreakerService, xContentRegistry, networkService, dispatcher, clusterSettings); + pageCacheRecycler, circuitBreakerService, xContentRegistry, networkService, dispatcher, clusterSettings, mediaTypeParser + ); for (Map.Entry> entry : httpTransportFactory.entrySet()) { registerHttpTransport(entry.getKey(), entry.getValue()); } diff --git a/server/src/main/java/org/elasticsearch/http/AbstractHttpServerTransport.java b/server/src/main/java/org/elasticsearch/http/AbstractHttpServerTransport.java index af8095d6dece1..0dc952b18dbc1 100644 --- a/server/src/main/java/org/elasticsearch/http/AbstractHttpServerTransport.java +++ b/server/src/main/java/org/elasticsearch/http/AbstractHttpServerTransport.java @@ -40,6 +40,7 @@ import org.elasticsearch.common.unit.ByteSizeValue; import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.common.xcontent.MediaTypeParser; import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.rest.RestChannel; import org.elasticsearch.rest.RestRequest; @@ -77,6 +78,7 @@ public abstract class AbstractHttpServerTransport extends AbstractLifecycleCompo protected final Dispatcher dispatcher; protected final CorsHandler corsHandler; private final NamedXContentRegistry xContentRegistry; + private final MediaTypeParser mediaTypeParser; protected final PortsRange port; protected final ByteSizeValue maxContentLength; @@ -91,12 +93,14 @@ public abstract class AbstractHttpServerTransport extends AbstractLifecycleCompo private final HttpTracer tracer; protected AbstractHttpServerTransport(Settings settings, NetworkService networkService, BigArrays bigArrays, ThreadPool threadPool, - NamedXContentRegistry xContentRegistry, Dispatcher dispatcher, ClusterSettings clusterSettings) { + NamedXContentRegistry xContentRegistry, Dispatcher dispatcher, ClusterSettings clusterSettings, + MediaTypeParser mediaTypeParser) { this.settings = settings; this.networkService = networkService; this.bigArrays = bigArrays; this.threadPool = threadPool; this.xContentRegistry = xContentRegistry; + this.mediaTypeParser = mediaTypeParser; this.dispatcher = dispatcher; this.handlingSettings = HttpHandlingSettings.fromSettings(settings); this.corsHandler = CorsHandler.fromSettings(settings); @@ -344,13 +348,13 @@ private void handleIncomingRequest(final HttpRequest httpRequest, final HttpChan { RestRequest innerRestRequest; try { - innerRestRequest = RestRequest.request(xContentRegistry, httpRequest, httpChannel); + innerRestRequest = RestRequest.request(xContentRegistry, httpRequest, httpChannel, mediaTypeParser); } catch (final RestRequest.ContentTypeHeaderException e) { badRequestCause = ExceptionsHelper.useOrSuppress(badRequestCause, e); innerRestRequest = requestWithoutContentTypeHeader(httpRequest, httpChannel, badRequestCause); } catch (final RestRequest.BadParameterException e) { badRequestCause = ExceptionsHelper.useOrSuppress(badRequestCause, e); - innerRestRequest = RestRequest.requestWithoutParameters(xContentRegistry, httpRequest, httpChannel); + innerRestRequest = RestRequest.requestWithoutParameters(xContentRegistry, httpRequest, httpChannel, mediaTypeParser); } restRequest = innerRestRequest; } @@ -373,7 +377,8 @@ private void handleIncomingRequest(final HttpRequest httpRequest, final HttpChan trace); } catch (final IllegalArgumentException e) { badRequestCause = ExceptionsHelper.useOrSuppress(badRequestCause, e); - final RestRequest innerRequest = RestRequest.requestWithoutParameters(xContentRegistry, httpRequest, httpChannel); + final RestRequest innerRequest = + RestRequest.requestWithoutParameters(xContentRegistry, httpRequest, httpChannel, mediaTypeParser); innerChannel = new DefaultRestChannel(httpChannel, httpRequest, innerRequest, bigArrays, handlingSettings, threadContext, corsHandler, trace); @@ -387,10 +392,10 @@ private void handleIncomingRequest(final HttpRequest httpRequest, final HttpChan private RestRequest requestWithoutContentTypeHeader(HttpRequest httpRequest, HttpChannel httpChannel, Exception badRequestCause) { HttpRequest httpRequestWithoutContentType = httpRequest.removeHeader("Content-Type"); try { - return RestRequest.request(xContentRegistry, httpRequestWithoutContentType, httpChannel); + return RestRequest.request(xContentRegistry, httpRequestWithoutContentType, httpChannel, mediaTypeParser); } catch (final RestRequest.BadParameterException e) { badRequestCause.addSuppressed(e); - return RestRequest.requestWithoutParameters(xContentRegistry, httpRequestWithoutContentType, httpChannel); + return RestRequest.requestWithoutParameters(xContentRegistry, httpRequestWithoutContentType, httpChannel, mediaTypeParser); } } diff --git a/server/src/main/java/org/elasticsearch/node/Node.java b/server/src/main/java/org/elasticsearch/node/Node.java index 4cf13f117d62c..cfa7637609a32 100644 --- a/server/src/main/java/org/elasticsearch/node/Node.java +++ b/server/src/main/java/org/elasticsearch/node/Node.java @@ -87,6 +87,8 @@ import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.common.util.PageCacheRecycler; +import org.elasticsearch.common.xcontent.MediaTypeDefinition; +import org.elasticsearch.common.xcontent.MediaTypeParser; import org.elasticsearch.common.xcontent.MediaTypeRegistry; import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.common.xcontent.XContentType; @@ -532,20 +534,15 @@ protected Node(final Environment initialEnvironment, repositoriesServiceReference::get).stream()) .collect(Collectors.toList()); - Collection mediaTypesFromPlugins = pluginsService.filterPlugins(ActionPlugin.class) + List mediaTypeDefinitions = pluginsService.filterPlugins(ActionPlugin.class) .stream() - .map(ActionPlugin::getAdditionalMediaTypes) + .flatMap(plugin -> plugin.getAdditionalMediaTypes().stream()) .collect(toList()); + mediaTypeDefinitions.addAll(XContentType.MEDIA_TYPE_DEFINITIONS); - MediaTypeRegistry globalMediaTypeRegistry = new MediaTypeRegistry() - .register(mediaTypesFromPlugins) - .register(XContentType.getMediaTypeRegistry()); + MediaTypeRegistry globalMediaTypeRegistry = new MediaTypeRegistry(mediaTypeDefinitions); - // passes down to SQL and CompatibleVersion plugins -// pluginsService.filterPlugins(MediaTypeRegistryPlugin.class) -// .forEach(plugin -> plugin.setGlobalMediaTypeRegistry(globalMediaTypeRegistry)); - - CompatibleVersion restCompatibleFunction = getRestCompatibleFunction(globalMediaTypeRegistry); + CompatibleVersion restCompatibleFunction = getRestCompatibleFunction(); ActionModule actionModule = new ActionModule(settings, clusterModule.getIndexNameExpressionResolver(), settingsModule.getIndexScopedSettings(), settingsModule.getClusterSettings(), settingsModule.getSettingsFilter(), @@ -556,7 +553,7 @@ protected Node(final Environment initialEnvironment, final RestController restController = actionModule.getRestController(); final NetworkModule networkModule = new NetworkModule(settings, pluginsService.filterPlugins(NetworkPlugin.class), threadPool, bigArrays, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, xContentRegistry, - networkService, restController, clusterService.getClusterSettings()); + networkService, restController, clusterService.getClusterSettings(), new MediaTypeParser<>(globalMediaTypeRegistry)); Collection>> indexTemplateMetadataUpgraders = pluginsService.filterPlugins(Plugin.class).stream() .map(Plugin::getIndexTemplateMetadataUpgrader) @@ -729,14 +726,13 @@ protected Node(final Environment initialEnvironment, * @return A function that can be used to determine the requested REST compatible version * package scope for testing */ - CompatibleVersion getRestCompatibleFunction(MediaTypeRegistry globalMediaTypeRegistry) { + CompatibleVersion getRestCompatibleFunction() { List restCompatibilityPlugins = pluginsService.filterPlugins(RestCompatibilityPlugin.class); final CompatibleVersion compatibleVersion; if (restCompatibilityPlugins.size() > 1) { throw new IllegalStateException("Only one RestCompatibilityPlugin is allowed"); } else if (restCompatibilityPlugins.size() == 1) { - compatibleVersion = (acceptHeader, contentTypeHeader, hasContent) -> - restCompatibilityPlugins.get(0).getCompatibleVersion(acceptHeader, contentTypeHeader, hasContent, globalMediaTypeRegistry); + compatibleVersion = restCompatibilityPlugins.get(0)::getCompatibleVersion; } else { compatibleVersion = CompatibleVersion.CURRENT_VERSION; } diff --git a/server/src/main/java/org/elasticsearch/plugins/ActionPlugin.java b/server/src/main/java/org/elasticsearch/plugins/ActionPlugin.java index 266a0e7550be7..c7e37ba8c4c6d 100644 --- a/server/src/main/java/org/elasticsearch/plugins/ActionPlugin.java +++ b/server/src/main/java/org/elasticsearch/plugins/ActionPlugin.java @@ -19,9 +19,9 @@ package org.elasticsearch.plugins; -import org.elasticsearch.action.ActionType; import org.elasticsearch.action.ActionRequest; import org.elasticsearch.action.ActionResponse; +import org.elasticsearch.action.ActionType; import org.elasticsearch.action.RequestValidators; import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest; import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest; @@ -34,7 +34,7 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.SettingsFilter; import org.elasticsearch.common.util.concurrent.ThreadContext; -import org.elasticsearch.common.xcontent.MediaTypeRegistry; +import org.elasticsearch.common.xcontent.MediaTypeDefinition; import org.elasticsearch.rest.RestController; import org.elasticsearch.rest.RestHandler; import org.elasticsearch.rest.RestHeaderDefinition; @@ -182,7 +182,7 @@ default Collection> in return Collections.emptyList(); } - default MediaTypeRegistry getAdditionalMediaTypes(){ - return new MediaTypeRegistry(); + default List getAdditionalMediaTypes() { + return List.of(); } } diff --git a/server/src/main/java/org/elasticsearch/plugins/NetworkPlugin.java b/server/src/main/java/org/elasticsearch/plugins/NetworkPlugin.java index a7c9e7bd842b7..d5e907e1a0288 100644 --- a/server/src/main/java/org/elasticsearch/plugins/NetworkPlugin.java +++ b/server/src/main/java/org/elasticsearch/plugins/NetworkPlugin.java @@ -18,11 +18,6 @@ */ package org.elasticsearch.plugins; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.function.Supplier; - import org.elasticsearch.common.io.stream.NamedWriteableRegistry; import org.elasticsearch.common.network.NetworkService; import org.elasticsearch.common.settings.ClusterSettings; @@ -30,13 +25,20 @@ import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.common.util.PageCacheRecycler; import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.common.xcontent.MediaTypeParser; import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.http.HttpServerTransport; +import org.elasticsearch.http.HttpServerTransport.Dispatcher; import org.elasticsearch.indices.breaker.CircuitBreakerService; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.Transport; import org.elasticsearch.transport.TransportInterceptor; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; + /** * Plugin for extending network and transport related classes */ @@ -69,13 +71,16 @@ default Map> getTransports(Settings settings, Thread * Returns a map of {@link HttpServerTransport} suppliers. * See {@link org.elasticsearch.common.network.NetworkModule#HTTP_TYPE_SETTING} to configure a specific implementation. */ - default Map> getHttpTransports(Settings settings, ThreadPool threadPool, BigArrays bigArrays, + default Map> getHttpTransports(Settings settings, + ThreadPool threadPool, + BigArrays bigArrays, PageCacheRecycler pageCacheRecycler, CircuitBreakerService circuitBreakerService, NamedXContentRegistry xContentRegistry, NetworkService networkService, - HttpServerTransport.Dispatcher dispatcher, - ClusterSettings clusterSettings) { + Dispatcher dispatcher, + ClusterSettings clusterSettings, + MediaTypeParser mediaTypeParser) { return Collections.emptyMap(); } } diff --git a/server/src/main/java/org/elasticsearch/plugins/RestCompatibilityPlugin.java b/server/src/main/java/org/elasticsearch/plugins/RestCompatibilityPlugin.java index cea394036d6e2..c5e4fcbe60059 100644 --- a/server/src/main/java/org/elasticsearch/plugins/RestCompatibilityPlugin.java +++ b/server/src/main/java/org/elasticsearch/plugins/RestCompatibilityPlugin.java @@ -21,7 +21,7 @@ import org.elasticsearch.Version; import org.elasticsearch.common.Nullable; -import org.elasticsearch.common.xcontent.MediaTypeRegistry; +import org.elasticsearch.common.xcontent.MediaTypeParser.ParsedMediaType; /** @@ -36,6 +36,6 @@ public interface RestCompatibilityPlugin { * @param hasContent - a flag indicating if a request has content * @return a requested Compatible API Version */ - Version getCompatibleVersion(@Nullable String acceptHeader, @Nullable String contentTypeHeader, boolean hasContent, MediaTypeRegistry mediaTypeRegistry); + Version getCompatibleVersion(@Nullable ParsedMediaType acceptHeader, @Nullable ParsedMediaType contentTypeHeader, boolean hasContent); } diff --git a/server/src/main/java/org/elasticsearch/rest/CompatibleVersion.java b/server/src/main/java/org/elasticsearch/rest/CompatibleVersion.java index 48ef5a8f8a87e..1f7461ea65f5b 100644 --- a/server/src/main/java/org/elasticsearch/rest/CompatibleVersion.java +++ b/server/src/main/java/org/elasticsearch/rest/CompatibleVersion.java @@ -21,6 +21,7 @@ import org.elasticsearch.Version; import org.elasticsearch.common.Nullable; +import org.elasticsearch.common.xcontent.MediaTypeParser.ParsedMediaType; /** * An interface used to specify a function that returns a compatible API version @@ -28,7 +29,7 @@ */ @FunctionalInterface public interface CompatibleVersion { - Version get(@Nullable String acceptHeader, @Nullable String contentTypeHeader, boolean hasContent); + Version get(@Nullable ParsedMediaType acceptMediaType, @Nullable ParsedMediaType contentTypeHeader, boolean hasContent); CompatibleVersion CURRENT_VERSION = (acceptHeader, contentTypeHeader, hasContent) -> Version.CURRENT; } diff --git a/server/src/main/java/org/elasticsearch/rest/RestController.java b/server/src/main/java/org/elasticsearch/rest/RestController.java index 5f404100939eb..0484c9089633d 100644 --- a/server/src/main/java/org/elasticsearch/rest/RestController.java +++ b/server/src/main/java/org/elasticsearch/rest/RestController.java @@ -34,6 +34,7 @@ import org.elasticsearch.common.logging.DeprecationLogger; import org.elasticsearch.common.path.PathTrie; import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.common.xcontent.MediaTypeParser.ParsedMediaType; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.core.internal.io.Streams; @@ -91,7 +92,7 @@ public class RestController implements HttpServerTransport.Dispatcher { /** Rest headers that are copied to internal requests made during a rest request. */ private final Set headersToCopy; private final UsageService usageService; - private CompatibleVersion compatibleVersion; + private final CompatibleVersion compatibleVersion; public RestController(Set headersToCopy, UnaryOperator handlerWrapper, @@ -229,14 +230,16 @@ private void dispatchRequest(RestRequest request, RestChannel channel, RestHandl throws Exception { final int contentLength = request.contentLength(); if (contentLength > 0) { - final XContentType xContentType = request.getXContentType(); - if (xContentType == null) { + final ParsedMediaType parsedMediaType = request.getContentType(); + if (parsedMediaType == null) { sendContentTypeErrorMessage(request.getAllHeaderValues("Content-Type"), channel); return; } - if (handler.supportsContentStream() && xContentType != XContentType.JSON && xContentType != XContentType.SMILE) { + if (handler.supportsContentStream() && parsedMediaType.getMediaType() != XContentType.JSON && + parsedMediaType.getMediaType() != XContentType.SMILE) { channel.sendResponse(BytesRestResponse.createSimpleErrorResponse(channel, RestStatus.NOT_ACCEPTABLE, - "Content-Type [" + xContentType + "] does not support stream parsing. Use JSON or SMILE instead")); + "Content-Type [" + parsedMediaType.getMediaType().typeWithSubtype() + "] does not support stream parsing. " + + "Use JSON or SMILE instead")); return; } } @@ -324,8 +327,8 @@ private void tryAllHandlers(final RestRequest request, final RestChannel channel final String rawPath = request.rawPath(); final String uri = request.uri(); final RestRequest.Method requestMethod; - //TODO: USAGE_1 now that we have a version we can implement a REST handler that accepts path, method AND version - Version version = compatibleVersion.get(request.header("Accept"), request.header("Content-Type"), request.hasContent()); + + Version version = compatibleVersion.get(request.getContentType(), request.getContentType(), request.hasContent()); try { // Resolves the HTTP method and fails if the method is invalid diff --git a/server/src/main/java/org/elasticsearch/rest/RestRequest.java b/server/src/main/java/org/elasticsearch/rest/RestRequest.java index 83f1a89798531..1197e5d5f4e44 100644 --- a/server/src/main/java/org/elasticsearch/rest/RestRequest.java +++ b/server/src/main/java/org/elasticsearch/rest/RestRequest.java @@ -19,7 +19,6 @@ package org.elasticsearch.rest; -import org.apache.lucene.util.SetOnce; import org.elasticsearch.ElasticsearchParseException; import org.elasticsearch.common.Booleans; import org.elasticsearch.common.CheckedConsumer; @@ -31,6 +30,9 @@ import org.elasticsearch.common.unit.ByteSizeValue; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.xcontent.LoggingDeprecationHandler; +import org.elasticsearch.common.xcontent.MediaType; +import org.elasticsearch.common.xcontent.MediaTypeParser; +import org.elasticsearch.common.xcontent.MediaTypeParser.ParsedMediaType; import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentParser; @@ -48,7 +50,6 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicLong; -import java.util.regex.Pattern; import java.util.stream.Collectors; import static org.elasticsearch.common.unit.ByteSizeValue.parseBytesSizeValue; @@ -56,9 +57,6 @@ public class RestRequest implements ToXContent.Params { - // tchar pattern as defined by RFC7230 section 3.2.6 - private static final Pattern TCHAR_PATTERN = Pattern.compile("[a-zA-z0-9!#$%&'*+\\-.\\^_`|~]+"); - private static final AtomicLong requestIdGenerator = new AtomicLong(); private final NamedXContentRegistry xContentRegistry; @@ -66,8 +64,14 @@ public class RestRequest implements ToXContent.Params { private final Map> headers; private final String rawPath; private final Set consumedParams = new HashSet<>(); - private final SetOnce xContentType = new SetOnce<>(); private final HttpChannel httpChannel; + private final MediaTypeParser mediaTypeParser; + @Nullable + private final ParsedMediaType contentType; + @Nullable + private final ParsedMediaType acceptMediaType; + @Nullable + private final MediaType formatMediaType; private HttpRequest httpRequest; @@ -81,27 +85,21 @@ public boolean isContentConsumed() { // for testing protected RestRequest(NamedXContentRegistry xContentRegistry, Map params, String path, - Map> headers, HttpRequest httpRequest, HttpChannel httpChannel) { - this(xContentRegistry, params, path, headers, httpRequest, httpChannel, requestIdGenerator.incrementAndGet()); + Map> headers, HttpRequest httpRequest, HttpChannel httpChannel, + MediaTypeParser mediaTypeParser) { + this(xContentRegistry, params, path, headers, httpRequest, httpChannel, mediaTypeParser, requestIdGenerator.incrementAndGet()); } protected RestRequest(RestRequest restRequest) { this(restRequest.getXContentRegistry(), restRequest.params(), restRequest.path(), restRequest.getHeaders(), - restRequest.getHttpRequest(), restRequest.getHttpChannel(), restRequest.getRequestId()); + restRequest.getHttpRequest(), restRequest.getHttpChannel(), restRequest.mediaTypeParser, restRequest.getRequestId()); } private RestRequest(NamedXContentRegistry xContentRegistry, Map params, String path, - Map> headers, HttpRequest httpRequest, HttpChannel httpChannel, long requestId) { - final XContentType xContentType; - try { - xContentType = parseContentType(headers.get("Content-Type")); - } catch (final IllegalArgumentException e) { - throw new ContentTypeHeaderException(e); - } - if (xContentType != null) { - this.xContentType.set(xContentType); - } + Map> headers, HttpRequest httpRequest, HttpChannel httpChannel, + MediaTypeParser mediaTypeParser, long requestId) { + this.mediaTypeParser = mediaTypeParser; this.xContentRegistry = xContentRegistry; this.httpRequest = httpRequest; this.httpChannel = httpChannel; @@ -109,6 +107,9 @@ private RestRequest(NamedXContentRegistry xContentRegistry, Map this.rawPath = path; this.headers = Collections.unmodifiableMap(headers); this.requestId = requestId; + this.acceptMediaType = mediaTypeParser.parseMediaType(header("Accept")); + this.contentType = mediaTypeParser.parseMediaType(header("Content-Type")); + this.formatMediaType = mediaTypeParser.fromFormat(param("format")); } @@ -132,10 +133,11 @@ void ensureSafeBuffers() { * @throws BadParameterException if the parameters can not be decoded * @throws ContentTypeHeaderException if the Content-Type header can not be parsed */ - public static RestRequest request(NamedXContentRegistry xContentRegistry, HttpRequest httpRequest, HttpChannel httpChannel) { + public static RestRequest request(NamedXContentRegistry xContentRegistry, HttpRequest httpRequest, HttpChannel httpChannel, + MediaTypeParser mediaTypeParser) { Map params = params(httpRequest.uri()); String path = path(httpRequest.uri()); - return new RestRequest(xContentRegistry, params, path, httpRequest.getHeaders(), httpRequest, httpChannel, + return new RestRequest(xContentRegistry, params, path, httpRequest.getHeaders(), httpRequest, httpChannel, mediaTypeParser, requestIdGenerator.incrementAndGet()); } @@ -171,10 +173,10 @@ private static String path(final String uri) { * @throws ContentTypeHeaderException if the Content-Type header can not be parsed */ public static RestRequest requestWithoutParameters(NamedXContentRegistry xContentRegistry, HttpRequest httpRequest, - HttpChannel httpChannel) { + HttpChannel httpChannel, MediaTypeParser mediaTypeParser) { Map params = Collections.emptyMap(); return new RestRequest(xContentRegistry, params, httpRequest.uri(), httpRequest.getHeaders(), httpRequest, httpChannel, - requestIdGenerator.incrementAndGet()); + mediaTypeParser, requestIdGenerator.incrementAndGet()); } public enum Method { @@ -231,7 +233,7 @@ public BytesReference content() { public final BytesReference requiredContent() { if (hasContent() == false) { throw new ElasticsearchParseException("request body is required"); - } else if (xContentType.get() == null) { + } else if (contentType == null) { throw new IllegalStateException("unknown content type"); } return content(); @@ -277,7 +279,29 @@ public final long getRequestId() { */ @Nullable public final XContentType getXContentType() { - return xContentType.get(); + if (contentType != null) { + if (contentType.getMediaType() instanceof XContentType) { + return (XContentType) contentType.getMediaType(); + } else { + throw new IllegalStateException("content type [" + contentType.getMediaType().typeWithSubtype() + "] is not xcontent"); + } + } + return null; + } + + @Nullable + public final ParsedMediaType getContentType() { + return contentType; + } + + @Nullable + public final ParsedMediaType getAcceptMediaType() { + return acceptMediaType; + } + + @Nullable + public final MediaType getFormatMediaType() { + return formatMediaType; } public HttpChannel getHttpChannel() { @@ -426,7 +450,12 @@ public NamedXContentRegistry getXContentRegistry() { */ public final XContentParser contentParser() throws IOException { BytesReference content = requiredContent(); // will throw exception if body or content type missing - return xContentType.get().xContent().createParser(xContentRegistry, LoggingDeprecationHandler.INSTANCE, content.streamInput()); + if (contentType.getMediaType() instanceof XContentType) { + return ((XContentType) contentType.getMediaType()).xContent() + .createParser(xContentRegistry, LoggingDeprecationHandler.INSTANCE, content.streamInput()); + } else { + throw new IllegalStateException("content type [" + contentType.getMediaType().typeWithSubtype() + "] is not xcontent"); + } } /** @@ -486,7 +515,11 @@ public final Tuple contentOrSourceParam() { if (hasContentOrSourceParam() == false) { throw new ElasticsearchParseException("request body or source parameter is required"); } else if (hasContent()) { - return new Tuple<>(xContentType.get(), requiredContent()); + if (contentType.getMediaType() instanceof XContentType) { + return new Tuple<>((XContentType) contentType.getMediaType(), requiredContent()); + } else { + throw new IllegalStateException("content type [" + contentType.getMediaType().typeWithSubtype() + "] is not xcontent"); + } } String source = param("source"); String typeParam = param("source_content_type"); @@ -505,7 +538,7 @@ public final Tuple contentOrSourceParam() { * Parses the given content type string for the media type. This method currently ignores parameters. */ // TODO stop ignoring parameters such as charset... - public static XContentType parseContentType(List header) { + public XContentType parseContentType(List header) { if (header == null || header.isEmpty()) { return null; } else if (header.size() > 1) { @@ -513,17 +546,16 @@ public static XContentType parseContentType(List header) { } String rawContentType = header.get(0); - final String[] elements = rawContentType.split("[ \t]*;"); - if (elements.length > 0) { - final String[] splitMediaType = elements[0].split("/"); - if (splitMediaType.length == 2 && TCHAR_PATTERN.matcher(splitMediaType[0]).matches() - && TCHAR_PATTERN.matcher(splitMediaType[1].trim()).matches()) { - return XContentType.fromMediaType(elements[0]); + ParsedMediaType parsedMediaType = mediaTypeParser.parseMediaType(rawContentType); + if (parsedMediaType != null) { + if (parsedMediaType.getMediaType() instanceof XContentType) { + return (XContentType) parsedMediaType.getMediaType(); } else { - throw new IllegalArgumentException("invalid Content-Type header [" + rawContentType + "]"); + throw new IllegalArgumentException("content type [" + contentType.getMediaType().typeWithSubtype() + "] is not xcontent"); } + } else { + throw new IllegalArgumentException("unsupported Content-Type header [" + rawContentType + "]"); } - throw new IllegalArgumentException("empty Content-Type header"); } public static class ContentTypeHeaderException extends RuntimeException { diff --git a/server/src/test/java/org/elasticsearch/common/network/NetworkModuleTests.java b/server/src/test/java/org/elasticsearch/common/network/NetworkModuleTests.java index 43fd669f71da3..abb4582f2deab 100644 --- a/server/src/test/java/org/elasticsearch/common/network/NetworkModuleTests.java +++ b/server/src/test/java/org/elasticsearch/common/network/NetworkModuleTests.java @@ -27,9 +27,13 @@ import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.common.util.PageCacheRecycler; import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.common.xcontent.MediaTypeParser; +import org.elasticsearch.common.xcontent.MediaTypeRegistry; import org.elasticsearch.common.xcontent.NamedXContentRegistry; +import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.http.HttpInfo; import org.elasticsearch.http.HttpServerTransport; +import org.elasticsearch.http.HttpServerTransport.Dispatcher; import org.elasticsearch.http.HttpStats; import org.elasticsearch.http.NullDispatcher; import org.elasticsearch.indices.breaker.CircuitBreakerService; @@ -112,14 +116,17 @@ public void testRegisterHttpTransport() { NetworkModule module = newNetworkModule(settings, new NetworkPlugin() { @Override - public Map> getHttpTransports(Settings settings, ThreadPool threadPool, + public Map> getHttpTransports(Settings settings, + ThreadPool threadPool, BigArrays bigArrays, PageCacheRecycler pageCacheRecycler, CircuitBreakerService circuitBreakerService, NamedXContentRegistry xContentRegistry, NetworkService networkService, - HttpServerTransport.Dispatcher requestDispatcher, - ClusterSettings clusterSettings) { + Dispatcher requestDispatcher, + ClusterSettings clusterSettings, + MediaTypeParser mediaTypeParser + ) { return Collections.singletonMap("custom", custom); } }); @@ -150,14 +157,17 @@ public Map> getTransports(Settings settings, ThreadP } @Override - public Map> getHttpTransports(Settings settings, ThreadPool threadPool, + public Map> getHttpTransports(Settings settings, + ThreadPool threadPool, BigArrays bigArrays, PageCacheRecycler pageCacheRecycler, CircuitBreakerService circuitBreakerService, NamedXContentRegistry xContentRegistry, NetworkService networkService, - HttpServerTransport.Dispatcher requestDispatcher, - ClusterSettings clusterSettings) { + Dispatcher requestDispatcher, + ClusterSettings clusterSettings, + MediaTypeParser mediaTypeParser + ) { Map> supplierMap = new HashMap<>(); supplierMap.put("custom", custom); supplierMap.put("default_custom", def); @@ -186,14 +196,17 @@ public Map> getTransports(Settings settings, ThreadP } @Override - public Map> getHttpTransports(Settings settings, ThreadPool threadPool, + public Map> getHttpTransports(Settings settings, + ThreadPool threadPool, BigArrays bigArrays, PageCacheRecycler pageCacheRecycler, CircuitBreakerService circuitBreakerService, NamedXContentRegistry xContentRegistry, NetworkService networkService, - HttpServerTransport.Dispatcher requestDispatcher, - ClusterSettings clusterSettings) { + Dispatcher requestDispatcher, + ClusterSettings clusterSettings, + MediaTypeParser mediaTypeParser + ) { Map> supplierMap = new HashMap<>(); supplierMap.put("custom", custom); supplierMap.put("default_custom", def); @@ -259,6 +272,7 @@ public List getTransportInterceptors(NamedWriteableRegistr private NetworkModule newNetworkModule(Settings settings, NetworkPlugin... plugins) { return new NetworkModule(settings, Arrays.asList(plugins), threadPool, null, null, null, null, xContentRegistry(), null, new NullDispatcher(), - new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS)); + new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS), + new MediaTypeParser<>(new MediaTypeRegistry(XContentType.MEDIA_TYPE_DEFINITIONS))); } } diff --git a/server/src/test/java/org/elasticsearch/http/AbstractHttpServerTransportTests.java b/server/src/test/java/org/elasticsearch/http/AbstractHttpServerTransportTests.java index 348e8a83af731..2ec7a39d514f5 100644 --- a/server/src/test/java/org/elasticsearch/http/AbstractHttpServerTransportTests.java +++ b/server/src/test/java/org/elasticsearch/http/AbstractHttpServerTransportTests.java @@ -33,7 +33,10 @@ import org.elasticsearch.common.util.MockBigArrays; import org.elasticsearch.common.util.MockPageCacheRecycler; import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.common.xcontent.MediaTypeParser; +import org.elasticsearch.common.xcontent.MediaTypeRegistry; import org.elasticsearch.common.xcontent.NamedXContentRegistry; +import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.indices.breaker.NoneCircuitBreakerService; import org.elasticsearch.rest.RestChannel; import org.elasticsearch.rest.RestRequest; @@ -140,7 +143,8 @@ public void dispatchBadRequest(final RestChannel channel, try (AbstractHttpServerTransport transport = new AbstractHttpServerTransport(Settings.EMPTY, networkService, bigArrays, threadPool, xContentRegistry(), dispatcher, - new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS)) { + new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS), + new MediaTypeParser<>(new MediaTypeRegistry(XContentType.MEDIA_TYPE_DEFINITIONS))) { @Override protected HttpServerChannel bind(InetSocketAddress hostAddress) { @@ -199,7 +203,7 @@ public void dispatchRequest(RestRequest request, RestChannel channel, ThreadCont public void dispatchBadRequest(RestChannel channel, ThreadContext threadContext, Throwable cause) { channel.sendResponse(emptyResponse(RestStatus.BAD_REQUEST)); } - }, clusterSettings) { + }, clusterSettings, new MediaTypeParser<>(new MediaTypeRegistry(XContentType.MEDIA_TYPE_DEFINITIONS))) { @Override protected HttpServerChannel bind(InetSocketAddress hostAddress) { return null; diff --git a/server/src/test/java/org/elasticsearch/http/DefaultRestChannelTests.java b/server/src/test/java/org/elasticsearch/http/DefaultRestChannelTests.java index d633c2211b6e2..f9a43c97187bb 100644 --- a/server/src/test/java/org/elasticsearch/http/DefaultRestChannelTests.java +++ b/server/src/test/java/org/elasticsearch/http/DefaultRestChannelTests.java @@ -31,7 +31,10 @@ import org.elasticsearch.common.util.ByteArray; import org.elasticsearch.common.util.MockBigArrays; import org.elasticsearch.common.util.MockPageCacheRecycler; +import org.elasticsearch.common.xcontent.MediaTypeParser; +import org.elasticsearch.common.xcontent.MediaTypeRegistry; import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.common.xcontent.json.JsonXContent; import org.elasticsearch.indices.breaker.NoneCircuitBreakerService; import org.elasticsearch.rest.BytesRestResponse; @@ -148,7 +151,8 @@ public void testHeadersSet() { Settings settings = Settings.builder().build(); final TestHttpRequest httpRequest = new TestHttpRequest(HttpRequest.HttpVersion.HTTP_1_1, RestRequest.Method.GET, "/"); httpRequest.getHeaders().put(Task.X_OPAQUE_ID, Collections.singletonList("abc")); - final RestRequest request = RestRequest.request(xContentRegistry(), httpRequest, httpChannel); + final RestRequest request = RestRequest.request(xContentRegistry(), httpRequest, httpChannel, + new MediaTypeParser<>(new MediaTypeRegistry(XContentType.MEDIA_TYPE_DEFINITIONS))); HttpHandlingSettings handlingSettings = HttpHandlingSettings.fromSettings(settings); // send a response @@ -176,7 +180,8 @@ public void testCookiesSet() { Settings settings = Settings.builder().put(HttpTransportSettings.SETTING_HTTP_RESET_COOKIES.getKey(), true).build(); final TestHttpRequest httpRequest = new TestHttpRequest(HttpRequest.HttpVersion.HTTP_1_1, RestRequest.Method.GET, "/"); httpRequest.getHeaders().put(Task.X_OPAQUE_ID, Collections.singletonList("abc")); - final RestRequest request = RestRequest.request(xContentRegistry(), httpRequest, httpChannel); + final RestRequest request = RestRequest.request(xContentRegistry(), httpRequest, httpChannel, + new MediaTypeParser<>(new MediaTypeRegistry(XContentType.MEDIA_TYPE_DEFINITIONS))); HttpHandlingSettings handlingSettings = HttpHandlingSettings.fromSettings(settings); // send a response @@ -197,7 +202,8 @@ public void testCookiesSet() { public void testReleaseInListener() throws IOException { final Settings settings = Settings.builder().build(); final TestHttpRequest httpRequest = new TestHttpRequest(HttpRequest.HttpVersion.HTTP_1_1, RestRequest.Method.GET, "/"); - final RestRequest request = RestRequest.request(xContentRegistry(), httpRequest, httpChannel); + final RestRequest request = RestRequest.request(xContentRegistry(), httpRequest, httpChannel, + new MediaTypeParser<>(new MediaTypeRegistry(XContentType.MEDIA_TYPE_DEFINITIONS))); HttpHandlingSettings handlingSettings = HttpHandlingSettings.fromSettings(settings); DefaultRestChannel channel = new DefaultRestChannel(httpChannel, httpRequest, request, bigArrays, handlingSettings, @@ -251,7 +257,8 @@ public void testConnectionClose() throws Exception { httpRequest.getHeaders().put(DefaultRestChannel.CONNECTION, Collections.singletonList(DefaultRestChannel.KEEP_ALIVE)); } } - final RestRequest request = RestRequest.request(xContentRegistry(), httpRequest, httpChannel); + final RestRequest request = RestRequest.request(xContentRegistry(), httpRequest, httpChannel, + new MediaTypeParser<>(new MediaTypeRegistry(XContentType.MEDIA_TYPE_DEFINITIONS))); HttpHandlingSettings handlingSettings = HttpHandlingSettings.fromSettings(settings); @@ -283,7 +290,7 @@ public void testUnsupportedHttpMethod() { public RestRequest.Method method() { throw new IllegalArgumentException("test"); } - }, httpChannel); + }, httpChannel, new MediaTypeParser<>(new MediaTypeRegistry(XContentType.MEDIA_TYPE_DEFINITIONS))); request.getHttpRequest().getHeaders().put(DefaultRestChannel.CONNECTION, Collections.singletonList(httpConnectionHeaderValue)); DefaultRestChannel channel = new DefaultRestChannel(httpChannel, request.getHttpRequest(), request, bigArrays, @@ -321,7 +328,7 @@ public void testCloseOnException() { public HttpResponse createResponse(RestStatus status, BytesReference content) { throw new IllegalArgumentException("test"); } - }, httpChannel); + }, httpChannel, new MediaTypeParser<>(new MediaTypeRegistry(XContentType.MEDIA_TYPE_DEFINITIONS))); request.getHttpRequest().getHeaders().put(DefaultRestChannel.CONNECTION, Collections.singletonList(httpConnectionHeaderValue)); DefaultRestChannel channel = new DefaultRestChannel(httpChannel, request.getHttpRequest(), request, bigArrays, @@ -352,7 +359,8 @@ private TestHttpResponse executeRequest(final Settings settings, final String or httpRequest.getHeaders().put(CorsHandler.ORIGIN, Collections.singletonList(originValue)); } httpRequest.getHeaders().put(CorsHandler.HOST, Collections.singletonList(host)); - final RestRequest request = RestRequest.request(xContentRegistry(), httpRequest, httpChannel); + final RestRequest request = RestRequest.request(xContentRegistry(), httpRequest, httpChannel, + new MediaTypeParser<>(new MediaTypeRegistry(XContentType.MEDIA_TYPE_DEFINITIONS))); HttpHandlingSettings httpHandlingSettings = HttpHandlingSettings.fromSettings(settings); RestChannel channel = new DefaultRestChannel(httpChannel, httpRequest, request, bigArrays, httpHandlingSettings, diff --git a/server/src/test/java/org/elasticsearch/node/NodeTests.java b/server/src/test/java/org/elasticsearch/node/NodeTests.java index e6e60c6d5bdd4..a8537b39e4267 100644 --- a/server/src/test/java/org/elasticsearch/node/NodeTests.java +++ b/server/src/test/java/org/elasticsearch/node/NodeTests.java @@ -28,8 +28,7 @@ import org.elasticsearch.common.network.NetworkModule; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.transport.BoundTransportAddress; -import org.elasticsearch.common.xcontent.MediaTypeRegistry; -import org.elasticsearch.common.xcontent.XContentType; +import org.elasticsearch.common.xcontent.MediaTypeParser.ParsedMediaType; import org.elasticsearch.env.Environment; import org.elasticsearch.index.IndexService; import org.elasticsearch.index.engine.Engine.Searcher; @@ -347,16 +346,14 @@ public void setCircuitBreaker(CircuitBreaker circuitBreaker) { public static class TestRestCompatibility1 extends Plugin implements RestCompatibilityPlugin { @Override - public Version getCompatibleVersion(String acceptHeader, String contentTypeHeader, boolean hasContent, - MediaTypeRegistry mediaTypeRegistry) { + public Version getCompatibleVersion(ParsedMediaType acceptMediaType, ParsedMediaType contentType, boolean hasContent) { return Version.CURRENT.previousMajor(); } } public static class TestRestCompatibility2 extends Plugin implements RestCompatibilityPlugin { @Override - public Version getCompatibleVersion(String acceptHeader, String contentTypeHeader, boolean hasContent, - MediaTypeRegistry mediaTypeRegistry) { + public Version getCompatibleVersion(ParsedMediaType acceptMediaType, ParsedMediaType contentType, boolean hasContent) { return null; } } @@ -380,8 +377,8 @@ public void testCorrectUsageOfRestCompatibilityPlugin() throws IOException { plugins.add(TestRestCompatibility1.class); try (Node node = new MockNode(settings.build(), plugins)) { - CompatibleVersion restCompatibleFunction = node.getRestCompatibleFunction(XContentType.getMediaTypeRegistry()); - assertThat(restCompatibleFunction.get("", "", false), equalTo(Version.CURRENT.previousMajor())); + CompatibleVersion restCompatibleFunction = node.getRestCompatibleFunction(); + assertThat(restCompatibleFunction.get(null, null, false), equalTo(Version.CURRENT.previousMajor())); } } @@ -393,8 +390,8 @@ public void testDefaultingRestCompatibilityPlugin() throws IOException { List> plugins = basePlugins(); try (Node node = new MockNode(settings.build(), plugins)) { - CompatibleVersion restCompatibleFunction = node.getRestCompatibleFunction(XContentType.getMediaTypeRegistry()); - assertThat(restCompatibleFunction.get("", "", false), equalTo(Version.CURRENT)); + CompatibleVersion restCompatibleFunction = node.getRestCompatibleFunction(); + assertThat(restCompatibleFunction.get(null, null, false), equalTo(Version.CURRENT)); } } } diff --git a/server/src/test/java/org/elasticsearch/rest/RestControllerTests.java b/server/src/test/java/org/elasticsearch/rest/RestControllerTests.java index 4346600692091..10d1026590e48 100644 --- a/server/src/test/java/org/elasticsearch/rest/RestControllerTests.java +++ b/server/src/test/java/org/elasticsearch/rest/RestControllerTests.java @@ -30,6 +30,8 @@ import org.elasticsearch.common.transport.TransportAddress; import org.elasticsearch.common.unit.ByteSizeValue; import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.common.xcontent.MediaTypeParser; +import org.elasticsearch.common.xcontent.MediaTypeRegistry; import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentType; @@ -394,7 +396,7 @@ public void testDispatchWithContentStream() { String content = randomAlphaOfLength((int) Math.round(BREAKER_LIMIT.getBytes() / inFlightRequestsBreaker.getOverhead())); final List contentTypeHeader = Collections.singletonList(mimeType); FakeRestRequest fakeRestRequest = new FakeRestRequest.Builder(NamedXContentRegistry.EMPTY) - .withContent(new BytesArray(content), RestRequest.parseContentType(contentTypeHeader)).withPath("/foo") + .withContent(new BytesArray(content), mimeType.contains("json") ? XContentType.JSON : XContentType.SMILE).withPath("/foo") .withHeaders(Collections.singletonMap("Content-Type", contentTypeHeader)).build(); AssertingChannel channel = new AssertingChannel(fakeRestRequest, true, RestStatus.OK); restController.registerHandler(RestRequest.Method.GET, "/foo", new RestHandler() { @@ -616,7 +618,7 @@ public HttpRequest releaseAndCopy() { public Exception getInboundException() { return null; } - }, null); + }, null, new MediaTypeParser<>(new MediaTypeRegistry(XContentType.MEDIA_TYPE_DEFINITIONS))); final AssertingChannel channel = new AssertingChannel(request, true, RestStatus.METHOD_NOT_ALLOWED); assertFalse(channel.getSendResponseCalled()); diff --git a/server/src/test/java/org/elasticsearch/rest/RestRequestTests.java b/server/src/test/java/org/elasticsearch/rest/RestRequestTests.java index 487bbed5a5999..e435e07b036a6 100644 --- a/server/src/test/java/org/elasticsearch/rest/RestRequestTests.java +++ b/server/src/test/java/org/elasticsearch/rest/RestRequestTests.java @@ -23,6 +23,8 @@ import org.elasticsearch.common.CheckedConsumer; import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.common.xcontent.MediaTypeParser; +import org.elasticsearch.common.xcontent.MediaTypeRegistry; import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentType; @@ -94,7 +96,8 @@ private void runConsumesContentTest( when (httpRequest.getHeaders()).thenReturn( Collections.singletonMap("Content-Type", Collections.singletonList(randomFrom("application/json", "application/x-ndjson")))); final RestRequest request = - RestRequest.request(mock(NamedXContentRegistry.class), httpRequest, mock(HttpChannel.class)); + RestRequest.request(mock(NamedXContentRegistry.class), httpRequest, mock(HttpChannel.class), + new MediaTypeParser<>(new MediaTypeRegistry(XContentType.MEDIA_TYPE_DEFINITIONS))); assertFalse(request.isContentConsumed()); try { consumer.accept(request); @@ -265,7 +268,8 @@ private static final class ContentRestRequest extends RestRequest { private ContentRestRequest(RestRequest restRequest) { super(restRequest.getXContentRegistry(), restRequest.params(), restRequest.path(), restRequest.getHeaders(), - restRequest.getHttpRequest(), restRequest.getHttpChannel()); + restRequest.getHttpRequest(), restRequest.getHttpChannel(), + new MediaTypeParser<>(new MediaTypeRegistry(XContentType.MEDIA_TYPE_DEFINITIONS))); this.restRequest = restRequest; } diff --git a/test/framework/src/main/java/org/elasticsearch/test/rest/FakeRestRequest.java b/test/framework/src/main/java/org/elasticsearch/test/rest/FakeRestRequest.java index e36d4ae13b668..1d82e024b2d30 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/rest/FakeRestRequest.java +++ b/test/framework/src/main/java/org/elasticsearch/test/rest/FakeRestRequest.java @@ -22,6 +22,8 @@ import org.elasticsearch.action.ActionListener; import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.common.xcontent.MediaTypeParser; +import org.elasticsearch.common.xcontent.MediaTypeRegistry; import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.http.HttpChannel; @@ -45,7 +47,8 @@ public FakeRestRequest() { private FakeRestRequest(NamedXContentRegistry xContentRegistry, HttpRequest httpRequest, Map params, HttpChannel httpChannel) { - super(xContentRegistry, params, httpRequest.uri(), httpRequest.getHeaders(), httpRequest, httpChannel); + super(xContentRegistry, params, httpRequest.uri(), httpRequest.getHeaders(), httpRequest, httpChannel, + new MediaTypeParser<>(new MediaTypeRegistry(XContentType.MEDIA_TYPE_DEFINITIONS))); } private static class FakeHttpRequest implements HttpRequest { diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/LocalStateCompositeXPackPlugin.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/LocalStateCompositeXPackPlugin.java index 54555c2639968..1395f2c27879c 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/LocalStateCompositeXPackPlugin.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/LocalStateCompositeXPackPlugin.java @@ -34,11 +34,13 @@ import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.common.util.PageCacheRecycler; import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.common.xcontent.MediaTypeParser; import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.core.internal.io.IOUtils; import org.elasticsearch.env.Environment; import org.elasticsearch.env.NodeEnvironment; import org.elasticsearch.http.HttpServerTransport; +import org.elasticsearch.http.HttpServerTransport.Dispatcher; import org.elasticsearch.index.IndexModule; import org.elasticsearch.index.IndexSettings; import org.elasticsearch.index.analysis.TokenizerFactory; @@ -309,16 +311,21 @@ public Map> getTransports(Settings settings, ThreadP } @Override - public Map> getHttpTransports(Settings settings, ThreadPool threadPool, BigArrays bigArrays, + public Map> getHttpTransports(Settings settings, + ThreadPool threadPool, + BigArrays bigArrays, PageCacheRecycler pageCacheRecycler, CircuitBreakerService circuitBreakerService, NamedXContentRegistry xContentRegistry, NetworkService networkService, - HttpServerTransport.Dispatcher dispatcher, - ClusterSettings clusterSettings) { + Dispatcher dispatcher, + ClusterSettings clusterSettings, + MediaTypeParser mediaTypeParser + ) { Map> transports = new HashMap<>(); filterPlugins(NetworkPlugin.class).stream().forEach(p -> transports.putAll(p.getHttpTransports(settings, threadPool, bigArrays, - pageCacheRecycler, circuitBreakerService, xContentRegistry, networkService, dispatcher, clusterSettings))); + pageCacheRecycler, circuitBreakerService, xContentRegistry, networkService, dispatcher, clusterSettings, mediaTypeParser + ))); return transports; } diff --git a/x-pack/plugin/rest-compatibility/src/main/java/org/elasticsearch/compat/CompatibleVersionPlugin.java b/x-pack/plugin/rest-compatibility/src/main/java/org/elasticsearch/compat/CompatibleVersionPlugin.java index fb8942155d6dc..107ee3af12917 100644 --- a/x-pack/plugin/rest-compatibility/src/main/java/org/elasticsearch/compat/CompatibleVersionPlugin.java +++ b/x-pack/plugin/rest-compatibility/src/main/java/org/elasticsearch/compat/CompatibleVersionPlugin.java @@ -8,72 +8,97 @@ import org.elasticsearch.ElasticsearchStatusException; import org.elasticsearch.Version; import org.elasticsearch.common.Nullable; -import org.elasticsearch.common.xcontent.MediaType; -import org.elasticsearch.common.xcontent.MediaTypeParser; -import org.elasticsearch.common.xcontent.MediaTypeRegistry; +import org.elasticsearch.common.xcontent.MediaTypeParser.ParsedMediaType; import org.elasticsearch.plugins.Plugin; import org.elasticsearch.plugins.RestCompatibilityPlugin; import org.elasticsearch.rest.RestStatus; +import static org.elasticsearch.common.xcontent.XContentType.COMPATIBLE_WITH_PARAMETER_NAME; + public class CompatibleVersionPlugin extends Plugin implements RestCompatibilityPlugin { @Override - public Version getCompatibleVersion(@Nullable String acceptHeader, @Nullable String contentTypeHeader, boolean hasContent, - MediaTypeRegistry mediaTypeRegistry) { - MediaTypeParser mediaTypeParser = new MediaTypeParser<>(mediaTypeRegistry); - Byte aVersion = mediaTypeParser.parseVersion(acceptHeader); - byte acceptVersion = aVersion == null ? Version.CURRENT.major : Integer.valueOf(aVersion).byteValue(); - Byte cVersion = mediaTypeParser.parseVersion(contentTypeHeader); - byte contentTypeVersion = cVersion == null ? Version.CURRENT.major : Integer.valueOf(cVersion).byteValue(); + public Version getCompatibleVersion(@Nullable ParsedMediaType acceptMediaType, @Nullable ParsedMediaType contentTypeMediaType, + boolean hasContent) { + final VersionInfo acceptVersionInfo = majorVersionFromMediaType(acceptMediaType); + final VersionInfo contentTypeVersionInfo = majorVersionFromMediaType(contentTypeMediaType); // accept version must be current or prior - if (acceptVersion > Version.CURRENT.major || acceptVersion < Version.CURRENT.major - 1) { + if (acceptVersionInfo.version() > Version.CURRENT.major || acceptVersionInfo.version() < Version.CURRENT.major - 1) { throw new ElasticsearchStatusException( "Compatible version must be equal or less then the current version. Accept={}} Content-Type={}}", RestStatus.BAD_REQUEST, - acceptHeader, - contentTypeHeader + acceptMediaType, // TODO implement toString!!! + contentTypeMediaType ); } if (hasContent) { // content-type version must be current or prior - if (contentTypeVersion > Version.CURRENT.major || contentTypeVersion < Version.CURRENT.major - 1) { + if (contentTypeVersionInfo.version() > Version.CURRENT.major || contentTypeVersionInfo.version() < Version.CURRENT.major - 1) { throw new ElasticsearchStatusException( "Compatible version must be equal or less then the current version. Accept={} Content-Type={}", RestStatus.BAD_REQUEST, - acceptHeader, - contentTypeHeader, + acceptMediaType, + contentTypeMediaType, RestStatus.BAD_REQUEST ); } // if both accept and content-type are sent, the version must match - if (contentTypeVersion != acceptVersion) { + if (contentTypeVersionInfo.version() != acceptVersionInfo.version()) { throw new ElasticsearchStatusException( "Content-Type and Accept version requests have to match. Accept={} Content-Type={}", RestStatus.BAD_REQUEST, - acceptHeader, - contentTypeHeader + acceptMediaType, + contentTypeMediaType ); } // both headers should be versioned or none - if ((cVersion == null && aVersion != null) || (aVersion == null && cVersion != null)) { + if (contentTypeVersionInfo.isVersioned() != acceptVersionInfo.isVersioned()) { throw new ElasticsearchStatusException( "Versioning is required on both Content-Type and Accept headers. Accept={} Content-Type={}", RestStatus.BAD_REQUEST, - acceptHeader, - contentTypeHeader + acceptMediaType, + contentTypeMediaType ); } - if (contentTypeVersion < Version.CURRENT.major) { + if (contentTypeVersionInfo.version() < Version.CURRENT.major) { return Version.CURRENT.previousMajor(); } } - if (acceptVersion < Version.CURRENT.major) { + if (acceptVersionInfo.version() < Version.CURRENT.major) { return Version.CURRENT.previousMajor(); } return Version.CURRENT; } + + private static VersionInfo majorVersionFromMediaType(@Nullable ParsedMediaType parsedMediaType) { + if (parsedMediaType != null) { + String versionString = parsedMediaType.getParameters().get(COMPATIBLE_WITH_PARAMETER_NAME); + if (versionString != null && versionString.isBlank() == false) { + return new VersionInfo(true, Byte.parseByte(versionString)); + } + } + return new VersionInfo(false, Version.CURRENT.major); + } + + private static class VersionInfo { + private final boolean isVersioned; + private final byte version; + + VersionInfo(boolean isVersioned, byte version) { + this.isVersioned = isVersioned; + this.version = version; + } + + boolean isVersioned() { + return isVersioned; + } + + byte version() { + return version; + } + } } diff --git a/x-pack/plugin/rest-compatibility/src/test/java/org/elasticsearch/compat/CompatibleVersionPluginTests.java b/x-pack/plugin/rest-compatibility/src/test/java/org/elasticsearch/compat/CompatibleVersionPluginTests.java index 334a13dd62647..6d9d9cc1648e3 100644 --- a/x-pack/plugin/rest-compatibility/src/test/java/org/elasticsearch/compat/CompatibleVersionPluginTests.java +++ b/x-pack/plugin/rest-compatibility/src/test/java/org/elasticsearch/compat/CompatibleVersionPluginTests.java @@ -8,12 +8,19 @@ import org.elasticsearch.ElasticsearchStatusException; import org.elasticsearch.Version; +import org.elasticsearch.common.xcontent.MediaType; +import org.elasticsearch.common.xcontent.MediaTypeParser.ParsedMediaType; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.hamcrest.ElasticsearchMatchers; import org.hamcrest.Matcher; +import java.util.HashMap; +import java.util.Map; + import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; +import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; public class CompatibleVersionPluginTests extends ESTestCase { @@ -183,32 +190,87 @@ private String bodyPresent() { return "some body"; } - private String contentTypeHeader(int version) { + private ParsedMediaType contentTypeHeader(int version) { return mediaType(String.valueOf(version)); } - private String acceptHeader(int version) { + private ParsedMediaType acceptHeader(int version) { return mediaType(String.valueOf(version)); } - private String acceptHeader(String value) { - return value; + private ParsedMediaType acceptHeader(String value) { + return fromString(value); + } + + private ParsedMediaType contentTypeHeader(String value) { + return fromString(value); } - private String contentTypeHeader(String value) { - return value; + private ParsedMediaType fromString(String value) { + if (value == null) { + return null; + } + String[] splitParams = value.split(";"); + assertThat(splitParams.length, greaterThanOrEqualTo(1)); + String mediaType = splitParams[0]; + String[] splitMediaType = mediaType.split("/"); + assertThat(splitMediaType.length, is(2)); + String type = splitMediaType[0]; + String subtype = splitMediaType[1]; + + Map params; + if (splitParams.length > 1) { + params = new HashMap<>(); + for (int i = 1; i < splitParams.length; i++) { + String[] paramAndValue = splitParams[i].split("="); + assertThat(paramAndValue.length, is(2)); + params.put(paramAndValue[0], paramAndValue[1]); + } + } else { + params = Map.of(); + } + return new ParsedMediaType(new MediaType() { + @Override + public String type() { + return type; + } + + @Override + public String subtype() { + return subtype; + } + + @Override + public String format() { + return null; + } + }, params); } - private String mediaType(String version) { + private ParsedMediaType mediaType(String version) { if (version != null) { - return "application/vnd.elasticsearch+json;compatible-with=" + version; + return new ParsedMediaType(new MediaType() { + @Override + public String type() { + return "application"; + } + + @Override + public String subtype() { + return "vnd.elasticsearch+json"; + } + + @Override + public String format() { + return null; + } + }, Map.of(XContentType.COMPATIBLE_WITH_PARAMETER_NAME, version)); } return null; } - private Version requestWith(String accept, String contentType, String body) { - return compatibleVersionPlugin.getCompatibleVersion(accept, contentType, body.isEmpty() == false, - XContentType.getMediaTypeRegistry()); + private Version requestWith(ParsedMediaType accept, ParsedMediaType contentType, String body) { + return compatibleVersionPlugin.getCompatibleVersion(accept, contentType, body.isEmpty() == false); } } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java index 4474e401ecbaf..97e62685463d2 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java @@ -37,10 +37,12 @@ import org.elasticsearch.common.util.concurrent.EsExecutors; import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.common.util.set.Sets; +import org.elasticsearch.common.xcontent.MediaTypeParser; import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.env.Environment; import org.elasticsearch.env.NodeEnvironment; import org.elasticsearch.http.HttpServerTransport; +import org.elasticsearch.http.HttpServerTransport.Dispatcher; import org.elasticsearch.index.IndexModule; import org.elasticsearch.indices.SystemIndexDescriptor; import org.elasticsearch.indices.breaker.CircuitBreakerService; @@ -988,13 +990,17 @@ public Map> getTransports(Settings settings, ThreadP } @Override - public Map> getHttpTransports(Settings settings, ThreadPool threadPool, BigArrays bigArrays, + public Map> getHttpTransports(Settings settings, + ThreadPool threadPool, + BigArrays bigArrays, PageCacheRecycler pageCacheRecycler, CircuitBreakerService circuitBreakerService, NamedXContentRegistry xContentRegistry, NetworkService networkService, - HttpServerTransport.Dispatcher dispatcher, - ClusterSettings clusterSettings) { + Dispatcher dispatcher, + ClusterSettings clusterSettings, + MediaTypeParser mediaTypeParser + ) { if (enabled == false) { // don't register anything if we are not enabled return Collections.emptyMap(); } @@ -1002,10 +1008,10 @@ public Map> getHttpTransports(Settings set Map> httpTransports = new HashMap<>(); httpTransports.put(SecurityField.NAME4, () -> new SecurityNetty4HttpServerTransport(settings, networkService, bigArrays, ipFilter.get(), getSslService(), threadPool, xContentRegistry, dispatcher, clusterSettings, - getNettySharedGroupFactory(settings))); + getNettySharedGroupFactory(settings), mediaTypeParser)); httpTransports.put(SecurityField.NIO, () -> new SecurityNioHttpServerTransport(settings, networkService, bigArrays, pageCacheRecycler, threadPool, xContentRegistry, dispatcher, ipFilter.get(), getSslService(), getNioGroupFactory(settings), - clusterSettings)); + clusterSettings, mediaTypeParser)); return httpTransports; } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/netty4/SecurityNetty4HttpServerTransport.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/netty4/SecurityNetty4HttpServerTransport.java index e3207605658f0..03e9a3f9fd0d0 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/netty4/SecurityNetty4HttpServerTransport.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/netty4/SecurityNetty4HttpServerTransport.java @@ -14,6 +14,7 @@ import org.elasticsearch.common.settings.ClusterSettings; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.BigArrays; +import org.elasticsearch.common.xcontent.MediaTypeParser; import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.http.HttpChannel; import org.elasticsearch.http.netty4.Netty4HttpServerTransport; @@ -39,8 +40,9 @@ public class SecurityNetty4HttpServerTransport extends Netty4HttpServerTransport public SecurityNetty4HttpServerTransport(Settings settings, NetworkService networkService, BigArrays bigArrays, IPFilter ipFilter, SSLService sslService, ThreadPool threadPool, NamedXContentRegistry xContentRegistry, Dispatcher dispatcher, ClusterSettings clusterSettings, - SharedGroupFactory sharedGroupFactory) { - super(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher, clusterSettings, sharedGroupFactory); + SharedGroupFactory sharedGroupFactory, MediaTypeParser mediaTypeParser) { + super(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher, clusterSettings, sharedGroupFactory, + mediaTypeParser); this.securityExceptionHandler = new SecurityHttpExceptionHandler(logger, lifecycle, (c, e) -> super.onException(c, e)); this.ipFilter = ipFilter; final boolean ssl = HTTP_SSL_ENABLED.get(settings); diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/nio/SecurityNioHttpServerTransport.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/nio/SecurityNioHttpServerTransport.java index ff2c91da208ea..02e78054c0f39 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/nio/SecurityNioHttpServerTransport.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/nio/SecurityNioHttpServerTransport.java @@ -12,6 +12,7 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.common.util.PageCacheRecycler; +import org.elasticsearch.common.xcontent.MediaTypeParser; import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.http.nio.HttpReadWriteHandler; import org.elasticsearch.http.nio.NioHttpChannel; @@ -55,9 +56,9 @@ public SecurityNioHttpServerTransport(Settings settings, NetworkService networkS PageCacheRecycler pageCacheRecycler, ThreadPool threadPool, NamedXContentRegistry xContentRegistry, Dispatcher dispatcher, IPFilter ipFilter, SSLService sslService, NioGroupFactory nioGroupFactory, - ClusterSettings clusterSettings) { + ClusterSettings clusterSettings, MediaTypeParser mediaTypeParser) { super(settings, networkService, bigArrays, pageCacheRecycler, threadPool, xContentRegistry, dispatcher, nioGroupFactory, - clusterSettings); + clusterSettings, mediaTypeParser); this.securityExceptionHandler = new SecurityHttpExceptionHandler(logger, lifecycle, (c, e) -> super.onException(c, e)); this.ipFilter = ipFilter; this.sslEnabled = HTTP_SSL_ENABLED.get(settings); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/netty4/SecurityNetty4HttpServerTransportTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/netty4/SecurityNetty4HttpServerTransportTests.java index 951c2d5fb4238..0e4d144e6cebd 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/netty4/SecurityNetty4HttpServerTransportTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/netty4/SecurityNetty4HttpServerTransportTests.java @@ -13,6 +13,9 @@ import org.elasticsearch.common.settings.MockSecureSettings; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.BigArrays; +import org.elasticsearch.common.xcontent.MediaTypeParser; +import org.elasticsearch.common.xcontent.MediaTypeRegistry; +import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.env.Environment; import org.elasticsearch.env.TestEnvironment; import org.elasticsearch.http.NullDispatcher; @@ -42,6 +45,8 @@ public class SecurityNetty4HttpServerTransportTests extends ESTestCase { private Environment env; private Path testnodeCert; private Path testnodeKey; + private MediaTypeParser mediaTypeParser; + @Before public void createSSLService() { testnodeCert = getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.crt"); @@ -58,6 +63,7 @@ public void createSSLService() { .build(); env = TestEnvironment.newEnvironment(settings); sslService = new SSLService(env); + mediaTypeParser = new MediaTypeParser<>(new MediaTypeRegistry(XContentType.MEDIA_TYPE_DEFINITIONS)); } public void testDefaultClientAuth() throws Exception { @@ -68,7 +74,8 @@ public void testDefaultClientAuth() throws Exception { SecurityNetty4HttpServerTransport transport = new SecurityNetty4HttpServerTransport(settings, new NetworkService(Collections.emptyList()), mock(BigArrays.class), mock(IPFilter.class), sslService, mock(ThreadPool.class), xContentRegistry(), new NullDispatcher(), - new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS), new SharedGroupFactory(settings)); + new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS), new SharedGroupFactory(settings), + mediaTypeParser); ChannelHandler handler = transport.configureServerChannelHandler(); final EmbeddedChannel ch = new EmbeddedChannel(handler); assertThat(ch.pipeline().get(SslHandler.class).engine().getNeedClientAuth(), is(false)); @@ -85,7 +92,8 @@ public void testOptionalClientAuth() throws Exception { SecurityNetty4HttpServerTransport transport = new SecurityNetty4HttpServerTransport(settings, new NetworkService(Collections.emptyList()), mock(BigArrays.class), mock(IPFilter.class), sslService, mock(ThreadPool.class), xContentRegistry(), new NullDispatcher(), - new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS), new SharedGroupFactory(settings)); + new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS), new SharedGroupFactory(settings), + mediaTypeParser); ChannelHandler handler = transport.configureServerChannelHandler(); final EmbeddedChannel ch = new EmbeddedChannel(handler); assertThat(ch.pipeline().get(SslHandler.class).engine().getNeedClientAuth(), is(false)); @@ -102,7 +110,8 @@ public void testRequiredClientAuth() throws Exception { SecurityNetty4HttpServerTransport transport = new SecurityNetty4HttpServerTransport(settings, new NetworkService(Collections.emptyList()), mock(BigArrays.class), mock(IPFilter.class), sslService, mock(ThreadPool.class), xContentRegistry(), new NullDispatcher(), - new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS), new SharedGroupFactory(settings)); + new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS), new SharedGroupFactory(settings), + mediaTypeParser); ChannelHandler handler = transport.configureServerChannelHandler(); final EmbeddedChannel ch = new EmbeddedChannel(handler); assertThat(ch.pipeline().get(SslHandler.class).engine().getNeedClientAuth(), is(true)); @@ -119,7 +128,8 @@ public void testNoClientAuth() throws Exception { SecurityNetty4HttpServerTransport transport = new SecurityNetty4HttpServerTransport(settings, new NetworkService(Collections.emptyList()), mock(BigArrays.class), mock(IPFilter.class), sslService, mock(ThreadPool.class), xContentRegistry(), new NullDispatcher(), - new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS), new SharedGroupFactory(settings)); + new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS), new SharedGroupFactory(settings), + mediaTypeParser); ChannelHandler handler = transport.configureServerChannelHandler(); final EmbeddedChannel ch = new EmbeddedChannel(handler); assertThat(ch.pipeline().get(SslHandler.class).engine().getNeedClientAuth(), is(false)); @@ -134,7 +144,8 @@ public void testCustomSSLConfiguration() throws Exception { SecurityNetty4HttpServerTransport transport = new SecurityNetty4HttpServerTransport(settings, new NetworkService(Collections.emptyList()), mock(BigArrays.class), mock(IPFilter.class), sslService, mock(ThreadPool.class), xContentRegistry(), new NullDispatcher(), - new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS), new SharedGroupFactory(settings)); + new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS), new SharedGroupFactory(settings), + mediaTypeParser); ChannelHandler handler = transport.configureServerChannelHandler(); EmbeddedChannel ch = new EmbeddedChannel(handler); SSLEngine defaultEngine = ch.pipeline().get(SslHandler.class).engine(); @@ -147,7 +158,8 @@ public void testCustomSSLConfiguration() throws Exception { sslService = new SSLService(TestEnvironment.newEnvironment(settings)); transport = new SecurityNetty4HttpServerTransport(settings, new NetworkService(Collections.emptyList()), mock(BigArrays.class), mock(IPFilter.class), sslService, mock(ThreadPool.class), xContentRegistry(), new NullDispatcher(), - new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS), new SharedGroupFactory(settings)); + new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS), new SharedGroupFactory(settings), + mediaTypeParser); handler = transport.configureServerChannelHandler(); ch = new EmbeddedChannel(handler); SSLEngine customEngine = ch.pipeline().get(SslHandler.class).engine(); @@ -170,7 +182,8 @@ public void testNoExceptionWhenConfiguredWithoutSslKeySSLDisabled() throws Excep SecurityNetty4HttpServerTransport transport = new SecurityNetty4HttpServerTransport(settings, new NetworkService(Collections.emptyList()), mock(BigArrays.class), mock(IPFilter.class), sslService, mock(ThreadPool.class), xContentRegistry(), new NullDispatcher(), - new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS), new SharedGroupFactory(settings)); + new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS), new SharedGroupFactory(settings), + mediaTypeParser); assertNotNull(transport.configureServerChannelHandler()); } } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/nio/SecurityNioHttpServerTransportTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/nio/SecurityNioHttpServerTransportTests.java index 39d19578cec6f..9b028e1155e05 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/nio/SecurityNioHttpServerTransportTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/nio/SecurityNioHttpServerTransportTests.java @@ -11,6 +11,9 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.common.util.PageCacheRecycler; +import org.elasticsearch.common.xcontent.MediaTypeParser; +import org.elasticsearch.common.xcontent.MediaTypeRegistry; +import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.env.Environment; import org.elasticsearch.env.TestEnvironment; import org.elasticsearch.http.NullDispatcher; @@ -49,6 +52,7 @@ public class SecurityNioHttpServerTransportTests extends ESTestCase { private Environment env; private InetSocketAddress address = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0); private NioGroupFactory nioGroupFactory; + private MediaTypeParser mediaTypeParser; @Before public void createSSLService() { @@ -65,6 +69,7 @@ public void createSSLService() { .build(); env = TestEnvironment.newEnvironment(settings); sslService = new SSLService(env); + mediaTypeParser = new MediaTypeParser<>(new MediaTypeRegistry(XContentType.MEDIA_TYPE_DEFINITIONS)); } public void testDefaultClientAuth() throws IOException { @@ -76,7 +81,7 @@ public void testDefaultClientAuth() throws IOException { SecurityNioHttpServerTransport transport = new SecurityNioHttpServerTransport(settings, new NetworkService(Collections.emptyList()), mock(BigArrays.class), mock(PageCacheRecycler.class), mock(ThreadPool.class), xContentRegistry(), new NullDispatcher(), mock(IPFilter.class), sslService, nioGroupFactory, - new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS)); + new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS), mediaTypeParser); SecurityNioHttpServerTransport.SecurityHttpChannelFactory factory = transport.channelFactory(); SocketChannel socketChannel = mock(SocketChannel.class); when(socketChannel.getRemoteAddress()).thenReturn(address); @@ -98,7 +103,7 @@ public void testOptionalClientAuth() throws IOException { SecurityNioHttpServerTransport transport = new SecurityNioHttpServerTransport(settings, new NetworkService(Collections.emptyList()), mock(BigArrays.class), mock(PageCacheRecycler.class), mock(ThreadPool.class), xContentRegistry(), new NullDispatcher(), mock(IPFilter.class), sslService, nioGroupFactory, - new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS)); + new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS), mediaTypeParser); SecurityNioHttpServerTransport.SecurityHttpChannelFactory factory = transport.channelFactory(); SocketChannel socketChannel = mock(SocketChannel.class); @@ -120,7 +125,7 @@ public void testRequiredClientAuth() throws IOException { SecurityNioHttpServerTransport transport = new SecurityNioHttpServerTransport(settings, new NetworkService(Collections.emptyList()), mock(BigArrays.class), mock(PageCacheRecycler.class), mock(ThreadPool.class), xContentRegistry(), new NullDispatcher(), mock(IPFilter.class), sslService, nioGroupFactory, - new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS)); + new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS), mediaTypeParser); SecurityNioHttpServerTransport.SecurityHttpChannelFactory factory = transport.channelFactory(); SocketChannel socketChannel = mock(SocketChannel.class); @@ -142,7 +147,7 @@ public void testNoClientAuth() throws IOException { SecurityNioHttpServerTransport transport = new SecurityNioHttpServerTransport(settings, new NetworkService(Collections.emptyList()), mock(BigArrays.class), mock(PageCacheRecycler.class), mock(ThreadPool.class), xContentRegistry(), new NullDispatcher(), mock(IPFilter.class), sslService, nioGroupFactory, - new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS)); + new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS), mediaTypeParser); SecurityNioHttpServerTransport.SecurityHttpChannelFactory factory = transport.channelFactory(); SocketChannel socketChannel = mock(SocketChannel.class); @@ -162,7 +167,7 @@ public void testCustomSSLConfiguration() throws IOException { SecurityNioHttpServerTransport transport = new SecurityNioHttpServerTransport(settings, new NetworkService(Collections.emptyList()), mock(BigArrays.class), mock(PageCacheRecycler.class), mock(ThreadPool.class), xContentRegistry(), new NullDispatcher(), mock(IPFilter.class), sslService, nioGroupFactory, - new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS)); + new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS), mediaTypeParser); SecurityNioHttpServerTransport.SecurityHttpChannelFactory factory = transport.channelFactory(); SocketChannel socketChannel = mock(SocketChannel.class); when(socketChannel.getRemoteAddress()).thenReturn(address); @@ -179,7 +184,7 @@ public void testCustomSSLConfiguration() throws IOException { transport = new SecurityNioHttpServerTransport(settings, new NetworkService(Collections.emptyList()), mock(BigArrays.class), mock(PageCacheRecycler.class), mock(ThreadPool.class), xContentRegistry(), new NullDispatcher(), mock(IPFilter.class), sslService, nioGroupFactory, - new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS)); + new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS), mediaTypeParser); factory = transport.channelFactory(); channel = factory.createChannel(mock(NioSelector.class), socketChannel, mock(Config.Socket.class)); SSLEngine customEngine = SSLEngineUtils.getSSLEngine(channel); @@ -203,6 +208,6 @@ public void testNoExceptionWhenConfiguredWithoutSslKeySSLDisabled() { SecurityNioHttpServerTransport transport = new SecurityNioHttpServerTransport(settings, new NetworkService(Collections.emptyList()), mock(BigArrays.class), mock(PageCacheRecycler.class), mock(ThreadPool.class), xContentRegistry(), new NullDispatcher(), mock(IPFilter.class), sslService, nioGroupFactory, - new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS)); + new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS), mediaTypeParser); } } diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/RestSqlQueryAction.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/RestSqlQueryAction.java index 431651e7b1412..5cb3df91663be 100644 --- a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/RestSqlQueryAction.java +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/RestSqlQueryAction.java @@ -8,8 +8,7 @@ import org.elasticsearch.client.node.NodeClient; import org.elasticsearch.common.xcontent.MediaType; -import org.elasticsearch.common.xcontent.MediaTypeParser; -import org.elasticsearch.common.xcontent.MediaTypeRegistry; +import org.elasticsearch.common.xcontent.MediaTypeParser.ParsedMediaType; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentType; @@ -22,6 +21,7 @@ import org.elasticsearch.xpack.sql.action.SqlQueryAction; import org.elasticsearch.xpack.sql.action.SqlQueryRequest; import org.elasticsearch.xpack.sql.action.SqlQueryResponse; +import org.elasticsearch.xpack.sql.proto.Mode; import org.elasticsearch.xpack.sql.proto.Protocol; import java.io.IOException; @@ -33,15 +33,11 @@ import static org.elasticsearch.rest.RestRequest.Method.GET; import static org.elasticsearch.rest.RestRequest.Method.POST; import static org.elasticsearch.xpack.sql.proto.Protocol.URL_PARAM_DELIMITER; +import static org.elasticsearch.xpack.sql.proto.Protocol.URL_PARAM_FORMAT; public class RestSqlQueryAction extends BaseRestHandler { MediaType responseMediaType; - private final SqlMediaTypeParser sqlMediaTypeParser ; - - public RestSqlQueryAction(MediaTypeRegistry additionalMediaTypes) { - sqlMediaTypeParser = new SqlMediaTypeParser(additionalMediaTypes); - } @Override public List routes() { @@ -58,7 +54,9 @@ protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient cli sqlRequest = SqlQueryRequest.fromXContent(parser); } - responseMediaType = sqlMediaTypeParser.getMediaType(request, sqlRequest); + final MediaType localMediaType = getMediaType(request, sqlRequest); + // this is a hack and we shouldn't rely on the class instance value asynchronously + this.responseMediaType = localMediaType; long startNanos = System.nanoTime(); return channel -> client.execute(SqlQueryAction.INSTANCE, sqlRequest, new RestResponseListener(channel) { @@ -67,13 +65,13 @@ public RestResponse buildResponse(SqlQueryResponse response) throws Exception { RestResponse restResponse; // XContent branch - if (responseMediaType != null && responseMediaType instanceof XContentType) { - XContentType type = (XContentType) responseMediaType; + if (localMediaType instanceof XContentType) { + XContentType type = (XContentType) localMediaType; XContentBuilder builder = channel.newBuilder(request.getXContentType(), type, true); response.toXContent(builder, request); restResponse = new BytesRestResponse(RestStatus.OK, builder); } else { // TextFormat - TextFormat type = (TextFormat)responseMediaType; + TextFormat type = (TextFormat) localMediaType; final String data = type.format(request, response); restResponse = new BytesRestResponse(RestStatus.OK, type.contentType(request), @@ -90,16 +88,48 @@ public RestResponse buildResponse(SqlQueryResponse response) throws Exception { }); } - //we should override - - - - @Override protected Set responseParams() { return responseMediaType == TextFormat.CSV ? Collections.singleton(URL_PARAM_DELIMITER) : Collections.emptySet(); } + /* + * Since we support {@link TextFormat} and + * {@link XContent} outputs we can't use {@link RestToXContentListener} + * like everything else. We want to stick as closely as possible to + * Elasticsearch's defaults though, while still layering in ways to + * control the output more easily. + * + * First we find the string that the user used to specify the response + * format. If there is a {@code format} parameter we use that. If there + * isn't but there is a {@code Accept} header then we use that. If there + * isn't then we use the {@code Content-Type} header which is required. + */ + public MediaType getMediaType(RestRequest request, SqlQueryRequest sqlRequest) { + if (Mode.isDedicatedClient(sqlRequest.requestInfo().mode()) + && (sqlRequest.binaryCommunication() == null || sqlRequest.binaryCommunication())) { + // enforce CBOR response for drivers and CLI (unless instructed differently through the config param) + return XContentType.CBOR; + } else if (request.getFormatMediaType() != null) { + return validateColumnarRequest(sqlRequest.columnar(), request.getFormatMediaType()); + } + if (request.getAcceptMediaType() != null) { + return validateColumnarRequest(sqlRequest.columnar(), request.getAcceptMediaType().getMediaType()); + } + + ParsedMediaType contentType = request.getContentType(); + assert contentType != null : "The Content-Type header is required"; + return validateColumnarRequest(sqlRequest.columnar(), contentType.getMediaType()); + } + + private static MediaType validateColumnarRequest(boolean requestIsColumnar, MediaType fromMediaType) { + if (requestIsColumnar && fromMediaType instanceof TextFormat){ + throw new IllegalArgumentException("Invalid use of [columnar] argument: cannot be used in combination with " + + "txt, csv or tsv formats"); + } + return fromMediaType; + } + @Override public String getName() { return "sql_query"; diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/SqlMediaTypeParser.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/SqlMediaTypeParser.java deleted file mode 100644 index 7f4fc14d8f353..0000000000000 --- a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/SqlMediaTypeParser.java +++ /dev/null @@ -1,73 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -package org.elasticsearch.xpack.sql.plugin; - -import org.elasticsearch.common.xcontent.MediaType; -import org.elasticsearch.common.xcontent.MediaTypeParser; -import org.elasticsearch.common.xcontent.MediaTypeRegistry; -import org.elasticsearch.common.xcontent.XContentType; -import org.elasticsearch.rest.RestRequest; -import org.elasticsearch.xpack.sql.action.SqlQueryRequest; -import org.elasticsearch.xpack.sql.proto.Mode; - - -import static org.elasticsearch.xpack.sql.proto.Protocol.URL_PARAM_FORMAT; - -public class SqlMediaTypeParser { - private MediaTypeParser mediaTypeParser ; - - public SqlMediaTypeParser(MediaTypeRegistry additionalMediaTypes) { - MediaTypeRegistry register = new MediaTypeRegistry() - .register(XContentType.getMediaTypeRegistry()) - .register(additionalMediaTypes); - mediaTypeParser = new MediaTypeParser<>(register); - } - - - /* - * Since we support {@link TextFormat} and - * {@link XContent} outputs we can't use {@link RestToXContentListener} - * like everything else. We want to stick as closely as possible to - * Elasticsearch's defaults though, while still layering in ways to - * control the output more easily. - * - * First we find the string that the user used to specify the response - * format. If there is a {@code format} parameter we use that. If there - * isn't but there is a {@code Accept} header then we use that. If there - * isn't then we use the {@code Content-Type} header which is required. - */ - public MediaType getMediaType(RestRequest request, SqlQueryRequest sqlRequest) { - - if (Mode.isDedicatedClient(sqlRequest.requestInfo().mode()) - && (sqlRequest.binaryCommunication() == null || sqlRequest.binaryCommunication())) { - // enforce CBOR response for drivers and CLI (unless instructed differently through the config param) - return XContentType.CBOR; - } else if (request.hasParam(URL_PARAM_FORMAT)) { - return validateColumnarRequest(sqlRequest.columnar(), mediaTypeParser.fromFormat(request.param(URL_PARAM_FORMAT))); - } - if (request.getHeaders().containsKey("Accept")) { - String accept = request.header("Accept"); - // */* means "I don't care" which we should treat like not specifying the header - if ("*/*".equals(accept) == false) { - return validateColumnarRequest(sqlRequest.columnar(), mediaTypeParser.fromMediaType(accept)); - } - } - - String contentType = request.header("Content-Type"); - assert contentType != null : "The Content-Type header is required"; - return validateColumnarRequest(sqlRequest.columnar(), mediaTypeParser.fromMediaType(contentType)); - } - - private static MediaType validateColumnarRequest(boolean requestIsColumnar, MediaType fromMediaType) { - if(requestIsColumnar && fromMediaType instanceof TextFormat){ - throw new IllegalArgumentException("Invalid use of [columnar] argument: cannot be used in combination with " - + "txt, csv or tsv formats"); - } - return fromMediaType; - } - -} diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/SqlPlugin.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/SqlPlugin.java index 24b6973fd298e..ec9a6396f5c4a 100644 --- a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/SqlPlugin.java +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/SqlPlugin.java @@ -16,6 +16,7 @@ import org.elasticsearch.common.settings.IndexScopedSettings; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.SettingsFilter; +import org.elasticsearch.common.xcontent.MediaTypeDefinition; import org.elasticsearch.common.xcontent.MediaTypeRegistry; import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.common.xcontent.XContentType; @@ -50,6 +51,9 @@ import java.util.Map; import java.util.function.Supplier; +import static org.elasticsearch.common.xcontent.XContentType.COMPATIBLE_WITH_PARAMETER_NAME; +import static org.elasticsearch.common.xcontent.XContentType.VERSION_PATTERN; + public class SqlPlugin extends Plugin implements ActionPlugin { private final SqlLicenseChecker sqlLicenseChecker = new SqlLicenseChecker( @@ -109,7 +113,7 @@ public List getRestHandlers(Settings settings, RestController restC SettingsFilter settingsFilter, IndexNameExpressionResolver indexNameExpressionResolver, Supplier nodesInCluster) { - return Arrays.asList(new RestSqlQueryAction(getAdditionalMediaTypes()), + return Arrays.asList(new RestSqlQueryAction(), new RestSqlTranslateAction(), new RestSqlClearCursorAction(), new RestSqlStatsAction()); @@ -129,39 +133,18 @@ public List getRestHandlers(Settings settings, RestController restC } @Override - public MediaTypeRegistry getAdditionalMediaTypes() { - MediaTypeRegistry mediaTypeRegistry = new MediaTypeRegistry(); - mediaTypeRegistry.register(TextFormat.PLAIN_TEXT.typeWithSubtype(), - TextFormat.PLAIN_TEXT, - TextFormat.PLAIN_TEXT.format(), - Map.of("header", "present|absent", "charset", "utf-8")) - .register(TextFormat.CSV.typeWithSubtype(), - TextFormat.CSV, - TextFormat.CSV.format(), - Map.of("header", "present|absent", "charset", "utf-8", - "delimiter", ".+")) - .register(TextFormat.TSV.typeWithSubtype(), - TextFormat.TSV, - TextFormat.TSV.format(), - Map.of("header", "present|absent", "charset", "utf-8")) - - .register("text/vnd.elasticsearch+plain", - TextFormat.PLAIN_TEXT, - null, - Map.of("header", "present|absent", "charset", "utf-8", - XContentType.COMPATIBLE_WITH_PARAMETER_NAME, XContentType.VERSION_PATTERN)) - .register("text/vnd.elasticsearch+csv", - TextFormat.CSV, - null, - Map.of("header", "present|absent", "charset", "utf-8", - "delimiter", ".+", XContentType.COMPATIBLE_WITH_PARAMETER_NAME, XContentType.VERSION_PATTERN)) - .register("text/vnd.elasticsearch+tsv", - TextFormat.TSV, - null, - Map.of("header", "present|absent", "charset", "utf-8", - XContentType.COMPATIBLE_WITH_PARAMETER_NAME, XContentType.VERSION_PATTERN)); - - return mediaTypeRegistry; + public List getAdditionalMediaTypes() { + return List.of( + new MediaTypeDefinition(TextFormat.PLAIN_TEXT, Map.of("header", "present|absent", "charset", "utf-8")), + new MediaTypeDefinition(TextFormat.CSV, Map.of("header", "present|absent", "charset", "utf-8", "delimiter", ".+")), + new MediaTypeDefinition(TextFormat.TSV, Map.of("header", "present|absent", "charset", "utf-8")), + new MediaTypeDefinition("text/vnd.elasticsearch+plain", TextFormat.PLAIN_TEXT, + Map.of("header", "present|absent", "charset", "utf-8", COMPATIBLE_WITH_PARAMETER_NAME, VERSION_PATTERN)), + new MediaTypeDefinition("text/vnd.elasticsearch+csv", TextFormat.CSV, + Map.of("header", "present|absent", "charset", "utf-8", "delimiter", ".+", COMPATIBLE_WITH_PARAMETER_NAME, VERSION_PATTERN)), + new MediaTypeDefinition("text/vnd.elasticsearch+tsv", TextFormat.TSV, + Map.of("header", "present|absent", "charset", "utf-8", COMPATIBLE_WITH_PARAMETER_NAME, VERSION_PATTERN)) + ); } } diff --git a/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/plugin/SqlMediaTypeParserTests.java b/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/plugin/SqlMediaTypeParserTests.java deleted file mode 100644 index fd3e49801655f..0000000000000 --- a/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/plugin/SqlMediaTypeParserTests.java +++ /dev/null @@ -1,100 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -package org.elasticsearch.xpack.sql.plugin; - -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.common.unit.TimeValue; -import org.elasticsearch.common.xcontent.MediaType; -import org.elasticsearch.common.xcontent.MediaTypeParser; -import org.elasticsearch.common.xcontent.MediaTypeRegistry; -import org.elasticsearch.common.xcontent.NamedXContentRegistry; -import org.elasticsearch.common.xcontent.XContentType; -import org.elasticsearch.rest.RestRequest; -import org.elasticsearch.test.ESTestCase; -import org.elasticsearch.test.rest.FakeRestRequest; -import org.elasticsearch.xpack.sql.action.SqlQueryRequest; -import org.elasticsearch.xpack.sql.proto.Mode; -import org.elasticsearch.xpack.sql.proto.RequestInfo; - -import java.util.Collections; -import java.util.Map; - -import static org.elasticsearch.xpack.sql.plugin.TextFormat.CSV; -import static org.elasticsearch.xpack.sql.plugin.TextFormat.PLAIN_TEXT; -import static org.elasticsearch.xpack.sql.plugin.TextFormat.TSV; -import static org.elasticsearch.xpack.sql.proto.RequestInfo.CLIENT_IDS; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.nullValue; - -public class SqlMediaTypeParserTests extends ESTestCase { - SqlMediaTypeParser parser = new SqlMediaTypeParser( - new MediaTypeRegistry() - .register(new SqlPlugin(Settings.EMPTY).getAdditionalMediaTypes()) - .register(XContentType.getMediaTypeRegistry())); - - public void testPlainTextDetection() { - MediaType text = parser.getMediaType(reqWithAccept("text/plain"), createTestInstance(false, Mode.PLAIN, false)); - assertThat(text, is(PLAIN_TEXT)); - } - - public void testCsvDetection() { - MediaType text = parser.getMediaType(reqWithAccept("text/csv"), createTestInstance(false, Mode.PLAIN, false)); - assertThat(text, is(CSV)); - - text = parser.getMediaType(reqWithAccept("text/csv; delimiter=x"), createTestInstance(false, Mode.PLAIN, false)); - assertThat(text, is(CSV)); - } - - public void testTsvDetection() { - MediaType text = parser.getMediaType(reqWithAccept("text/tab-separated-values"), createTestInstance(false, Mode.PLAIN, false)); - assertThat(text, is(TSV)); - } - - public void testMediaTypeDetectionWithParameters() { - assertThat(parser.getMediaType(reqWithAccept("text/plain; charset=utf-8"), - createTestInstance(false, Mode.PLAIN, false)), is(PLAIN_TEXT)); - assertThat(parser.getMediaType(reqWithAccept("text/plain; header=present"), - createTestInstance(false, Mode.PLAIN, false)), is(PLAIN_TEXT)); - assertThat(parser.getMediaType(reqWithAccept("text/plain; charset=utf-8; header=present"), - createTestInstance(false, Mode.PLAIN, false)), is(PLAIN_TEXT)); - - assertThat(parser.getMediaType(reqWithAccept("text/csv; charset=utf-8"), - createTestInstance(false, Mode.PLAIN, false)), is(CSV)); - assertThat(parser.getMediaType(reqWithAccept("text/csv; header=present"), - createTestInstance(false, Mode.PLAIN, false)), is(CSV)); - assertThat(parser.getMediaType(reqWithAccept("text/csv; charset=utf-8; header=present"), - createTestInstance(false, Mode.PLAIN, false)), is(CSV)); - - assertThat(parser.getMediaType(reqWithAccept("text/tab-separated-values; charset=utf-8"), - createTestInstance(false, Mode.PLAIN, false)), is(TSV)); - assertThat(parser.getMediaType(reqWithAccept("text/tab-separated-values; header=present"), - createTestInstance(false, Mode.PLAIN, false)), is(TSV)); - assertThat(parser.getMediaType(reqWithAccept("text/tab-separated-values; charset=utf-8; header=present"), - createTestInstance(false, Mode.PLAIN, false)), is(TSV)); - } - - public void testInvalidFormat() { - MediaType mediaType = parser.getMediaType(reqWithAccept("text/garbage"), createTestInstance(false, Mode.PLAIN, false)); - assertThat(mediaType, is(nullValue())); - } - - private static RestRequest reqWithAccept(String acceptHeader) { - - return new FakeRestRequest.Builder(NamedXContentRegistry.EMPTY) - .withHeaders(Map.of("Content-Type", Collections.singletonList("application/json"), - "Accept", Collections.singletonList(acceptHeader))) - .build(); - } - - protected SqlQueryRequest createTestInstance(boolean binaryCommunication, Mode mode, boolean columnar) { - return new SqlQueryRequest(randomAlphaOfLength(10), Collections.emptyList(), null, - randomZone(), between(1, Integer.MAX_VALUE), TimeValue.parseTimeValue(randomTimeValue(), null, "test"), - TimeValue.parseTimeValue(randomTimeValue(), null, "test"), columnar, randomAlphaOfLength(10), - new RequestInfo(mode, randomFrom(randomFrom(CLIENT_IDS), randomAlphaOfLengthBetween(10, 20))), - randomBoolean(), randomBoolean()).binaryCommunication(binaryCommunication); - } -} From 1e02e9acbe525c974d88a987c5f4b608df19c039 Mon Sep 17 00:00:00 2001 From: jaymode Date: Fri, 16 Oct 2020 14:01:55 -0600 Subject: [PATCH 8/8] cleanup cleanup --- .../common/xcontent/XContentType.java | 17 +++-------- .../rest/AbstractRestChannel.java | 9 +++--- .../rest/action/cat/RestTable.java | 7 +++-- .../common/xcontent/XContentTypeTests.java | 28 ------------------- 4 files changed, 13 insertions(+), 48 deletions(-) diff --git a/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/XContentType.java b/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/XContentType.java index e8f68dc44f5bf..54f671f4ad9cd 100644 --- a/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/XContentType.java +++ b/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/XContentType.java @@ -135,6 +135,8 @@ public XContent xContent() { Map.of(COMPATIBLE_WITH_PARAMETER_NAME, VERSION_PATTERN, "charset", "UTF-8")) ); + private static final MediaTypeParser PARSER = new MediaTypeParser<>(new MediaTypeRegistry(MEDIA_TYPE_DEFINITIONS)); + /** * Accepts a format string, which is most of the time is equivalent to {@link XContentType#subtype()} * and attempts to match the value to an {@link XContentType}. @@ -142,7 +144,7 @@ public XContent xContent() { * This method will return {@code null} if no match is found */ public static XContentType fromFormat(String mediaType) { - return null; // TODO + return PARSER.fromFormat(mediaType); } /** @@ -152,7 +154,7 @@ public static XContentType fromFormat(String mediaType) { * This method will return {@code null} if no match is found */ public static XContentType fromMediaType(String mediaTypeHeaderValue) { - return null; // TODO + return PARSER.fromMediaType(mediaTypeHeaderValue); } private final int index; @@ -161,17 +163,6 @@ public static XContentType fromMediaType(String mediaTypeHeaderValue) { this.index = index; } - public static Byte parseVersion(String mediaType) { - /*MediaTypeParser.ParsedMediaType parsedMediaType = mediaTypeParser.parseMediaType(mediaType); - if (parsedMediaType != null) { - String version = parsedMediaType - .getParameters() - .get(COMPATIBLE_WITH_PARAMETER_NAME); - return version != null ? Byte.parseByte(version) : null; - }*/ - return null; - } - public int index() { return index; } diff --git a/server/src/main/java/org/elasticsearch/rest/AbstractRestChannel.java b/server/src/main/java/org/elasticsearch/rest/AbstractRestChannel.java index 6f5aa618ae4d0..8cd1ea1be29ef 100644 --- a/server/src/main/java/org/elasticsearch/rest/AbstractRestChannel.java +++ b/server/src/main/java/org/elasticsearch/rest/AbstractRestChannel.java @@ -101,11 +101,12 @@ public XContentBuilder newBuilder(@Nullable XContentType requestContentType, boo public XContentBuilder newBuilder(@Nullable XContentType requestContentType, @Nullable XContentType responseContentType, boolean useFiltering) throws IOException { if (responseContentType == null) { - if (Strings.hasText(format)) { - responseContentType = XContentType.fromFormat(format); + if (request.getFormatMediaType() != null && request.getFormatMediaType() instanceof XContentType) { + responseContentType = (XContentType) request.getFormatMediaType(); } - if (responseContentType == null) { - responseContentType = XContentType.fromMediaType(acceptHeader); + if (responseContentType == null && request.getAcceptMediaType() != null && + request.getAcceptMediaType().getMediaType() instanceof XContentType) { + responseContentType = (XContentType) request.getAcceptMediaType().getMediaType(); } } // try to determine the response content type from the media type or the format query string parameter, with the format parameter diff --git a/server/src/main/java/org/elasticsearch/rest/action/cat/RestTable.java b/server/src/main/java/org/elasticsearch/rest/action/cat/RestTable.java index bbb746baafe92..b287bd791b4f8 100644 --- a/server/src/main/java/org/elasticsearch/rest/action/cat/RestTable.java +++ b/server/src/main/java/org/elasticsearch/rest/action/cat/RestTable.java @@ -59,10 +59,11 @@ public static RestResponse buildResponse(Table table, RestChannel channel) throw } private static XContentType getXContentType(RestRequest request) { - if (request.hasParam("format")) { - return XContentType.fromFormat(request.param("format")); + if (request.getFormatMediaType() != null && request.getFormatMediaType() instanceof XContentType) { + return (XContentType) request.getFormatMediaType(); } - return XContentType.fromMediaType(request.header("Accept")); + return request.getAcceptMediaType() != null && request.getAcceptMediaType().getMediaType() instanceof XContentType ? + (XContentType) request.getAcceptMediaType().getMediaType() : null; } public static RestResponse buildXContentBuilder(Table table, RestChannel channel) throws Exception { diff --git a/server/src/test/java/org/elasticsearch/common/xcontent/XContentTypeTests.java b/server/src/test/java/org/elasticsearch/common/xcontent/XContentTypeTests.java index fa937f84d615c..6bc8ed29f15e7 100644 --- a/server/src/test/java/org/elasticsearch/common/xcontent/XContentTypeTests.java +++ b/server/src/test/java/org/elasticsearch/common/xcontent/XContentTypeTests.java @@ -23,7 +23,6 @@ import java.util.Locale; import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.nullValue; public class XContentTypeTests extends ESTestCase { @@ -118,33 +117,6 @@ public void testVersionedMediaType() { equalTo(XContentType.JSON)); } - public void testVersionParsing() { - byte version = (byte) Math.abs(randomByte()); - assertThat(XContentType.parseVersion("application/vnd.elasticsearch+json;compatible-with=" + version), - equalTo(version)); - assertThat(XContentType.parseVersion("application/vnd.elasticsearch+cbor;compatible-with=" + version), - equalTo(version)); - assertThat(XContentType.parseVersion("application/vnd.elasticsearch+smile;compatible-with=" + version), - equalTo(version)); - assertThat(XContentType.parseVersion("application/vnd.elasticsearch+x-ndjson;compatible-with=" + version), - equalTo(version)); - assertThat(XContentType.parseVersion("application/json"), - nullValue()); - - - assertThat(XContentType.parseVersion("APPLICATION/VND.ELASTICSEARCH+JSON;COMPATIBLE-WITH=" + version), - equalTo(version)); - assertThat(XContentType.parseVersion("APPLICATION/JSON"), - nullValue()); - - assertThat(XContentType.parseVersion("application/json;compatible-with=" + version + ".0"), - is(nullValue())); - } - - public void testUnrecognizedParameter() { - assertThat(XContentType.parseVersion("application/json; sth=123"), - is(nullValue())); } - public void testMediaTypeWithoutESSubtype() { String version = String.valueOf(Math.abs(randomByte())); assertThat(XContentType.fromMediaType("application/json;compatible-with=" + version), nullValue());