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