Initial search service commit
[aai/search-data-service.git] / src / main / java / org / openecomp / sa / searchdbabstraction / searchapi / SearchStatement.java
1 /**
2  * ============LICENSE_START=======================================================
3  * Search Data Service
4  * ================================================================================
5  * Copyright © 2017 AT&T Intellectual Property.
6  * Copyright © 2017 Amdocs
7  * All rights reserved.
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
12  *
13  *    http://www.apache.org/licenses/LICENSE-2.0
14  *
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=========================================================
21  *
22  * ECOMP and OpenECOMP are trademarks
23  * and service marks of AT&T Intellectual Property.
24  */
25 package org.openecomp.sa.searchdbabstraction.searchapi;
26
27 import com.fasterxml.jackson.annotation.JsonProperty;
28 import org.radeox.util.logging.Logger;
29
30 import java.util.ArrayList;
31 import java.util.List;
32 import java.util.concurrent.atomic.AtomicBoolean;
33
34 /**
35  * This class represents the structure of a search statement.
36  *
37  * <p>The expected JSON structure to represent a search statement is as follows:
38  *
39  * <p><pre>
40  *     {
41  *         "results-start": int,  - Optional: index of starting point in result set.
42  *         "results-size": int,   - Optional: maximum number of documents to include in result set.
43  *
44  *         "filter": {
45  *             { filter structure - see {@link Filter} }
46  *         },
47  *
48  *         "queries": [
49  *             { query structure - see {@link QueryStatement} },
50  *             { query structure - see {@link QueryStatement} },
51  *                              .
52  *                              .
53  *             { query structure - see {@link QueryStatement} },
54  *         ],
55  *
56  *         "aggregations": [
57  *             { aggregation structure - see {@link AggregationStatement} },
58  *             { aggregation structure - see {@link AggregationStatement} },
59  *                              .
60  *                              .
61  *             { aggregation structure - see {@link AggregationStatement} },
62  *         ]
63  *     }
64  * </pre>
65  */
66 public class SearchStatement {
67
68   /**
69    * Defines the filters that should be applied before running the
70    * actual queries.  This is optional.
71    */
72   private Filter filter;
73
74   /**
75    * The list of queries to be applied to the document store.
76    */
77   private Query[] queries;
78
79   /**
80    * The list of aggregations to be applied to the search
81    */
82   private Aggregation[] aggregations;
83
84   /**
85    * Defines the sort criteria to apply to the query result set.
86    * This is optional.
87    */
88   private Sort sort;
89
90   @JsonProperty("results-start")
91   private Integer resultsStart;
92
93   @JsonProperty("results-size")
94   private Integer size;
95
96   public Filter getFilter() {
97     return filter;
98   }
99
100   public void setFilter(Filter filter) {
101     this.filter = filter;
102   }
103
104   public Query[] getQueries() {
105     return queries;
106   }
107
108   public void setQueries(Query[] queries) {
109     this.queries = queries;
110   }
111
112   public Sort getSort() {
113     return sort;
114   }
115
116   public void setSort(Sort sort) {
117     this.sort = sort;
118   }
119
120   public boolean isFiltered() {
121     return filter != null;
122   }
123
124   public Aggregation[] getAggregations() {
125     return aggregations;
126   }
127
128   public void setAggregations(Aggregation[] aggregations) {
129     this.aggregations = aggregations;
130   }
131
132   public boolean hasAggregations() {
133     return aggregations != null && aggregations.length > 0;
134   }
135
136   public Integer getFrom() {
137     return resultsStart;
138   }
139
140   public void setFrom(Integer from) {
141     this.resultsStart = from;
142   }
143
144   public Integer getSize() {
145     return size;
146   }
147
148   public void setSize(Integer size) {
149     this.size = size;
150   }
151
152   /**
153    * This method returns a string which represents this statement in syntax
154    * that is understandable by ElasticSearch and is suitable for inclusion
155    * in an ElasticSearch query string.
156    *
157    * @return - ElasticSearch syntax string.
158    */
159   public String toElasticSearch() {
160
161     StringBuilder sb = new StringBuilder();
162     List<QueryStatement> notMatchQueries = new ArrayList<QueryStatement>();
163     List<QueryStatement> mustQueries = new ArrayList<QueryStatement>();
164     List<QueryStatement> shouldQueries = new ArrayList<QueryStatement>();
165
166     createQueryLists(queries, mustQueries, shouldQueries, notMatchQueries);
167
168     sb.append("{");
169
170     sb.append("\"version\": true,");
171
172     // If the client has specified an index into the results for the first
173     // document in the result set then include that in the ElasticSearch
174     // query.
175     if (resultsStart != null) {
176       sb.append("\"from\": ").append(resultsStart).append(", ");
177     }
178
179     // If the client has specified a maximum number of documents to be returned
180     // in the result set then include that in the ElasticSearch query.
181     if (size != null) {
182       sb.append("\"size\": ").append(size).append(", ");
183     }
184
185     sb.append("\"query\": {");
186     sb.append("\"bool\": {");
187
188     sb.append("\"must\": [");
189     AtomicBoolean firstQuery = new AtomicBoolean(true);
190     for (QueryStatement query : mustQueries) {
191
192       if (!firstQuery.compareAndSet(true, false)) {
193         sb.append(", ");
194       }
195
196       sb.append(query.toElasticSearch());
197     }
198     sb.append("], ");
199
200     sb.append("\"should\": [");
201
202     firstQuery = new AtomicBoolean(true);
203     for (QueryStatement query : shouldQueries) {
204
205       if (!firstQuery.compareAndSet(true, false)) {
206         sb.append(", ");
207       }
208
209       sb.append(query.toElasticSearch());
210     }
211
212     sb.append("],"); // close should list
213
214     sb.append("\"must_not\": [");
215     firstQuery.set(true);
216     for (QueryStatement query : notMatchQueries) {
217       sb.append(query.toElasticSearch());
218     }
219     sb.append("]");
220
221     // Add the filter stanza, if one is required.
222     if (isFiltered()) {
223       sb.append(", \"filter\": ").append(filter.toElasticSearch());
224     }
225
226     sb.append("}"); // close bool clause
227     sb.append("}"); // close query clause
228
229     // Add the sort directive, if one is required.
230     if (sort != null) {
231       sb.append(", \"sort\": ").append(sort.toElasticSearch());
232     }
233
234     // Add aggregations
235     if (hasAggregations()) {
236       sb.append(", \"aggs\": {");
237
238       for (int i = 0; i < aggregations.length; i++) {
239         if (i > 0) {
240           sb.append(",");
241         }
242         sb.append(aggregations[i].toElasticSearch());
243       }
244
245       sb.append("}");
246     }
247
248     sb.append("}");
249
250     Logger.debug("Generated raw ElasticSearch query statement: " + sb.toString());
251     return sb.toString();
252   }
253
254   private void createQueryLists(Query[] queries, List<QueryStatement> mustList,
255                                 List<QueryStatement> mayList, List<QueryStatement> mustNotList) {
256
257     for (Query query : queries) {
258
259       if (query.isMust()) {
260
261         if (query.getQueryStatement().isNotMatch()) {
262           mustNotList.add(query.getQueryStatement());
263         } else {
264           mustList.add(query.getQueryStatement());
265         }
266       } else {
267
268         if (query.getQueryStatement().isNotMatch()) {
269           mustNotList.add(query.getQueryStatement());
270         } else {
271           mayList.add(query.getQueryStatement());
272         }
273       }
274     }
275   }
276
277
278   @Override
279   public String toString() {
280
281     StringBuilder sb = new StringBuilder();
282
283     sb.append("SEARCH STATEMENT: {");
284
285     if (size != null) {
286       sb.append("from: ").append(resultsStart).append(", size: ").append(size).append(", ");
287     }
288
289     if (filter != null) {
290       sb.append("filter: ").append(filter.toString()).append(", ");
291     }
292
293     sb.append("queries: [");
294     AtomicBoolean firstQuery = new AtomicBoolean(true);
295     if (queries != null) {
296       for (Query query : queries) {
297
298         if (!firstQuery.compareAndSet(true, false)) {
299           sb.append(", ");
300         }
301         sb.append(query.toString());
302       }
303     }
304     sb.append("]");
305
306     sb.append("aggregations: [");
307     firstQuery = new AtomicBoolean(true);
308
309     if (aggregations != null) {
310       for (Aggregation agg : aggregations) {
311
312         if (!firstQuery.compareAndSet(true, false)) {
313           sb.append(", ");
314         }
315         sb.append(agg.toString());
316       }
317     }
318     sb.append("]");
319
320     sb.append("]}");
321
322     return sb.toString();
323   }
324
325 }