Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make scripting media type parsing more flexible #66857

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
27d5d30
draft
pgomulka Nov 17, 2020
6e349a9
include in tests
pgomulka Nov 17, 2020
ca9aa4d
checkstyle'
pgomulka Nov 17, 2020
d396dbf
line wrap
pgomulka Nov 17, 2020
ea066f4
guess what was the response type
pgomulka Nov 20, 2020
51ade08
take media type from provided xconttenttype
pgomulka Nov 20, 2020
a8315be
remove redudnant ifs
pgomulka Nov 20, 2020
c65c5ef
removed unused code
pgomulka Nov 24, 2020
48db876
format response content type basing on params
pgomulka Nov 25, 2020
78e3f1c
Merge branch 'master' into compat/xcontent_responsetype_format_params
pgomulka Nov 25, 2020
227e0f8
xcontent factory for vnd
pgomulka Dec 1, 2020
d94b4dc
text fixtures
pgomulka Dec 2, 2020
91fcf3a
precommit
pgomulka Dec 2, 2020
04b44ce
Merge branch 'master' into compat/xcontent_responsetype_format_params
pgomulka Dec 3, 2020
a085dc6
test fixes
pgomulka Dec 3, 2020
7aeabf7
fix serialisation
pgomulka Dec 3, 2020
d594495
fixing restcontroller
pgomulka Dec 7, 2020
0e3c446
Merge remote-tracking branch 'upstream/master' into compat/xcontent_r…
pgomulka Dec 8, 2020
8c520ce
spotless
pgomulka Dec 8, 2020
521b5ad
small cleanup
pgomulka Dec 8, 2020
261df30
tests relying on xcontent being in cannonical form - not chaning
pgomulka Dec 8, 2020
48200e2
test fix
pgomulka Dec 9, 2020
5030a57
inline read and javadoc
pgomulka Dec 10, 2020
1ce88ad
use canonical in mapxcontentparsertests
pgomulka Dec 10, 2020
f97f0c4
javadocs
pgomulka Dec 16, 2020
752968c
response media type to be a ParsedMediaType instead of just a string
pgomulka Dec 17, 2020
01ce0f4
fix tests and new test for request/response headers
pgomulka Dec 17, 2020
d3ebf1f
replace application/json;charset=utf-8
pgomulka Dec 17, 2020
e1d0463
file header and ;;
pgomulka Dec 17, 2020
e02970c
javadoc, it test in javaRestTest and scripting response about charset
pgomulka Dec 18, 2020
1f28dfd
Make scripting use media type parsing
pgomulka Dec 29, 2020
2285c25
Merge branch 'master' into scripting/parsing_content_type
pgomulka Jan 5, 2021
2b788b0
Merge branch 'master' into scripting/parsing_content_type
pgomulka Jan 5, 2021
a4fadb4
unmute
pgomulka Jan 5, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
import org.elasticsearch.common.collect.Tuple;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
* Abstracts a <a href="http://en.wikipedia.org/wiki/Internet_media_type">Media Type</a> and a query parameter <code>format</code>.
Expand All @@ -44,7 +44,7 @@ public interface MediaType {
* Returns a set of HeaderValues - allowed media type values on Accept or Content-Type headers
* Also defines media type parameters for validation.
*/
Set<HeaderValue> headerValues();
List<HeaderValue> headerValues();

/**
* A class to represent supported mediaType values i.e. application/json and parameters to be validated.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

/**
Expand Down Expand Up @@ -63,12 +62,16 @@ public Map<String, Pattern> parametersFor(String typeWithSubtype) {

public MediaTypeRegistry<T> register(T[] mediaTypes ) {
for (T mediaType : mediaTypes) {
Set<MediaType.HeaderValue> tuples = mediaType.headerValues();
for (MediaType.HeaderValue headerValue : tuples) {
queryParamToMediaType.put(mediaType.queryParameter(), mediaType);
typeWithSubtypeToMediaType.put(headerValue.v1(), mediaType);
parametersMap.put(headerValue.v1(), convertPatterns(headerValue.v2()));
}
register(mediaType);
}
return this;
}

public MediaTypeRegistry<T> register(T mediaType) {
for (MediaType.HeaderValue headerValue : mediaType.headerValues()) {
queryParamToMediaType.put(mediaType.queryParameter(), mediaType);
typeWithSubtypeToMediaType.put(headerValue.v1(), mediaType);
parametersMap.put(headerValue.v1(), convertPatterns(headerValue.v2()));
}
return this;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,8 @@ public static ParsedMediaType parseMediaType(String headerValue) {
return null;
}

public static ParsedMediaType parseMediaType(XContentType requestContentType, Map<String, String> parameters) {
ParsedMediaType parsedMediaType = parseMediaType(requestContentType.mediaTypeWithoutParameters());
public static ParsedMediaType parseMediaType(MediaType requestContentType, Map<String, String> parameters) {
ParsedMediaType parsedMediaType = parseMediaType(requestContentType.headerValues().get(0).v1());
parsedMediaType.parameters.putAll(parameters);
return parsedMediaType;
}
Expand Down Expand Up @@ -159,6 +159,10 @@ public String toString() {
return originalHeaderValue;
}

public String responseContentTypeHeader() {
return responseContentTypeHeader(this.parameters);
}

public String responseContentTypeHeader(Map<String,String> parameters) {
return this.mediaTypeWithoutParameters() + formatParameters(parameters);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,9 +208,7 @@ public XContentBuilder(XContent xContent, OutputStream os, Set<String> includes,
}

public String getResponseContentTypeString() {
Map<String, String> parameters = responseContentType != null ?
responseContentType.getParameters() : Collections.emptyMap();
return responseContentType.responseContentTypeHeader(parameters);
return responseContentType.responseContentTypeHeader();
}

public XContentType contentType() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
import org.elasticsearch.common.xcontent.smile.SmileXContent;
import org.elasticsearch.common.xcontent.yaml.YamlXContent;

import java.util.List;
import java.util.Map;
import java.util.Set;

/**
* The content type of {@link org.elasticsearch.common.xcontent.XContent}.
Expand Down Expand Up @@ -57,8 +57,8 @@ public XContent xContent() {
}

@Override
public Set<HeaderValue> headerValues() {
return Set.of(
public List<HeaderValue> headerValues() {
return List.of(
new HeaderValue("application/json"),
new HeaderValue("application/x-ndjson"),
new HeaderValue("application/*"));
Expand All @@ -84,8 +84,8 @@ public XContent xContent() {
}

@Override
public Set<HeaderValue> headerValues() {
return Set.of(
public List<HeaderValue> headerValues() {
return List.of(
new HeaderValue("application/smile"));
}
},
Expand All @@ -109,8 +109,8 @@ public XContent xContent() {
}

@Override
public Set<HeaderValue> headerValues() {
return Set.of(
public List<HeaderValue> headerValues() {
return List.of(
new HeaderValue("application/yaml"));
}
},
Expand All @@ -134,8 +134,8 @@ public XContent xContent() {
}

@Override
public Set<HeaderValue> headerValues() {
return Set.of(
public List<HeaderValue> headerValues() {
return List.of(
new HeaderValue("application/cbor"));
}
},
Expand All @@ -159,8 +159,8 @@ public XContent xContent() {
}

@Override
public Set<HeaderValue> headerValues() {
return Set.of(
public List<HeaderValue> headerValues() {
return List.of(
new HeaderValue(VENDOR_APPLICATION_PREFIX + "json",
Map.of(COMPATIBLE_WITH_PARAMETER_NAME, VERSION_PATTERN)),
new HeaderValue(VENDOR_APPLICATION_PREFIX + "x-ndjson",
Expand Down Expand Up @@ -192,8 +192,8 @@ public XContent xContent() {
}

@Override
public Set<HeaderValue> headerValues() {
return Set.of(
public List<HeaderValue> headerValues() {
return List.of(
new HeaderValue(VENDOR_APPLICATION_PREFIX + "smile",
Map.of(COMPATIBLE_WITH_PARAMETER_NAME, VERSION_PATTERN)));
}
Expand Down Expand Up @@ -223,8 +223,8 @@ public XContent xContent() {
}

@Override
public Set<HeaderValue> headerValues() {
return Set.of(
public List<HeaderValue> headerValues() {
return List.of(
new HeaderValue(VENDOR_APPLICATION_PREFIX + "yaml",
Map.of(COMPATIBLE_WITH_PARAMETER_NAME, VERSION_PATTERN)));
}
Expand Down Expand Up @@ -254,8 +254,8 @@ public XContent xContent() {
}

@Override
public Set<HeaderValue> headerValues() {
return Set.of(
public List<HeaderValue> headerValues() {
return List.of(
new HeaderValue(VENDOR_APPLICATION_PREFIX + "cbor",
Map.of(COMPATIBLE_WITH_PARAMETER_NAME, VERSION_PATTERN)));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,12 @@
import com.github.mustachejava.codes.DefaultMustache;
import com.github.mustachejava.codes.IterableCode;
import com.github.mustachejava.codes.WriteCode;
import org.apache.lucene.search.highlight.DefaultEncoder;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.xcontent.MediaType;
import org.elasticsearch.common.xcontent.ParsedMediaType;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.script.MustacheMediaType;

import java.io.IOException;
import java.io.StringWriter;
Expand All @@ -51,28 +53,32 @@
import java.util.regex.Pattern;

public class CustomMustacheFactory extends DefaultMustacheFactory {
private static final MediaType DEFAULT_MIME_TYPE = XContentType.JSON;

static final String JSON_MIME_TYPE_WITH_CHARSET = "application/json;charset=utf-8";
static final String JSON_MIME_TYPE = "application/json";
static final String PLAIN_TEXT_MIME_TYPE = "text/plain";
static final String X_WWW_FORM_URLENCODED_MIME_TYPE = "application/x-www-form-urlencoded";

private static final String DEFAULT_MIME_TYPE = JSON_MIME_TYPE;

private static final Map<String, Supplier<Encoder>> ENCODERS = Map.of(
JSON_MIME_TYPE_WITH_CHARSET, JsonEscapeEncoder::new,
JSON_MIME_TYPE, JsonEscapeEncoder::new,
PLAIN_TEXT_MIME_TYPE, DefaultEncoder::new,
X_WWW_FORM_URLENCODED_MIME_TYPE, UrlEncoder::new);
private static final Map<MediaType, Supplier<Encoder>> ENCODERS = Map.of(
XContentType.JSON, JsonEscapeEncoder::new,
MustacheMediaType.PLAIN_TEXT, DefaultEncoder::new,
MustacheMediaType.X_WWW_FORM_URLENCODED, UrlEncoder::new);

private final Encoder encoder;

public CustomMustacheFactory(String mimeType) {
super();
public CustomMustacheFactory(MediaType mimeType) {
setObjectHandler(new CustomReflectionObjectHandler());
this.encoder = createEncoder(mimeType);
}

public CustomMustacheFactory(String mimeType) {
this(parseMediaType(mimeType));
}

private static MediaType parseMediaType(String mimeType) {
MediaType mustacheMediaType = ParsedMediaType.parseMediaType(mimeType).toMediaType(MustacheMediaType.REGISTRY);
if (mustacheMediaType == null) {
throw new IllegalArgumentException("No encoder found for MIME type [" + mimeType + "]");
}
return mustacheMediaType;
}

public CustomMustacheFactory() {
this(DEFAULT_MIME_TYPE);
}
Expand All @@ -86,14 +92,19 @@ public void encode(String value, Writer writer) {
}
}

static Encoder createEncoder(String mimeType) {
final Supplier<Encoder> supplier = ENCODERS.get(mimeType);
private static Encoder createEncoder(MediaType mustacheMediaType) {
final Supplier<Encoder> supplier = ENCODERS.get(mustacheMediaType);
if (supplier == null) {
throw new IllegalArgumentException("No encoder found for MIME type [" + mimeType + "]");
throw new IllegalArgumentException("No encoder found for MIME type [" + mustacheMediaType + "]");
}
return supplier.get();
}

// package scope for testing
Encoder getEncoder() {
return encoder;
}

@Override
public MustacheVisitor createMustacheVisitor() {
return new CustomMustacheVisitor(this);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,54 +19,74 @@

package org.elasticsearch.script.mustache;

import org.elasticsearch.common.xcontent.ParsedMediaType;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.script.MustacheMediaType;
import org.elasticsearch.script.Script;
import org.elasticsearch.script.ScriptEngine;
import org.elasticsearch.script.TemplateScript;
import org.elasticsearch.test.ESTestCase;

import java.util.Collections;
import java.util.Map;

import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonMap;
import static org.elasticsearch.script.mustache.CustomMustacheFactory.JSON_MIME_TYPE;
import static org.elasticsearch.script.mustache.CustomMustacheFactory.PLAIN_TEXT_MIME_TYPE;
import static org.elasticsearch.script.mustache.CustomMustacheFactory.X_WWW_FORM_URLENCODED_MIME_TYPE;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;

public class CustomMustacheFactoryTests extends ESTestCase {
String JSON_MIME_TYPE_WITH_CHARSET = ParsedMediaType.parseMediaType(XContentType.JSON, Map.of("charset", "utf-8"))
.responseContentTypeHeader();
String JSON_MIME_TYPE = ParsedMediaType.parseMediaType(XContentType.JSON, Collections.emptyMap())
.responseContentTypeHeader();
String PLAIN_TEXT_MIME_TYPE = ParsedMediaType.parseMediaType(MustacheMediaType.PLAIN_TEXT, Collections.emptyMap())
.responseContentTypeHeader();
String X_WWW_FORM_URLENCODED_MIME_TYPE = ParsedMediaType.parseMediaType(MustacheMediaType.X_WWW_FORM_URLENCODED, Collections.emptyMap())
.responseContentTypeHeader();

public void testCreateEncoder() {
{
final IllegalArgumentException e =
expectThrows(IllegalArgumentException.class, () -> CustomMustacheFactory.createEncoder("non-existent"));
assertThat(e.getMessage(), equalTo("No encoder found for MIME type [non-existent]"));
expectThrows(IllegalArgumentException.class, () -> new CustomMustacheFactory("non-existent"));
assertThat(e.getMessage(), equalTo("invalid media-type [non-existent]"));
}

{
final IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> CustomMustacheFactory.createEncoder(""));
assertThat(e.getMessage(), equalTo("No encoder found for MIME type []"));
final IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> new CustomMustacheFactory(""));
assertThat(e.getMessage(), equalTo("invalid media-type []"));
}

{
final IllegalArgumentException e =
expectThrows(IllegalArgumentException.class, () -> CustomMustacheFactory.createEncoder("test"));
assertThat(e.getMessage(), equalTo("No encoder found for MIME type [test]"));
expectThrows(IllegalArgumentException.class, () -> new CustomMustacheFactory("test"));
assertThat(e.getMessage(), equalTo("invalid media-type [test]"));
}

assertThat(CustomMustacheFactory.createEncoder(CustomMustacheFactory.JSON_MIME_TYPE_WITH_CHARSET),
String contentType = ParsedMediaType.parseMediaType("application/json ; charset=UTF-8")
.responseContentTypeHeader();
assertThat(new CustomMustacheFactory(contentType).getEncoder(),
instanceOf(CustomMustacheFactory.JsonEscapeEncoder.class));
assertThat(new CustomMustacheFactory(JSON_MIME_TYPE_WITH_CHARSET).getEncoder(),
instanceOf(CustomMustacheFactory.JsonEscapeEncoder.class));
assertThat(new CustomMustacheFactory(JSON_MIME_TYPE).getEncoder(),
instanceOf(CustomMustacheFactory.JsonEscapeEncoder.class));

assertThat(new CustomMustacheFactory(PLAIN_TEXT_MIME_TYPE).getEncoder(),
instanceOf(CustomMustacheFactory.DefaultEncoder.class));
assertThat(new CustomMustacheFactory(X_WWW_FORM_URLENCODED_MIME_TYPE).getEncoder(),
instanceOf(CustomMustacheFactory.UrlEncoder.class));

//TODO PG should this be rejected?
contentType = ParsedMediaType.parseMediaType("application/json ; unknown=UTF-8")
.responseContentTypeHeader();
assertThat(new CustomMustacheFactory(contentType).getEncoder(),
instanceOf(CustomMustacheFactory.JsonEscapeEncoder.class));
assertThat(CustomMustacheFactory.createEncoder(CustomMustacheFactory.JSON_MIME_TYPE),
instanceOf(CustomMustacheFactory.JsonEscapeEncoder.class));
assertThat(CustomMustacheFactory.createEncoder(CustomMustacheFactory.PLAIN_TEXT_MIME_TYPE),
instanceOf(CustomMustacheFactory.DefaultEncoder.class));
assertThat(CustomMustacheFactory.createEncoder(CustomMustacheFactory.X_WWW_FORM_URLENCODED_MIME_TYPE),
instanceOf(CustomMustacheFactory.UrlEncoder.class));
}

public void testJsonEscapeEncoder() {
final ScriptEngine engine = new MustacheScriptEngine();
final Map<String, String> params = randomBoolean() ? singletonMap(Script.CONTENT_TYPE_OPTION, JSON_MIME_TYPE) : emptyMap();
final Map<String, String> params = randomBoolean() ? singletonMap(Script.CONTENT_TYPE_OPTION, JSON_MIME_TYPE) :
Collections.emptyMap();

TemplateScript.Factory compiled = engine.compile(null, "{\"field\": \"{{value}}\"}", TemplateScript.CONTEXT, params);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
---
"Verify that we can still find things with the template":
- skip:
version: "all"
reason: "Awaits fix: https://github.com/elastic/elasticsearch/issues/66986"

- do:
search_template:
rest_total_hits_as_int: true
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
---
"Verify that we can still find things with the template":
- skip:
version: "all"
reason: "Awaits fix: https://github.com/elastic/elasticsearch/issues/66986"

- do:
search_template:
Expand Down
Loading