2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright © 2017 AT&T Intellectual Property.
6 * Copyright © 2017 Amdocs
8 * ================================================================================
9 * Licensed under the Apache License, Version 2.0 (the "License");
10 * you may not use this file except in compliance with the License.
11 * You may obtain a copy of the License ati
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing, software
16 * distributed under the License is distributed on an "AS IS" BASIS,
17 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 * See the License for the specific language governing permissions and
19 * limitations under the License.
20 * ============LICENSE_END=========================================================
22 * ECOMP and OpenECOMP are trademarks
23 * and service marks of AT&T Intellectual Property.
25 package org.openecomp.sa.searchdbabstraction.searchapi;
27 import com.fasterxml.jackson.annotation.JsonProperty;
28 import edu.emory.mathcs.backport.java.util.Arrays;
30 import java.util.List;
31 import java.util.concurrent.atomic.AtomicBoolean;
34 * This class represents a simple term query.
36 * <p>A term query takes an operator, a field to apply the query to and a value to match
37 * against the query contents.
39 * <p>Valid operators include:
41 * <li> match - Field must contain the supplied value to produce a match. </li>
42 * <li> not-match - Field must NOT contain the supplied value to produce a match. </li>
44 * The following examples illustrate the structure of a few variants of the
48 * // Single Field Match Query:
50 * "match": {"field": "searchTags", "value": "abcd"}
53 * // Single Field Not-Match query:
55 * "not-match": {"field": "searchTags", "value": "efgh"}
60 * // Multi Field Match Query With A Single Value:
62 * "match": {"field": "entityType searchTags", "value": "pserver"}
65 * // Multi Field Match Query With Multiple Values:
67 * "match": {"field": "entityType searchTags", "value": "pserver tenant"}
71 public class TermQuery {
74 * The name of the field to apply the term query to.
79 * The value which the field must contain in order to have a match.
84 * For multi field queries only. Determines the rules for whether or not a document matches
85 * the query, as follows:
87 * <p>"and" - At least one occurrence of every supplied value must be present in any of the
90 * <p>"or" - At least one occurrence of any of the supplied values must be present in any of
91 * the supplied fields.
93 private String operator;
95 @JsonProperty("analyzer")
96 private String searchAnalyzer;
99 public String getField() {
103 public void setField(String field) {
107 public Object getValue() {
111 public void setValue(Object value) {
115 private boolean isNumericValue() {
116 return ((value instanceof Integer) || (value instanceof Double));
119 public String getOperator() {
123 public void setOperator(String operator) {
124 this.operator = operator;
127 public String getSearchAnalyzer() {
128 return searchAnalyzer;
131 public void setSearchAnalyzer(String searchAnalyzer) {
132 this.searchAnalyzer = searchAnalyzer;
136 * This method returns a string which represents this query in syntax
137 * that is understandable by ElasticSearch and is suitable for inclusion
138 * in an ElasticSearch query string.
140 * @return - ElasticSearch syntax string.
142 public String toElasticSearch() {
144 StringBuilder sb = new StringBuilder();
148 // Are we generating a multi field query?
149 if (isMultiFieldQuery()) {
151 // For multi field queries, we have to be careful about how we handle
152 // nested fields, so check to see if any of the specified fields are
154 if (field.contains(".")) {
156 // Build the equivalent of a multi match query across one or more nested fields.
157 toElasticSearchNestedMultiMatchQuery(sb);
161 // Build a real multi match query, since we don't need to worry about nested fields.
162 toElasticSearchMultiFieldQuery(sb);
166 // Single field query.
168 // Add the necessary wrapping if this is a query against a nested field.
169 if (fieldIsNested(field)) {
170 sb.append("{\"nested\": { \"path\": \"").append(pathForNestedField(field))
171 .append("\", \"query\": ");
175 toElasticSearchSingleFieldQuery(sb);
177 if (fieldIsNested(field)) {
184 return sb.toString();
189 * Determines whether or not the client has specified a term query with
192 * @return - true if the query is referencing multiple fields, false, otherwise.
194 private boolean isMultiFieldQuery() {
196 return (field.split(" ").length > 1);
201 * Constructs a single field term query in ElasticSearch syntax.
203 * @param sb - The string builder to assemble the query string with.
204 * @return - The single term query.
206 private void toElasticSearchSingleFieldQuery(StringBuilder sb) {
208 sb.append("\"term\": {\"").append(field).append("\" : ");
210 // For numeric values, don't enclose the value in quotes.
211 if (!isNumericValue()) {
212 sb.append("\"").append(value).append("\"");
222 * Constructs a multi field query in ElasticSearch syntax.
224 * @param sb - The string builder to assemble the query string with.
225 * @return - The multi field query.
227 private void toElasticSearchMultiFieldQuery(StringBuilder sb) {
229 sb.append("\"multi_match\": {");
231 sb.append("\"query\": \"").append(value).append("\", ");
232 sb.append("\"type\": \"cross_fields\",");
233 sb.append("\"fields\": [");
235 List<String> fields = Arrays.asList(field.split(" "));
236 AtomicBoolean firstField = new AtomicBoolean(true);
237 for (String f : fields) {
238 if (!firstField.compareAndSet(true, false)) {
241 sb.append("\"").append(f.trim()).append("\"");
245 sb.append("\"operator\": \"").append((operator != null)
246 ? operator.toLowerCase() : "and").append("\"");
248 if (searchAnalyzer != null) {
249 sb.append(", \"analyzer\": \"").append(searchAnalyzer).append("\"");
257 * Constructs the equivalent of an ElasticSearch multi match query across
258 * multiple nested fields.
260 * <p>Since ElasticSearch doesn't really let you do that, we have to be clever
261 * and construct an equivalent query using boolean operators to produce
264 * @param sb - The string builder to use to build the query.
266 public void toElasticSearchNestedMultiMatchQuery(StringBuilder sb) {
268 // Break out our whitespace delimited list of fields and values into a actual lists.
269 List<String> fields = Arrays.asList(field.split(" "));
270 List<String> values = Arrays.asList(((String) value).split(" ")); // GDF: revisit this cast.
272 sb.append("\"bool\": {");
274 if (operator != null) {
276 if (operator.toLowerCase().equals("and")) {
277 sb.append("\"must\": [");
278 } else if (operator.toLowerCase().equals("or")) {
279 sb.append("\"should\": [");
283 sb.append("\"must\": [");
286 AtomicBoolean firstField = new AtomicBoolean(true);
287 for (String f : fields) {
289 if (!firstField.compareAndSet(true, false)) {
295 // Is this a nested field?
296 if (fieldIsNested(f)) {
298 sb.append("\"nested\": {");
299 sb.append("\"path\": \"").append(pathForNestedField(f)).append("\", ");
300 sb.append("\"query\": ");
303 sb.append("{\"bool\": {");
304 sb.append("\"should\": [");
306 AtomicBoolean firstValue = new AtomicBoolean(true);
307 for (String v : values) {
308 if (!firstValue.compareAndSet(true, false)) {
311 sb.append("{\"match\": { \"");
312 sb.append(f).append("\": {\"query\": \"").append(v).append("\"");
314 if (searchAnalyzer != null) {
315 sb.append(", \"analyzer\": \"").append(searchAnalyzer).append("\"");
323 if (fieldIsNested(f)) {
337 public String toString() {
338 return "field: " + field + ", value: " + value + " (" + value.getClass().getName() + ")";
341 public boolean fieldIsNested(String field) {
342 return field.contains(".");
345 public String pathForNestedField(String field) {
346 int index = field.lastIndexOf('.');
347 return field.substring(0, index);