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