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

serialize suggestion responses as named writeables #30284

Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
64369c8
serialize suggestion responses as named writeables
andyb-elastic Mar 10, 2018
990d546
checkstyle fixes
andyb-elastic May 1, 2018
09b3ccc
Merge branch 'master' into feature-plugin-suggester-named-writeable-a…
andyb-elastic Jul 18, 2018
4f695a7
wip suggest update tests with new option usage
andyb-elastic Jul 18, 2018
155df07
wip suggest only one suggestion reader
andyb-elastic Jul 18, 2018
3add813
rest tests for custom suggester example plugin
andyb-elastic Jul 25, 2018
f062443
checkstyle fixes
andyb-elastic Jul 25, 2018
5b8fa25
fill in toXContent support for custom fields - change Entry and Option
andyb-elastic Jul 25, 2018
4e5ff17
remove custom suggester IT from server
andyb-elastic Jul 26, 2018
80ce511
Merge branch 'master' into feature-plugin-suggester-named-writeable-a…
andyb-elastic Jul 26, 2018
69163ba
use an empty test suggester impl in SearchModuleTests
andyb-elastic Jul 26, 2018
bcd3704
backwards compatiblity corrections
andyb-elastic Jul 27, 2018
87bddd1
make release note more clear
andyb-elastic Jul 27, 2018
a7c3f01
Merge branch 'master' into feature-plugin-suggester-named-writeable-a…
andyb-elastic Jul 30, 2018
2923532
use innerReadFrom innerWriteTo for old nodes
andyb-elastic Jul 31, 2018
0059b61
serialization shim for TermSuggestion
andyb-elastic Jul 31, 2018
a2b7bef
wip round triup works but only because it's not equalsing
andyb-elastic Aug 2, 2018
df532f1
serialization tests pass
andyb-elastic Aug 2, 2018
ee7efb4
remove commented out methods
andyb-elastic Aug 3, 2018
b77ed84
Merge branch 'master' into feature-plugin-suggester-named-writeable-a…
andyb-elastic Aug 3, 2018
4603a0d
Merge branch 'master' into feature-plugin-suggester-named-writeable-a…
andyb-elastic Aug 3, 2018
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 @@ -163,8 +163,11 @@
import org.elasticsearch.search.aggregations.pipeline.derivative.ParsedDerivative;
import org.elasticsearch.search.suggest.Suggest;
import org.elasticsearch.search.suggest.completion.CompletionSuggestion;
import org.elasticsearch.search.suggest.completion.CompletionSuggestionBuilder;
import org.elasticsearch.search.suggest.phrase.PhraseSuggestion;
import org.elasticsearch.search.suggest.phrase.PhraseSuggestionBuilder;
import org.elasticsearch.search.suggest.term.TermSuggestion;
import org.elasticsearch.search.suggest.term.TermSuggestionBuilder;

import java.io.Closeable;
import java.io.IOException;
Expand Down Expand Up @@ -1119,11 +1122,11 @@ static List<NamedXContentRegistry.Entry> getDefaultNamedXContents() {
List<NamedXContentRegistry.Entry> entries = map.entrySet().stream()
.map(entry -> new NamedXContentRegistry.Entry(Aggregation.class, new ParseField(entry.getKey()), entry.getValue()))
.collect(Collectors.toList());
entries.add(new NamedXContentRegistry.Entry(Suggest.Suggestion.class, new ParseField(TermSuggestion.NAME),
entries.add(new NamedXContentRegistry.Entry(Suggest.Suggestion.class, new ParseField(TermSuggestionBuilder.SUGGESTION_NAME),
(parser, context) -> TermSuggestion.fromXContent(parser, (String)context)));
entries.add(new NamedXContentRegistry.Entry(Suggest.Suggestion.class, new ParseField(PhraseSuggestion.NAME),
entries.add(new NamedXContentRegistry.Entry(Suggest.Suggestion.class, new ParseField(PhraseSuggestionBuilder.SUGGESTION_NAME),
(parser, context) -> PhraseSuggestion.fromXContent(parser, (String)context)));
entries.add(new NamedXContentRegistry.Entry(Suggest.Suggestion.class, new ParseField(CompletionSuggestion.NAME),
entries.add(new NamedXContentRegistry.Entry(Suggest.Suggestion.class, new ParseField(CompletionSuggestionBuilder.SUGGESTION_NAME),
(parser, context) -> CompletionSuggestion.fromXContent(parser, (String)context)));
return entries;
}
Expand Down
33 changes: 33 additions & 0 deletions plugins/examples/custom-suggester/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* 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.
*/

apply plugin: 'elasticsearch.esplugin'

esplugin {
name 'custom-suggester'
description 'An example plugin showing how to write and register a custom suggester'
classname 'org.elasticsearch.example.customsuggester.CustomSuggesterPlugin'
}

integTestCluster {
numNodes = 2
}

// this plugin has no unit tests, only rest tests
tasks.test.enabled = false
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* 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.example.customsuggester;

import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.util.CharsRefBuilder;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.search.suggest.Suggest;
import org.elasticsearch.search.suggest.Suggester;

import java.util.Locale;

public class CustomSuggester extends Suggester<CustomSuggestionContext> {

// This is a pretty dumb implementation which returns the original text + fieldName + custom config option + 12 or 123
@Override
public Suggest.Suggestion<? extends Suggest.Suggestion.Entry<? extends Suggest.Suggestion.Entry.Option>> innerExecute(
String name,
CustomSuggestionContext suggestion,
IndexSearcher searcher,
CharsRefBuilder spare) {

// Get the suggestion context
String text = suggestion.getText().utf8ToString();

// create two suggestions with 12 and 123 appended
CustomSuggestion response = new CustomSuggestion(name, suggestion.getSize(), "suggestion-dummy-value");

CustomSuggestion.Entry entry = new CustomSuggestion.Entry(new Text(text), 0, text.length(), "entry-dummy-value");

String firstOption =
String.format(Locale.ROOT, "%s-%s-%s-%s", text, suggestion.getField(), suggestion.options.get("suffix"), "12");
CustomSuggestion.Entry.Option option12 = new CustomSuggestion.Entry.Option(new Text(firstOption), 0.9f, "option-dummy-value-1");
entry.addOption(option12);

String secondOption =
String.format(Locale.ROOT, "%s-%s-%s-%s", text, suggestion.getField(), suggestion.options.get("suffix"), "123");
CustomSuggestion.Entry.Option option123 = new CustomSuggestion.Entry.Option(new Text(secondOption), 0.8f, "option-dummy-value-2");
entry.addOption(option123);

response.addTerm(entry);

return response;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* 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.example.customsuggester;

import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.plugins.SearchPlugin;

import java.util.Collections;
import java.util.List;

public class CustomSuggesterPlugin extends Plugin implements SearchPlugin {
@Override
public List<SearchPlugin.SuggesterSpec<?>> getSuggesters() {
return Collections.singletonList(
new SearchPlugin.SuggesterSpec<>(
CustomSuggestionBuilder.SUGGESTION_NAME,
CustomSuggestionBuilder::new,
CustomSuggestionBuilder::fromXContent,
CustomSuggestion::new
)
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
/*
* 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.example.customsuggester;

import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
import org.elasticsearch.common.xcontent.ObjectParser;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.search.suggest.Suggest;

import java.io.IOException;

import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;

public class CustomSuggestion extends Suggest.Suggestion<CustomSuggestion.Entry> {

public static final int TYPE = 999;

public static final ParseField DUMMY = new ParseField("dummy");

private String dummy;

public CustomSuggestion(String name, int size, String dummy) {
super(name, size);
this.dummy = dummy;
}

public CustomSuggestion(StreamInput in) throws IOException {
super(in);
dummy = in.readString();
}

@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
out.writeString(dummy);
}

@Override
public String getWriteableName() {
return CustomSuggestionBuilder.SUGGESTION_NAME;
}

@Override
public int getWriteableType() {
return TYPE;
}

/**
* A meaningless value used to test that plugin suggesters can add fields to their Suggestion types
*
* This can't be serialized to xcontent because Suggestions appear in xcontent as an array of entries, so there is no place
* to add a custom field. But we can still use a custom field internally and use it to define a Suggestion's behavior
*/
public String getDummy() {
return dummy;
}

@Override
protected Entry newEntry() {
return new Entry();
}

@Override
protected Entry newEntry(StreamInput in) throws IOException {
return new Entry(in);
}

public static CustomSuggestion fromXContent(XContentParser parser, String name) throws IOException {
CustomSuggestion suggestion = new CustomSuggestion(name, -1, null);
parseEntries(parser, suggestion, Entry::fromXContent);
return suggestion;
}

public static class Entry extends Suggest.Suggestion.Entry<CustomSuggestion.Entry.Option> {

private static final ObjectParser<Entry, Void> PARSER = new ObjectParser<>("CustomSuggestionEntryParser", true, Entry::new);

static {
declareCommonFields(PARSER);
PARSER.declareString((entry, dummy) -> entry.dummy = dummy, DUMMY);
PARSER.declareObjectArray(Entry::addOptions, (p, c) -> Option.fromXContent(p), new ParseField(OPTIONS));
}

private String dummy;

public Entry() {}

public Entry(Text text, int offset, int length, String dummy) {
super(text, offset, length);
this.dummy = dummy;
}

public Entry(StreamInput in) throws IOException {
super(in);
dummy = in.readString();
}

@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
out.writeString(dummy);
}

@Override
protected Option newOption() {
return new Option();
}

@Override
protected Option newOption(StreamInput in) throws IOException {
return new Option(in);
}

/*
* the value of dummy will always be the same, so this just tests that we can merge entries with custom fields
*/
@Override
protected void merge(Suggest.Suggestion.Entry<Option> otherEntry) {
dummy = ((Entry) otherEntry).getDummy();
}

/**
* Meaningless field used to test that plugin suggesters can add fields to their entries
*/
public String getDummy() {
return dummy;
}

@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder = super.toXContent(builder, params);
builder.field(DUMMY.getPreferredName(), getDummy());
return builder;
}

public static Entry fromXContent(XContentParser parser) {
return PARSER.apply(parser, null);
}

public static class Option extends Suggest.Suggestion.Entry.Option {

private static final ConstructingObjectParser<Option, Void> PARSER = new ConstructingObjectParser<>(
"CustomSuggestionObjectParser", true,
args -> {
Text text = new Text((String) args[0]);
float score = (float) args[1];
String dummy = (String) args[2];
return new Option(text, score, dummy);
});

static {
PARSER.declareString(constructorArg(), TEXT);
PARSER.declareFloat(constructorArg(), SCORE);
PARSER.declareString(constructorArg(), DUMMY);
}

private String dummy;

public Option() {}

public Option(Text text, float score, String dummy) {
super(text, score);
this.dummy = dummy;
}

public Option(StreamInput in) throws IOException {
super(in);
dummy = in.readString();
}

@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
out.writeString(dummy);
}

/**
* A meaningless value used to test that plugin suggesters can add fields to their options
*/
public String getDummy() {
return dummy;
}

/*
* the value of dummy will always be the same, so this just tests that we can merge options with custom fields
*/
@Override
protected void mergeInto(Suggest.Suggestion.Entry.Option otherOption) {
super.mergeInto(otherOption);
dummy = ((Option) otherOption).getDummy();
}

@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder = super.toXContent(builder, params);
builder.field(DUMMY.getPreferredName(), dummy);
return builder;
}

public static Option fromXContent(XContentParser parser) {
return PARSER.apply(parser, null);
}
}
}
}
Loading