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