1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.wikimedia.search.extra.simswitcher;
18
19 import java.io.IOException;
20 import java.util.Objects;
21
22 import org.apache.lucene.search.Query;
23 import org.apache.lucene.search.similarities.Similarity;
24 import org.elasticsearch.Version;
25 import org.elasticsearch.common.ParseField;
26 import org.elasticsearch.common.ParsingException;
27 import org.elasticsearch.common.TriFunction;
28 import org.elasticsearch.common.io.stream.StreamInput;
29 import org.elasticsearch.common.io.stream.StreamOutput;
30 import org.elasticsearch.common.settings.Settings;
31 import org.elasticsearch.common.xcontent.ObjectParser;
32 import org.elasticsearch.common.xcontent.XContentBuilder;
33 import org.elasticsearch.common.xcontent.XContentParser;
34 import org.elasticsearch.index.query.AbstractQueryBuilder;
35 import org.elasticsearch.index.query.QueryBuilder;
36 import org.elasticsearch.index.query.QueryRewriteContext;
37 import org.elasticsearch.index.query.QueryShardContext;
38 import org.elasticsearch.index.query.Rewriteable;
39 import org.elasticsearch.index.similarity.SimilarityService;
40 import org.elasticsearch.script.ScriptService;
41
42 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
43
44
45
46
47 public class SimSwitcherQueryBuilder extends AbstractQueryBuilder<SimSwitcherQueryBuilder> {
48 public static final String NAME = "simswitcher";
49 public static final ObjectParser<SimSwitcherQueryBuilder, Void> PARSER;
50 public static final ParseField QUERY = new ParseField("query");
51 public static final ParseField SIM_TYPE = new ParseField("type");
52 public static final ParseField PARAMS = new ParseField("params");
53
54 static {
55 PARSER = new ObjectParser<>(NAME, SimSwitcherQueryBuilder::new);
56 PARSER.declareObject(SimSwitcherQueryBuilder::setSubQuery,
57 (parser, ctx) -> parseInnerQueryBuilder(parser),
58 QUERY);
59 PARSER.declareString(SimSwitcherQueryBuilder::setSimilarityType,
60 SIM_TYPE);
61 PARSER.declareObject(SimSwitcherQueryBuilder::setParams,
62 (parser, ctx) -> Settings.fromXContent(parser),
63 PARAMS);
64 declareStandardFields(PARSER);
65 }
66 private QueryBuilder subQuery;
67 private String similarityType;
68 private Settings params;
69
70
71
72
73 public SimSwitcherQueryBuilder() {}
74
75
76
77
78 @SuppressFBWarnings(value = "OCP_OVERLY_CONCRETE_PARAMETER",
79 justification = "Spotbugs wants params to be ToXContent but this makes no sense")
80 public SimSwitcherQueryBuilder(QueryBuilder subQuery, String similarityType, Settings params) {
81 this.subQuery = subQuery;
82 this.similarityType = similarityType;
83 this.params = params != null ? params : Settings.EMPTY;
84 }
85
86
87
88
89 public SimSwitcherQueryBuilder(StreamInput input) throws IOException {
90 super(input);
91 subQuery = input.readNamedWriteable(QueryBuilder.class);
92 similarityType = input.readString();
93 params = Settings.readSettingsFromStream(input);
94 }
95
96 @Override
97 protected void doWriteTo(StreamOutput out) throws IOException {
98 out.writeNamedWriteable(subQuery);
99 out.writeString(similarityType);
100 Settings.writeSettingsToStream(this.params != null ? this.params : Settings.EMPTY, out);
101 }
102
103 @Override
104 @SuppressFBWarnings(value = "PRMC_POSSIBLY_REDUNDANT_METHOD_CALLS",
105 justification = "spotbugs does not understand that endObject() needs to called multiple times")
106 protected void doXContent(XContentBuilder builder, Params params) throws IOException {
107 builder.startObject(NAME);
108 builder.field(QUERY.getPreferredName(), subQuery, params)
109 .field(SIM_TYPE.getPreferredName(), similarityType)
110 .field(PARAMS.getPreferredName());
111 builder.startObject();
112 this.params.toXContent(builder, params);
113 builder.endObject();
114 printBoostAndQueryName(builder);
115 builder.endObject();
116 }
117
118
119
120
121 public static SimSwitcherQueryBuilder fromXContent(XContentParser parser) throws IOException {
122 try {
123 SimSwitcherQueryBuilder builder = PARSER.parse(parser, null);
124 if (builder.subQuery == null) {
125 throw new ParsingException(parser.getTokenLocation(), "[" + QUERY.getPreferredName() + "] is mandatory");
126 }
127 if (builder.similarityType == null) {
128 throw new ParsingException(parser.getTokenLocation(), "[" + SIM_TYPE.getPreferredName() + "] is mandatory");
129 }
130 return builder;
131 } catch (IllegalArgumentException iae) {
132 throw new ParsingException(parser.getTokenLocation(), iae.getMessage(), iae);
133 }
134 }
135
136 @Override
137 protected QueryBuilder doRewrite(QueryRewriteContext queryShardContext) throws IOException {
138 QueryBuilder q = Rewriteable.rewrite(subQuery, queryShardContext);
139 if (q != subQuery) {
140 return new SimSwitcherQueryBuilder(q, similarityType, params);
141 }
142 return this;
143 }
144
145 @Override
146 protected Query doToQuery(QueryShardContext context) throws IOException {
147 if (similarityType.equals("scripted")) {
148
149 throw new IllegalArgumentException("The similarity [scripted] is not supported by simswitcher");
150 }
151
152 TriFunction<Settings, Version, ScriptService, Similarity> provider = SimilarityService.BUILT_IN.get(similarityType);
153 Similarity sim = provider.apply(params != null ? params : Settings.EMPTY, Version.CURRENT, null);
154 return new SimSwitcherQuery(sim, subQuery.toQuery(context));
155 }
156
157 @Override
158 protected boolean doEquals(SimSwitcherQueryBuilder other) {
159 return Objects.equals(subQuery, other.subQuery) &&
160 Objects.equals(params, other.params);
161 }
162
163 @Override
164 protected int doHashCode() {
165 return Objects.hash(subQuery, params);
166 }
167
168 @Override
169 public String getWriteableName() {
170 return NAME;
171 }
172
173
174
175
176 public QueryBuilder getSubQuery() {
177 return subQuery;
178 }
179
180
181
182
183 public void setSubQuery(QueryBuilder subQuery) {
184 this.subQuery = subQuery;
185 }
186
187
188
189
190 public String getSimilarityType() {
191 return similarityType;
192 }
193
194
195
196
197 public void setSimilarityType(String similarityType) {
198 this.similarityType = similarityType;
199 }
200
201
202
203
204 public Settings getParams() {
205 return params;
206 }
207
208
209
210
211 public void setParams(Settings params) {
212 this.params = params;
213 }
214 }