Initial search service commit
[aai/search-data-service.git] / src / main / java / org / openecomp / sa / searchdbabstraction / searchapi / RangeQuery.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
29 /**
30  * This class represents a simple range query.
31  *
32  * <p>A range query is composed of one or more operator/value pairs which define
33  * the upper and lower bounds of the range, and a field to apply the query to.
34  *
35  * <p>Operators may be one of the following:
36  * <ul>
37  * <li>gt  - Greater than. </li>
38  * <li>gte - Greater than or equal to. </li>
39  * <li>lt  - Less than. </li>
40  * <li>lte - Less than or equal to. </li>
41  * </ul>
42  * Values may be either numeric values (Integer or Double) or Strings representing
43  * dates.
44  *
45  * <p>The following examples illustrate a couple of variants of the range query:
46  *
47  * <p><pre>
48  *     // A simple numeric range query:
49  *     {
50  *         "range": {
51  *             "field": "fieldname",
52  *             "gte": 5,
53  *             "lte": 10
54  *         }
55  *     }
56  *
57  *     // A simple date range query:
58  *     {
59  *         "range": {
60  *             "field": "fieldname",
61  *             "gt": "2016-10-06T00:00:00.558+03:00",
62  *             "lt": "2016-10-06T23:59:59.558+03:00"
63  *         }
64  *     }
65  * </pre>
66  */
67 public class RangeQuery {
68
69   /**
70    * The name of the field to apply the range query against.
71    */
72   private String field;
73
74   /**
75    * The value of the field must be greater than this value to be a match.<br>
76    * NOTE: Only one of 'gt' or 'gte' should be set on any single {@link RangeQuery}
77    * instance.
78    */
79   private Object gt;
80
81   /**
82    * The value of the field must be greater than or equal to this value to be a match.<br>
83    * NOTE: Only one of 'gt' or 'gte' should be set on any single {@link RangeQuery}
84    * instance.
85    */
86   private Object gte;
87
88   /**
89    * The value of the field must be less than this value to be a match.<br>
90    * NOTE: Only one of 'lt' or 'lte' should be set on any single {@link RangeQuery}
91    * instance.
92    */
93   private Object lt;
94
95   /**
96    * The value of the field must be less than or equal to than this value to be a match.<br>
97    * NOTE: Only one of 'lt' or 'lte' should be set on any single {@link RangeQuery}
98    * instance.
99    */
100   private Object lte;
101
102   private String format;
103
104   @JsonProperty("time-zone")
105   private String timeZone;
106
107   public String getField() {
108     return field;
109   }
110
111   public void setField(String field) {
112     this.field = field;
113   }
114
115   public Object getGt() {
116     return gt;
117   }
118
119   public void setGt(Object gt) {
120
121     // It does not make sense to assign a value to both the 'greater than'
122     // and 'greater than or equal' operations, so make sure we are not
123     // trying to do that.
124     if (gte == null) {
125
126       // Make sure that we are not trying to mix both numeric and date
127       // type values in the same queries.
128       if (((lt != null) && !typesMatch(gt, lt))
129           || ((lte != null) && !typesMatch(gt, lte))) {
130         throw new IllegalArgumentException("Cannot mix date and numeric values in the same ranged query");
131       }
132
133       // If we made it here, then we're all good.  Store the value.
134       this.gt = gt;
135     } else {
136       throw new IllegalArgumentException("Cannot assign both 'gt' and 'gte' fields in the same ranged query");
137     }
138   }
139
140
141   public Object getGte() {
142     return gte;
143   }
144
145   public void setGte(Object gte) {
146
147     // It does not make sense to assign a value to both the 'greater than'
148     // and 'greater than or equal' operations, so make sure we are not
149     // trying to do that.
150     if (gt == null) {
151
152       // Make sure that we are not trying to mix both numeric and date
153       // type values in the same queries.
154       if (((lt != null) && !typesMatch(gte, lt))
155           || ((lte != null) && !typesMatch(gte, lte))) {
156         throw new IllegalArgumentException("Cannot mix date and numeric values in the same ranged query");
157       }
158
159       // If we made it here, then we're all good.  Store the value.
160       this.gte = gte;
161
162     } else {
163       throw new IllegalArgumentException("Cannot assign both 'gt' and 'gte' fields in the same ranged query");
164     }
165   }
166
167   public Object getLt() {
168     return lt;
169   }
170
171   public void setLt(Object lt) {
172
173     // It does not make sense to assign a value to both the 'less than'
174     // and 'less than or equal' operations, so make sure we are not
175     // trying to do that.
176     if (lte == null) {
177
178       // Make sure that we are not trying to mix both numeric and date
179       // type values in the same queries.
180       if (((gt != null) && !typesMatch(lt, gt))
181           || ((gte != null) && !typesMatch(lt, gte))) {
182         throw new IllegalArgumentException("Cannot mix date and numeric values in the same ranged query");
183       }
184
185       // If we made it here, then we're all good.  Store the value.
186
187       this.lt = lt;
188     } else {
189       throw new IllegalArgumentException("Cannot assign both 'lt' and 'lte' fields in the same ranged query");
190     }
191   }
192
193   public Object getLte() {
194     return lte;
195   }
196
197   public void setLte(Object lte) {
198
199     // It does not make sense to assign a value to both the 'greater than'
200     // and 'greater than or equal' operations, so make sure we are not
201     // trying to do that.
202     if (lt == null) {
203
204       // Make sure that we are not trying to mix both numeric and date
205       // type values in the same queries.
206       if (((gt != null) && !typesMatch(lte, gt))
207           || ((gte != null) && !typesMatch(lte, gte))) {
208         throw new IllegalArgumentException("Cannot mix date and numeric values in the same ranged query");
209       }
210
211       // If we made it here, then we're all good.  Store the value.
212
213       this.lte = lte;
214     } else {
215       throw new IllegalArgumentException("Cannot assign both 'lt' and 'lte' fields in the same ranged query");
216     }
217   }
218
219   public String getFormat() {
220     return format;
221   }
222
223   public void setFormat(String format) {
224     this.format = format;
225   }
226
227   public String getTimeZone() {
228     return timeZone;
229   }
230
231   public void setTimeZone(String timeZone) {
232     this.timeZone = timeZone;
233   }
234
235   /**
236    * This convenience method determines whether or not the supplied
237    * value needs to be enclosed in '"' characters when generating
238    * ElasticSearch compatible syntax.
239    *
240    * @param val - The value to check.
241    * @return - A string representation of the value for inclusion
242    *     in an ElasticSearch syntax string.
243    */
244   private String formatStringOrNumericVal(Object val) {
245
246     if (val instanceof String) {
247       return "\"" + val.toString() + "\"";
248     } else {
249       return val.toString();
250     }
251   }
252
253
254   /**
255    * This convenience method verifies that the supplied objects are
256    * of classes considered to be compatible for a ranged query.
257    *
258    * @param value1 - The first value to check.
259    * @param value2 - The second value to check.
260    * @return - True if the two objects are compatible for inclusion in the
261    *     same ranged query, False, otherwise.
262    */
263   boolean typesMatch(Object value1, Object value2) {
264
265     return ((value1 instanceof String) && (value2 instanceof String))
266         || (!(value1 instanceof String) && !(value2 instanceof String));
267   }
268
269
270   /**
271    * This method returns a string which represents this query in syntax
272    * that is understandable by ElasticSearch and is suitable for inclusion
273    * in an ElasticSearch query string.
274    *
275    * @return - ElasticSearch syntax string.
276    */
277   public String toElasticSearch() {
278
279     StringBuilder sb = new StringBuilder();
280
281     sb.append("{");
282     sb.append("\"range\": {");
283     sb.append("\"").append(field).append("\": {");
284
285     // We may have one or zero of 'greater than' or 'greater
286     // than or equal'
287     boolean needComma = false;
288     if (gte != null) {
289       sb.append("\"gte\": ").append(formatStringOrNumericVal(gte));
290       needComma = true;
291     } else if (gt != null) {
292       sb.append("\"gt\": ").append(formatStringOrNumericVal(gt));
293       needComma = true;
294     }
295
296     // We may have one or zero of 'less than' or 'less
297     // than or equal'
298     if (lte != null) {
299       if (needComma) {
300         sb.append(", ");
301       }
302       sb.append("\"lte\": ").append(formatStringOrNumericVal(lte));
303     } else if (lt != null) {
304       if (needComma) {
305         sb.append(", ");
306       }
307       sb.append("\"lt\": ").append(formatStringOrNumericVal(lt));
308     }
309
310     // Append the format specifier if one was provided.
311     if (format != null) {
312       sb.append(", \"format\": \"").append(format).append("\"");
313     }
314
315     // Append the time zone specifier if one was provided.
316     if (timeZone != null) {
317       sb.append(", \"time_zone\": \"").append(timeZone).append("\"");
318     }
319
320     sb.append("}");
321     sb.append("}");
322     sb.append("}");
323
324     return sb.toString();
325   }
326
327   @Override
328   public String toString() {
329
330     String str = "{ field: " + field + ", ";
331
332     if (gt != null) {
333       str += "gt: " + gt;
334     } else if (gte != null) {
335       str += "gte: " + gte;
336     }
337
338     if (lt != null) {
339       str += (((gt != null) || (gte != null)) ? ", " : "") + "lt: " + lt;
340     } else if (lte != null) {
341       str += (((gt != null) || (gte != null)) ? ", " : "") + "lte: " + lte;
342     }
343
344     str += "}";
345
346     return str;
347   }
348 }