View Javadoc
1   /*
2    * Copyright [2017] Wikimedia Foundation
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
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   * QueryBuilder for {@link SimSwitcherQuery}.
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       * Empty constructor.
72       */
73      public SimSwitcherQueryBuilder() {}
74  
75      /**
76       * Build the builder with all its fields.
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       * Streamable constructor.
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      * Parse the builder from a QueryParseContext.
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             // TODO: To support the "scripted" similarity we might find ways to inject the ScriptService here
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      * The subquery.
175      */
176     public QueryBuilder getSubQuery() {
177         return subQuery;
178     }
179 
180     /**
181      * Set the subquery.
182      */
183     public void setSubQuery(QueryBuilder subQuery) {
184         this.subQuery = subQuery;
185     }
186 
187     /**
188      * Get the similarity type.
189      */
190     public String getSimilarityType() {
191         return similarityType;
192     }
193 
194     /**
195      * Set the similarity type.
196      */
197     public void setSimilarityType(String similarityType) {
198         this.similarityType = similarityType;
199     }
200 
201     /**
202      * Get the similarity settings.
203      */
204     public Settings getParams() {
205         return params;
206     }
207 
208     /**
209      * Set the similarity settings.
210      */
211     public void setParams(Settings params) {
212         this.params = params;
213     }
214 }