aboutsummaryrefslogtreecommitdiffstats
path: root/src/main/java/org/openecomp/sa/searchdbabstraction/searchapi
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/org/openecomp/sa/searchdbabstraction/searchapi')
-rw-r--r--src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/AbstractAggregation.java82
-rw-r--r--src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/Aggregation.java67
-rw-r--r--src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/AggregationStatement.java178
-rw-r--r--src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/DateHistogramAggregation.java119
-rw-r--r--src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/DateRange.java115
-rw-r--r--src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/DateRangeAggregation.java133
-rw-r--r--src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/Filter.java190
-rw-r--r--src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/GroupByAggregation.java73
-rw-r--r--src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/ParsedQuery.java120
-rw-r--r--src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/Query.java94
-rw-r--r--src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/QueryStatement.java142
-rw-r--r--src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/RangeQuery.java348
-rw-r--r--src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/SearchStatement.java325
-rw-r--r--src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/Sort.java76
-rw-r--r--src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/TermQuery.java349
15 files changed, 2411 insertions, 0 deletions
diff --git a/src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/AbstractAggregation.java b/src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/AbstractAggregation.java
new file mode 100644
index 0000000..6359227
--- /dev/null
+++ b/src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/AbstractAggregation.java
@@ -0,0 +1,82 @@
+/**
+ * ============LICENSE_START=======================================================
+ * Search Data Service
+ * ================================================================================
+ * Copyright © 2017 AT&T Intellectual Property.
+ * Copyright © 2017 Amdocs
+ * All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License ati
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ *
+ * ECOMP and OpenECOMP are trademarks
+ * and service marks of AT&T Intellectual Property.
+ */
+package org.openecomp.sa.searchdbabstraction.searchapi;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+
+/**
+ * This is the common parent from which all aggregation types inherit. It defines
+ * the common fields that all aggregations must include.
+ */
+public abstract class AbstractAggregation {
+
+ /**
+ * The name of the field to apply the aggregation against.
+ */
+ protected String field;
+
+ /**
+ * Optionally allows the number of buckets for the aggregation to be
+ * specified.
+ */
+ protected Integer size;
+
+ /**
+ * Optionally sets the minimum number of matches that must occur before
+ * a particular bucket is included in the aggregation result.
+ */
+ @JsonProperty("min-threshold")
+ protected Integer minThreshold;
+
+
+ public String getField() {
+ return field;
+ }
+
+ public void setField(String field) {
+ this.field = field;
+ }
+
+ public Integer getSize() {
+ return size;
+ }
+
+ public void setSize(Integer size) {
+ this.size = size;
+ }
+
+ public Integer getMinThreshold() {
+ return minThreshold;
+ }
+
+ public void setMinThreshold(Integer minThreshold) {
+ this.minThreshold = minThreshold;
+ }
+
+ public abstract String toElasticSearch();
+
+ public abstract String toString();
+}
diff --git a/src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/Aggregation.java b/src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/Aggregation.java
new file mode 100644
index 0000000..2cb42c6
--- /dev/null
+++ b/src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/Aggregation.java
@@ -0,0 +1,67 @@
+/**
+ * ============LICENSE_START=======================================================
+ * Search Data Service
+ * ================================================================================
+ * Copyright © 2017 AT&T Intellectual Property.
+ * Copyright © 2017 Amdocs
+ * All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License ati
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ *
+ * ECOMP and OpenECOMP are trademarks
+ * and service marks of AT&T Intellectual Property.
+ */
+package org.openecomp.sa.searchdbabstraction.searchapi;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class Aggregation {
+ private String name;
+
+ @JsonProperty("aggregation")
+ private AggregationStatement aggregation;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public AggregationStatement getStatement() {
+ return aggregation;
+ }
+
+ public void setStatement(AggregationStatement aggregation) {
+ this.aggregation = aggregation;
+ }
+
+ public String toElasticSearch() {
+ StringBuffer sb = new StringBuffer();
+
+ sb.append("\"");
+ sb.append(name);
+ sb.append("\": ");
+ sb.append(aggregation.toElasticSearch());
+
+ return sb.toString();
+ }
+
+ @Override
+ public String toString() {
+ return "{name: " + name + ", aggregation: " + aggregation.toString();
+ }
+
+}
diff --git a/src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/AggregationStatement.java b/src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/AggregationStatement.java
new file mode 100644
index 0000000..cf2314e
--- /dev/null
+++ b/src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/AggregationStatement.java
@@ -0,0 +1,178 @@
+/**
+ * ============LICENSE_START=======================================================
+ * Search Data Service
+ * ================================================================================
+ * Copyright © 2017 AT&T Intellectual Property.
+ * Copyright © 2017 Amdocs
+ * All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License ati
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ *
+ * ECOMP and OpenECOMP are trademarks
+ * and service marks of AT&T Intellectual Property.
+ */
+package org.openecomp.sa.searchdbabstraction.searchapi;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.Arrays;
+
+public class AggregationStatement {
+
+ @JsonProperty("group-by")
+ private GroupByAggregation groupBy;
+
+ @JsonProperty("date-range")
+ private DateRangeAggregation dateRange;
+
+ @JsonProperty("date-histogram")
+ private DateHistogramAggregation dateHist;
+
+ @JsonProperty("nested")
+ private Aggregation[] nested;
+
+ @JsonProperty("sub-aggregations")
+ private Aggregation[] subAggregations;
+
+ public GroupByAggregation getGroupBy() {
+ return groupBy;
+ }
+
+ public void setGroupBy(GroupByAggregation groupBy) {
+ this.groupBy = groupBy;
+ }
+
+ public DateRangeAggregation getDateRange() {
+ return dateRange;
+ }
+
+ public void setDateRange(DateRangeAggregation dateRange) {
+ this.dateRange = dateRange;
+ }
+
+ public DateHistogramAggregation getDateHist() {
+ return dateHist;
+ }
+
+ public void setDateHist(DateHistogramAggregation dateHist) {
+ this.dateHist = dateHist;
+ }
+
+ public Aggregation[] getNested() {
+ return nested;
+ }
+
+ public void setNested(Aggregation[] nested) {
+ this.nested = nested;
+ }
+
+ public Aggregation[] getSubAggregations() {
+ return subAggregations;
+ }
+
+ public void setSubAggregations(Aggregation[] subAggregations) {
+ this.subAggregations = subAggregations;
+ }
+
+ public String toElasticSearch() {
+ StringBuffer sb = new StringBuffer();
+
+ sb.append("{");
+
+ if (nested != null && nested.length > 0) {
+ sb.append("\"nested\": {\"path\": \"");
+ if (nested[0].getStatement() != null) {
+ sb.append(nested[0].getStatement().getNestedPath());
+ }
+ sb.append("\"}, \"aggs\": {");
+ for (int i = 0; i < nested.length; i++) {
+ if (i > 0) {
+ sb.append(",");
+ }
+ sb.append(nested[i].toElasticSearch());
+ }
+
+ sb.append("}");
+ } else {
+ if (groupBy != null) {
+ sb.append(groupBy.toElasticSearch());
+ } else if (dateRange != null) {
+ sb.append(dateRange.toElasticSearch());
+ } else if (dateHist != null) {
+ sb.append(dateHist.toElasticSearch());
+ }
+
+ if (subAggregations != null && subAggregations.length > 0) {
+ sb.append(", \"aggs\": {");
+ for (int i = 0; i < subAggregations.length; i++) {
+ if (i > 0) {
+ sb.append(",");
+ }
+ sb.append(subAggregations[i].toElasticSearch());
+ }
+ sb.append("}");
+ }
+ }
+
+ sb.append("}");
+
+ return sb.toString();
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+
+ if (nested != null) {
+ sb.append("{nested: ");
+ sb.append(Arrays.toString(nested));
+ } else if (groupBy != null) {
+ sb.append(groupBy.toString());
+ } else if (dateHist != null) {
+ sb.append(dateHist.toString());
+ } else if (dateRange != null) {
+ sb.append(dateRange.toString());
+ }
+
+ if (subAggregations != null) {
+ sb.append(", sub-aggregations: ");
+ sb.append(Arrays.toString(subAggregations));
+ }
+
+ sb.append("}");
+
+ return sb.toString();
+ }
+
+ public String getNestedPath() {
+ String path = null;
+ String fieldName = null;
+
+ if (groupBy != null) {
+ fieldName = groupBy.getField();
+ } else if (dateRange != null) {
+ fieldName = dateRange.getField();
+ } else if (dateHist != null) {
+ fieldName = dateHist.getField();
+ }
+
+ if (fieldName != null && fieldName.contains(".")) {
+ // we have nested field
+ path = fieldName.substring(0, fieldName.indexOf("."));
+ }
+
+ return path;
+ }
+
+}
diff --git a/src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/DateHistogramAggregation.java b/src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/DateHistogramAggregation.java
new file mode 100644
index 0000000..bc62a95
--- /dev/null
+++ b/src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/DateHistogramAggregation.java
@@ -0,0 +1,119 @@
+/**
+ * ============LICENSE_START=======================================================
+ * Search Data Service
+ * ================================================================================
+ * Copyright © 2017 AT&T Intellectual Property.
+ * Copyright © 2017 Amdocs
+ * All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License ati
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ *
+ * ECOMP and OpenECOMP are trademarks
+ * and service marks of AT&T Intellectual Property.
+ */
+package org.openecomp.sa.searchdbabstraction.searchapi;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * An example of elasticsearch date_histogram aggregation:
+ *
+ * <p><pre>
+ * {
+ * "aggs": {
+ * "my_group": {
+ * "date_histogram" : {
+ * "field" : "date",
+ * "interval" : "month"
+ * }
+ * }
+ * }
+ * }
+ * </pre>
+ */
+
+public class DateHistogramAggregation extends AbstractAggregation {
+
+ private String interval;
+
+ private String format;
+
+ @JsonProperty("time-zone")
+ private String timeZone;
+
+
+ public String getInterval() {
+ return interval;
+ }
+
+ public void setInterval(String interval) {
+ this.interval = interval;
+ }
+
+ public String getTimeZone() {
+ return timeZone;
+ }
+
+ public String getFormat() {
+ return format;
+ }
+
+ public void setFormat(String format) {
+ this.format = format;
+ }
+
+ public void setTimeZone(String timeZone) {
+ this.timeZone = timeZone;
+ }
+
+ @Override
+ public String toElasticSearch() {
+ StringBuilder sb = new StringBuilder();
+
+ sb.append("\"date_histogram\": {\"field\": \"");
+ sb.append(field);
+ sb.append("\"");
+ if (interval != null) {
+ sb.append(", \"interval\": \"");
+ sb.append(interval);
+ sb.append("\"");
+ }
+ if (format != null) {
+ sb.append(", \"format\": \"");
+ sb.append(format);
+ sb.append("\"");
+ }
+ if (timeZone != null) {
+ sb.append(", \"time_zone\": \"");
+ sb.append(timeZone);
+ sb.append("\"");
+ }
+ if (size != null) {
+ sb.append(", \"size\": ");
+ sb.append(size);
+ }
+ if (minThreshold != null) {
+ sb.append(", \"min_doc_count\": ").append(minThreshold);
+ }
+ sb.append("}");
+
+ return sb.toString();
+ }
+
+ @Override
+ public String toString() {
+ return "DateHistogramAggregation: [field=" + field + ", interval=" + interval + ", format="
+ + format + ", timeZone=" + timeZone + ", size=" + size + " minThreshold=" + minThreshold;
+ }
+}
diff --git a/src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/DateRange.java b/src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/DateRange.java
new file mode 100644
index 0000000..1bd0240
--- /dev/null
+++ b/src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/DateRange.java
@@ -0,0 +1,115 @@
+/**
+ * ============LICENSE_START=======================================================
+ * Search Data Service
+ * ================================================================================
+ * Copyright © 2017 AT&T Intellectual Property.
+ * Copyright © 2017 Amdocs
+ * All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License ati
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ *
+ * ECOMP and OpenECOMP are trademarks
+ * and service marks of AT&T Intellectual Property.
+ */
+package org.openecomp.sa.searchdbabstraction.searchapi;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * This class represents the ranges specification in an date_range statement.
+ * <p>
+ * The expected JSON structure for a ranges is as follows:
+ * <p>
+ * <pre>
+ * {
+ * "from": <from-date>
+ * }
+ * </pre>
+ * <p>
+ * or
+ * <p>
+ * <pre>
+ * {
+ * "to": <to-date>
+ * }
+ * </pre>
+ * <p>
+ * or
+ * <p>
+ * <pre>
+ * {
+ * "from": <from-date>,
+ * "to": <to-date>
+ * }
+ * </pre>
+ *
+ * @author sye
+ */
+public class DateRange {
+
+ @JsonProperty("from")
+ private String fromDate;
+
+ @JsonProperty("to")
+ private String toDate;
+
+ public String getFromDate() {
+ return fromDate;
+ }
+
+ public void setFromDate(String fromDate) {
+ this.fromDate = fromDate;
+ }
+
+ public String getToDate() {
+ return toDate;
+ }
+
+ public void setToDate(String toDate) {
+ this.toDate = toDate;
+ }
+
+ public String toElasticSearch() {
+ StringBuilder sb = new StringBuilder();
+
+ sb.append("{");
+
+ if (fromDate != null) {
+ sb.append("\"from\": \"");
+ sb.append(fromDate.toString());
+ sb.append("\"");
+ }
+
+ if (toDate != null) {
+ if (fromDate != null) {
+ sb.append(", \"to\": \"");
+ sb.append(toDate.toString());
+ sb.append("\"");
+ } else {
+ sb.append("\"to\": \"");
+ sb.append(toDate.toString());
+ sb.append("\"");
+ }
+ }
+
+ sb.append("}");
+
+ return sb.toString();
+ }
+
+ public String toString() {
+ return "{from: " + fromDate + ", to: " + toDate + "}";
+ }
+
+}
diff --git a/src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/DateRangeAggregation.java b/src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/DateRangeAggregation.java
new file mode 100644
index 0000000..f938e68
--- /dev/null
+++ b/src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/DateRangeAggregation.java
@@ -0,0 +1,133 @@
+/**
+ * ============LICENSE_START=======================================================
+ * Search Data Service
+ * ================================================================================
+ * Copyright © 2017 AT&T Intellectual Property.
+ * Copyright © 2017 Amdocs
+ * All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License ati
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ *
+ * ECOMP and OpenECOMP are trademarks
+ * and service marks of AT&T Intellectual Property.
+ */
+package org.openecomp.sa.searchdbabstraction.searchapi;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * An example of a date_range aggregation:
+ *
+ * <p><pre>
+ * {
+ * "aggs": {
+ * "range": {
+ * "date_range": {
+ * "field": "date",
+ * "format": "MM-yyy",
+ * "ranges": [
+ * { "to": "now-10M/M" },
+ * { "from": "now-10M/M" }
+ * ]
+ * }
+ * }
+ * }
+ * }
+ * </pre>
+ *
+ * @author sye
+ */
+public class DateRangeAggregation extends AbstractAggregation {
+
+
+ private String format;
+
+ @JsonProperty("ranges")
+ private DateRange[] dateRanges;
+
+
+ public String getFormat() {
+ return format;
+ }
+
+ public void setFormat(String format) {
+ this.format = format;
+ }
+
+ public DateRange[] getDateRanges() {
+ return dateRanges;
+ }
+
+ public void setDateRanges(DateRange[] dateRanges) {
+ this.dateRanges = dateRanges;
+ }
+
+ @Override
+ public String toElasticSearch() {
+ StringBuilder sb = new StringBuilder();
+
+ sb.append("\"date_range\": {\"field\": \"");
+ sb.append(field);
+ sb.append("\"");
+
+ if (format != null) {
+ sb.append(", \"format\": \"");
+ sb.append(format);
+ sb.append("\"");
+ }
+
+ if (dateRanges != null && dateRanges.length > 0) {
+ sb.append(", \"ranges\": [");
+
+ for (int i = 0; i < dateRanges.length; i++) {
+ if (i > 0) {
+ sb.append(",");
+ }
+ sb.append(dateRanges[i].toElasticSearch());
+ }
+
+ sb.append("]");
+ }
+
+ if (size != null) {
+ sb.append(", \"size\": ");
+ sb.append(size);
+ }
+
+ if (minThreshold != null) {
+ sb.append(", \"min_doc_count\": ").append(minThreshold);
+ }
+
+ sb.append("}");
+
+ return sb.toString();
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append("date-range: {field: " + field + ", format: " + format + ", size: " + size
+ + ", minThreshold: " + minThreshold + "ranges: [");
+ for (int i = 0; i < dateRanges.length; i++) {
+ if (i > 0) {
+ sb.append(",");
+ }
+ sb.append(dateRanges[i].toString());
+ }
+ sb.append("]");
+
+ return sb.toString();
+ }
+
+}
diff --git a/src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/Filter.java b/src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/Filter.java
new file mode 100644
index 0000000..13519ae
--- /dev/null
+++ b/src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/Filter.java
@@ -0,0 +1,190 @@
+/**
+ * ============LICENSE_START=======================================================
+ * Search Data Service
+ * ================================================================================
+ * Copyright © 2017 AT&T Intellectual Property.
+ * Copyright © 2017 Amdocs
+ * All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License ati
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ *
+ * ECOMP and OpenECOMP are trademarks
+ * and service marks of AT&T Intellectual Property.
+ */
+package org.openecomp.sa.searchdbabstraction.searchapi;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class represents the filter stanza in a search statement.
+ *
+ * <p>The expected JSON structure for a filter stanza is as follows:
+ * <pre>
+ * {
+ * "filter": {
+ * "all": [ {query structure}, {query structure}, ... {query structure} ],
+ * "any": [ {query structure}, {query structure}, ... {query structure} ]
+ * }
+ * }
+ * </pre>
+ */
+public class Filter {
+
+ /**
+ * All queries in this list must evaluate to true for the filter to pass.
+ */
+ private QueryStatement[] all;
+
+ /**
+ * Any one of the queries in this list must evaluate to true for the
+ * filter to pass.
+ */
+ private QueryStatement[] any;
+
+
+ public QueryStatement[] getAll() {
+ return all;
+ }
+
+ public void setAll(QueryStatement[] all) {
+ this.all = all;
+ }
+
+ public QueryStatement[] getAny() {
+ return any;
+ }
+
+ public void setAny(QueryStatement[] any) {
+ this.any = any;
+ }
+
+ /**
+ * This method returns a string which represents this filter in syntax
+ * that is understandable by ElasticSearch and is suitable for inclusion
+ * in an ElasticSearch query string.
+ *
+ * @return - ElasticSearch syntax string.
+ */
+ public String toElasticSearch() {
+
+ StringBuilder sb = new StringBuilder();
+
+ List<QueryStatement> notMatchQueries = new ArrayList<QueryStatement>();
+ sb.append("{");
+ sb.append("\"bool\": {");
+
+ // Add the queries from our 'all' list.
+ int matchQueriesCount = 0;
+ int notMatchQueriesCount = 0;
+ if (all != null) {
+ sb.append("\"must\": [");
+
+ for (QueryStatement query : all) {
+ if (matchQueriesCount > 0) {
+ sb.append(", ");
+ }
+
+ if (query.isNotMatch()) {
+ notMatchQueries.add(query);
+ } else {
+ sb.append(query.toElasticSearch());
+ matchQueriesCount++;
+ }
+ }
+ sb.append("],");
+
+
+ sb.append("\"must_not\": [");
+ for (QueryStatement query : notMatchQueries) {
+ if (notMatchQueriesCount > 0) {
+ sb.append(", ");
+ }
+ sb.append(query.toElasticSearch());
+ notMatchQueriesCount++;
+ }
+ sb.append("]");
+ }
+
+ // Add the queries from our 'any' list.
+ notMatchQueries.clear();
+ if (any != null) {
+ if (all != null) {
+ sb.append(",");
+ }
+ sb.append("\"should\": [");
+
+ matchQueriesCount = 0;
+ for (QueryStatement query : any) {
+ //if(!firstQuery.compareAndSet(true, false)) {
+ if (matchQueriesCount > 0) {
+ sb.append(", ");
+ }
+
+ if (query.isNotMatch()) {
+ notMatchQueries.add(query);
+ } else {
+ sb.append(query.toElasticSearch());
+ matchQueriesCount++;
+ }
+ }
+ sb.append("],");
+
+ //firstQuery.set(true);
+ notMatchQueriesCount = 0;
+ sb.append("\"must_not\": [");
+ for (QueryStatement query : notMatchQueries) {
+ //if(!firstQuery.compareAndSet(true, false)) {
+ if (notMatchQueriesCount > 0) {
+ sb.append(", ");
+ }
+ sb.append(query.toElasticSearch());
+ notMatchQueriesCount++;
+ }
+ sb.append("]");
+ }
+ sb.append("}");
+ sb.append("}");
+
+ return sb.toString();
+ }
+
+ @Override
+ public String toString() {
+
+ StringBuilder sb = new StringBuilder();
+
+ sb.append("{");
+
+ sb.append("all: [");
+ if (all != null) {
+ for (QueryStatement query : all) {
+ sb.append(query.toString());
+ }
+ }
+ sb.append("], ");
+
+ sb.append("any: [");
+ if (any != null) {
+ for (QueryStatement query : any) {
+ sb.append(query.toString());
+ }
+ }
+ sb.append("] ");
+
+ sb.append("}");
+
+ return sb.toString();
+ }
+}
diff --git a/src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/GroupByAggregation.java b/src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/GroupByAggregation.java
new file mode 100644
index 0000000..3225a93
--- /dev/null
+++ b/src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/GroupByAggregation.java
@@ -0,0 +1,73 @@
+/**
+ * ============LICENSE_START=======================================================
+ * Search Data Service
+ * ================================================================================
+ * Copyright © 2017 AT&T Intellectual Property.
+ * Copyright © 2017 Amdocs
+ * All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License ati
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ *
+ * ECOMP and OpenECOMP are trademarks
+ * and service marks of AT&T Intellectual Property.
+ */
+package org.openecomp.sa.searchdbabstraction.searchapi;
+
+/**
+ * An example of a date_range aggregation:
+ *
+ * <p><pre>
+ * {
+ * "aggs": {
+ * "my_group": {
+ * "term": {
+ * "field": "group"
+ * }
+ * }
+ * }
+ * }
+ * </pre>
+ *
+ * @author sye
+ */
+public class GroupByAggregation extends AbstractAggregation {
+
+ @Override
+ public String toElasticSearch() {
+ StringBuilder sb = new StringBuilder();
+
+ sb.append("\"terms\": {\"field\": \"");
+ sb.append(field);
+ sb.append("\"");
+ if (size != null) {
+ sb.append(", \"size\": ");
+ sb.append(size);
+ }
+
+ if (minThreshold != null) {
+ sb.append(", \"min_doc_count\": ").append(minThreshold);
+ }
+
+ sb.append("}");
+
+ return sb.toString();
+ }
+
+ @Override
+ public String toString() {
+ return "{group-by: {field: " + field + ", size: " + size
+ + " minThreshold: " + minThreshold + "}}";
+ }
+
+}
diff --git a/src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/ParsedQuery.java b/src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/ParsedQuery.java
new file mode 100644
index 0000000..8b07d50
--- /dev/null
+++ b/src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/ParsedQuery.java
@@ -0,0 +1,120 @@
+/**
+ * ============LICENSE_START=======================================================
+ * Search Data Service
+ * ================================================================================
+ * Copyright © 2017 AT&T Intellectual Property.
+ * Copyright © 2017 Amdocs
+ * All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License ati
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ *
+ * ECOMP and OpenECOMP are trademarks
+ * and service marks of AT&T Intellectual Property.
+ */
+package org.openecomp.sa.searchdbabstraction.searchapi;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * This class represents a simple parsed query statement.
+ *
+ * <p>A 'parsed query' specifies a document field to inspect and a query
+ * string which will be parsed by the document store to generate the
+ * exact query to be performed.
+ *
+ * <p>The query string will be tokenized into 'terms' and 'operators' where:
+ *
+ * <p>Terms may be any of the following:
+ * <ul>
+ * <li> single words </li>
+ * <li> exact phrases (denoted by surrounding the phrase with '"' characters) </li>
+ * <li> regular expressions (denoted by surrounding the phrase with '/' characters) </li>
+ * </ul>
+ *
+ * <p>Operators may be any of the following:
+ * <ul>
+ * <li> + -- The term to the right of the operator MUST be present to produce a match. </li>
+ * <li> - -- The term to the right of the operator MUST NOT be present to produce a match. </li>
+ * <li> AND -- Both the terms to the left and right of the operator MUST be present to produce a match. </li>
+ * <li> OR -- Either the term to the left or right of the operator MUST be present to produce a match. </li>
+ * <li> NOT -- The term to the right of the operator MUST NOT be present to produce a match. </li>
+ * </ul>
+ *
+ * <p>The expected JSON structure for a parsed query is as follows:
+ * <pre>
+ * {
+ * "parsed-query": {
+ * "field": "fieldname",
+ * "query-string": "string"
+ * }
+ * }
+ * </pre>
+ */
+public class ParsedQuery {
+
+ /**
+ * The name of the field which the query is to be applied to.
+ */
+ private String field;
+
+ /**
+ * The string to be parsed to generate the full query.
+ */
+ @JsonProperty("query-string")
+ private String queryString;
+
+
+ public String getField() {
+ return field;
+ }
+
+ public void setField(String field) {
+ this.field = field;
+ }
+
+ public String getQueryString() {
+ return queryString;
+ }
+
+ public void setQueryString(String queryString) {
+ this.queryString = queryString;
+ }
+
+
+ /**
+ * This method returns a string which represents this query in syntax
+ * that is understandable by ElasticSearch and is suitable for inclusion
+ * in an ElasticSearch query string.
+ *
+ * @return - ElasticSearch syntax string.
+ */
+ public String toElasticSearch() {
+
+ StringBuilder sb = new StringBuilder();
+
+ sb.append("{");
+ sb.append("\"query_string\": {");
+ sb.append("\"default_field\": \"").append(field).append("\", ");
+ sb.append("\"query\": \"").append(queryString).append("\"");
+ sb.append("}");
+ sb.append("}");
+
+ return sb.toString();
+ }
+
+ @Override
+ public String toString() {
+ return "{field:" + field + ", query-string: '" + queryString + "'}";
+ }
+}
diff --git a/src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/Query.java b/src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/Query.java
new file mode 100644
index 0000000..1b9c1ed
--- /dev/null
+++ b/src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/Query.java
@@ -0,0 +1,94 @@
+/**
+ * ============LICENSE_START=======================================================
+ * Search Data Service
+ * ================================================================================
+ * Copyright © 2017 AT&T Intellectual Property.
+ * Copyright © 2017 Amdocs
+ * All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License ati
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ *
+ * ECOMP and OpenECOMP are trademarks
+ * and service marks of AT&T Intellectual Property.
+ */
+package org.openecomp.sa.searchdbabstraction.searchapi;
+
+public class Query {
+
+ private QueryStatement may;
+ private QueryStatement must;
+
+ public QueryStatement getMay() {
+ return may;
+ }
+
+ public void setMay(QueryStatement may) {
+ this.may = may;
+ }
+
+ public QueryStatement getMust() {
+ return must;
+ }
+
+ public void setMust(QueryStatement must) {
+ this.must = must;
+ }
+
+ public QueryStatement getQueryStatement() {
+ if (isMust()) {
+ return must;
+ } else if (isMay()) {
+ return may;
+ } else {
+ return null;
+ }
+ }
+
+ public boolean isMust() {
+ return must != null;
+ }
+
+ public boolean isMay() {
+ return may != null;
+ }
+
+ public String toElasticSearch() {
+
+ if (isMust()) {
+ return must.toElasticSearch();
+ } else if (isMay()) {
+ return may.toElasticSearch();
+ } else {
+ return ""; // throw an exception?
+ }
+ }
+
+ @Override
+ public String toString() {
+
+ StringBuilder sb = new StringBuilder();
+
+ sb.append("Query:[");
+ if (isMust()) {
+ sb.append("must: ").append(must.toString());
+ } else if (isMay()) {
+ sb.append("may: ").append(may.toString());
+ } else {
+ sb.append("INVALID");
+ }
+ sb.append("]");
+
+ return sb.toString();
+ }
+}
diff --git a/src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/QueryStatement.java b/src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/QueryStatement.java
new file mode 100644
index 0000000..f5fc367
--- /dev/null
+++ b/src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/QueryStatement.java
@@ -0,0 +1,142 @@
+/**
+ * ============LICENSE_START=======================================================
+ * Search Data Service
+ * ================================================================================
+ * Copyright © 2017 AT&T Intellectual Property.
+ * Copyright © 2017 Amdocs
+ * All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License ati
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ *
+ * ECOMP and OpenECOMP are trademarks
+ * and service marks of AT&T Intellectual Property.
+ */
+package org.openecomp.sa.searchdbabstraction.searchapi;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class QueryStatement {
+
+ private TermQuery match;
+
+ @JsonProperty("not-match")
+ private TermQuery notMatch;
+
+ @JsonProperty("parsed-query")
+ private ParsedQuery parsedQuery;
+
+ private RangeQuery range;
+
+ public TermQuery getMatch() {
+ return match;
+ }
+
+ public void setMatch(TermQuery match) {
+ this.match = match;
+ }
+
+ public TermQuery getNotMatch() {
+ return notMatch;
+ }
+
+ public void setNotMatch(TermQuery notMatch) {
+ this.notMatch = notMatch;
+ }
+
+ public ParsedQuery getParsedQuery() {
+ return parsedQuery;
+ }
+
+ public void setParsedQuery(ParsedQuery parsedQuery) {
+ this.parsedQuery = parsedQuery;
+ }
+
+ public RangeQuery getRange() {
+ return range;
+ }
+
+ public void setRange(RangeQuery range) {
+ this.range = range;
+ }
+
+ public boolean isNotMatch() {
+ return (notMatch != null);
+ }
+
+ public String toElasticSearch() {
+
+ if (match != null) {
+ return match.toElasticSearch();
+
+ } else if (notMatch != null) {
+ return notMatch.toElasticSearch();
+
+ } else if (parsedQuery != null) {
+
+ // We need some special wrapping if this query is against a nested field.
+ if (fieldIsNested(parsedQuery.getField())) {
+ return "{\"nested\": { \"path\": \"" + pathForNestedField(parsedQuery.getField())
+ + "\", \"query\": " + parsedQuery.toElasticSearch() + "}}";
+ } else {
+ return parsedQuery.toElasticSearch();
+ }
+
+ } else if (range != null) {
+
+ // We need some special wrapping if this query is against a nested field.
+ if (fieldIsNested(range.getField())) {
+ return "{\"nested\": { \"path\": \"" + pathForNestedField(range.getField())
+ + "\", \"query\": " + range.toElasticSearch() + "}}";
+ } else {
+ return range.toElasticSearch();
+ }
+
+ } else {
+ // throw an exception?
+ return null;
+ }
+ }
+
+ private boolean fieldIsNested(String field) {
+ return field.contains(".");
+ }
+
+ private String pathForNestedField(String field) {
+ int index = field.lastIndexOf('.');
+ return field.substring(0, index);
+ }
+
+ @Override
+ public String toString() {
+
+ StringBuilder sb = new StringBuilder();
+
+ sb.append("{");
+
+ if (match != null) {
+ sb.append("TERM QUERY: { match: {").append(match.toString()).append("}}");
+ } else if (notMatch != null) {
+ sb.append("TERM QUERY: { not-match: {").append(match.toString()).append("}}");
+ } else if (parsedQuery != null) {
+ sb.append("PARSED QUERY: { ").append(parsedQuery.toString()).append("}");
+ } else if (range != null) {
+ sb.append("RANGE QUERY: { ").append(range.toString()).append("}");
+ } else {
+ sb.append("UNDEFINED");
+ }
+
+ sb.append("}");
+ return sb.toString();
+ }
+}
diff --git a/src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/RangeQuery.java b/src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/RangeQuery.java
new file mode 100644
index 0000000..fcb0212
--- /dev/null
+++ b/src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/RangeQuery.java
@@ -0,0 +1,348 @@
+/**
+ * ============LICENSE_START=======================================================
+ * Search Data Service
+ * ================================================================================
+ * Copyright © 2017 AT&T Intellectual Property.
+ * Copyright © 2017 Amdocs
+ * All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License ati
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ *
+ * ECOMP and OpenECOMP are trademarks
+ * and service marks of AT&T Intellectual Property.
+ */
+package org.openecomp.sa.searchdbabstraction.searchapi;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * This class represents a simple range query.
+ *
+ * <p>A range query is composed of one or more operator/value pairs which define
+ * the upper and lower bounds of the range, and a field to apply the query to.
+ *
+ * <p>Operators may be one of the following:
+ * <ul>
+ * <li>gt - Greater than. </li>
+ * <li>gte - Greater than or equal to. </li>
+ * <li>lt - Less than. </li>
+ * <li>lte - Less than or equal to. </li>
+ * </ul>
+ * Values may be either numeric values (Integer or Double) or Strings representing
+ * dates.
+ *
+ * <p>The following examples illustrate a couple of variants of the range query:
+ *
+ * <p><pre>
+ * // A simple numeric range query:
+ * {
+ * "range": {
+ * "field": "fieldname",
+ * "gte": 5,
+ * "lte": 10
+ * }
+ * }
+ *
+ * // A simple date range query:
+ * {
+ * "range": {
+ * "field": "fieldname",
+ * "gt": "2016-10-06T00:00:00.558+03:00",
+ * "lt": "2016-10-06T23:59:59.558+03:00"
+ * }
+ * }
+ * </pre>
+ */
+public class RangeQuery {
+
+ /**
+ * The name of the field to apply the range query against.
+ */
+ private String field;
+
+ /**
+ * The value of the field must be greater than this value to be a match.<br>
+ * NOTE: Only one of 'gt' or 'gte' should be set on any single {@link RangeQuery}
+ * instance.
+ */
+ private Object gt;
+
+ /**
+ * The value of the field must be greater than or equal to this value to be a match.<br>
+ * NOTE: Only one of 'gt' or 'gte' should be set on any single {@link RangeQuery}
+ * instance.
+ */
+ private Object gte;
+
+ /**
+ * The value of the field must be less than this value to be a match.<br>
+ * NOTE: Only one of 'lt' or 'lte' should be set on any single {@link RangeQuery}
+ * instance.
+ */
+ private Object lt;
+
+ /**
+ * The value of the field must be less than or equal to than this value to be a match.<br>
+ * NOTE: Only one of 'lt' or 'lte' should be set on any single {@link RangeQuery}
+ * instance.
+ */
+ private Object lte;
+
+ private String format;
+
+ @JsonProperty("time-zone")
+ private String timeZone;
+
+ public String getField() {
+ return field;
+ }
+
+ public void setField(String field) {
+ this.field = field;
+ }
+
+ public Object getGt() {
+ return gt;
+ }
+
+ public void setGt(Object gt) {
+
+ // It does not make sense to assign a value to both the 'greater than'
+ // and 'greater than or equal' operations, so make sure we are not
+ // trying to do that.
+ if (gte == null) {
+
+ // Make sure that we are not trying to mix both numeric and date
+ // type values in the same queries.
+ if (((lt != null) && !typesMatch(gt, lt))
+ || ((lte != null) && !typesMatch(gt, lte))) {
+ throw new IllegalArgumentException("Cannot mix date and numeric values in the same ranged query");
+ }
+
+ // If we made it here, then we're all good. Store the value.
+ this.gt = gt;
+ } else {
+ throw new IllegalArgumentException("Cannot assign both 'gt' and 'gte' fields in the same ranged query");
+ }
+ }
+
+
+ public Object getGte() {
+ return gte;
+ }
+
+ public void setGte(Object gte) {
+
+ // It does not make sense to assign a value to both the 'greater than'
+ // and 'greater than or equal' operations, so make sure we are not
+ // trying to do that.
+ if (gt == null) {
+
+ // Make sure that we are not trying to mix both numeric and date
+ // type values in the same queries.
+ if (((lt != null) && !typesMatch(gte, lt))
+ || ((lte != null) && !typesMatch(gte, lte))) {
+ throw new IllegalArgumentException("Cannot mix date and numeric values in the same ranged query");
+ }
+
+ // If we made it here, then we're all good. Store the value.
+ this.gte = gte;
+
+ } else {
+ throw new IllegalArgumentException("Cannot assign both 'gt' and 'gte' fields in the same ranged query");
+ }
+ }
+
+ public Object getLt() {
+ return lt;
+ }
+
+ public void setLt(Object lt) {
+
+ // It does not make sense to assign a value to both the 'less than'
+ // and 'less than or equal' operations, so make sure we are not
+ // trying to do that.
+ if (lte == null) {
+
+ // Make sure that we are not trying to mix both numeric and date
+ // type values in the same queries.
+ if (((gt != null) && !typesMatch(lt, gt))
+ || ((gte != null) && !typesMatch(lt, gte))) {
+ throw new IllegalArgumentException("Cannot mix date and numeric values in the same ranged query");
+ }
+
+ // If we made it here, then we're all good. Store the value.
+
+ this.lt = lt;
+ } else {
+ throw new IllegalArgumentException("Cannot assign both 'lt' and 'lte' fields in the same ranged query");
+ }
+ }
+
+ public Object getLte() {
+ return lte;
+ }
+
+ public void setLte(Object lte) {
+
+ // It does not make sense to assign a value to both the 'greater than'
+ // and 'greater than or equal' operations, so make sure we are not
+ // trying to do that.
+ if (lt == null) {
+
+ // Make sure that we are not trying to mix both numeric and date
+ // type values in the same queries.
+ if (((gt != null) && !typesMatch(lte, gt))
+ || ((gte != null) && !typesMatch(lte, gte))) {
+ throw new IllegalArgumentException("Cannot mix date and numeric values in the same ranged query");
+ }
+
+ // If we made it here, then we're all good. Store the value.
+
+ this.lte = lte;
+ } else {
+ throw new IllegalArgumentException("Cannot assign both 'lt' and 'lte' fields in the same ranged query");
+ }
+ }
+
+ public String getFormat() {
+ return format;
+ }
+
+ public void setFormat(String format) {
+ this.format = format;
+ }
+
+ public String getTimeZone() {
+ return timeZone;
+ }
+
+ public void setTimeZone(String timeZone) {
+ this.timeZone = timeZone;
+ }
+
+ /**
+ * This convenience method determines whether or not the supplied
+ * value needs to be enclosed in '"' characters when generating
+ * ElasticSearch compatible syntax.
+ *
+ * @param val - The value to check.
+ * @return - A string representation of the value for inclusion
+ * in an ElasticSearch syntax string.
+ */
+ private String formatStringOrNumericVal(Object val) {
+
+ if (val instanceof String) {
+ return "\"" + val.toString() + "\"";
+ } else {
+ return val.toString();
+ }
+ }
+
+
+ /**
+ * This convenience method verifies that the supplied objects are
+ * of classes considered to be compatible for a ranged query.
+ *
+ * @param value1 - The first value to check.
+ * @param value2 - The second value to check.
+ * @return - True if the two objects are compatible for inclusion in the
+ * same ranged query, False, otherwise.
+ */
+ boolean typesMatch(Object value1, Object value2) {
+
+ return ((value1 instanceof String) && (value2 instanceof String))
+ || (!(value1 instanceof String) && !(value2 instanceof String));
+ }
+
+
+ /**
+ * This method returns a string which represents this query in syntax
+ * that is understandable by ElasticSearch and is suitable for inclusion
+ * in an ElasticSearch query string.
+ *
+ * @return - ElasticSearch syntax string.
+ */
+ public String toElasticSearch() {
+
+ StringBuilder sb = new StringBuilder();
+
+ sb.append("{");
+ sb.append("\"range\": {");
+ sb.append("\"").append(field).append("\": {");
+
+ // We may have one or zero of 'greater than' or 'greater
+ // than or equal'
+ boolean needComma = false;
+ if (gte != null) {
+ sb.append("\"gte\": ").append(formatStringOrNumericVal(gte));
+ needComma = true;
+ } else if (gt != null) {
+ sb.append("\"gt\": ").append(formatStringOrNumericVal(gt));
+ needComma = true;
+ }
+
+ // We may have one or zero of 'less than' or 'less
+ // than or equal'
+ if (lte != null) {
+ if (needComma) {
+ sb.append(", ");
+ }
+ sb.append("\"lte\": ").append(formatStringOrNumericVal(lte));
+ } else if (lt != null) {
+ if (needComma) {
+ sb.append(", ");
+ }
+ sb.append("\"lt\": ").append(formatStringOrNumericVal(lt));
+ }
+
+ // Append the format specifier if one was provided.
+ if (format != null) {
+ sb.append(", \"format\": \"").append(format).append("\"");
+ }
+
+ // Append the time zone specifier if one was provided.
+ if (timeZone != null) {
+ sb.append(", \"time_zone\": \"").append(timeZone).append("\"");
+ }
+
+ sb.append("}");
+ sb.append("}");
+ sb.append("}");
+
+ return sb.toString();
+ }
+
+ @Override
+ public String toString() {
+
+ String str = "{ field: " + field + ", ";
+
+ if (gt != null) {
+ str += "gt: " + gt;
+ } else if (gte != null) {
+ str += "gte: " + gte;
+ }
+
+ if (lt != null) {
+ str += (((gt != null) || (gte != null)) ? ", " : "") + "lt: " + lt;
+ } else if (lte != null) {
+ str += (((gt != null) || (gte != null)) ? ", " : "") + "lte: " + lte;
+ }
+
+ str += "}";
+
+ return str;
+ }
+}
diff --git a/src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/SearchStatement.java b/src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/SearchStatement.java
new file mode 100644
index 0000000..e111163
--- /dev/null
+++ b/src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/SearchStatement.java
@@ -0,0 +1,325 @@
+/**
+ * ============LICENSE_START=======================================================
+ * Search Data Service
+ * ================================================================================
+ * Copyright © 2017 AT&T Intellectual Property.
+ * Copyright © 2017 Amdocs
+ * All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License ati
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ *
+ * ECOMP and OpenECOMP are trademarks
+ * and service marks of AT&T Intellectual Property.
+ */
+package org.openecomp.sa.searchdbabstraction.searchapi;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.radeox.util.logging.Logger;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * This class represents the structure of a search statement.
+ *
+ * <p>The expected JSON structure to represent a search statement is as follows:
+ *
+ * <p><pre>
+ * {
+ * "results-start": int, - Optional: index of starting point in result set.
+ * "results-size": int, - Optional: maximum number of documents to include in result set.
+ *
+ * "filter": {
+ * { filter structure - see {@link Filter} }
+ * },
+ *
+ * "queries": [
+ * { query structure - see {@link QueryStatement} },
+ * { query structure - see {@link QueryStatement} },
+ * .
+ * .
+ * { query structure - see {@link QueryStatement} },
+ * ],
+ *
+ * "aggregations": [
+ * { aggregation structure - see {@link AggregationStatement} },
+ * { aggregation structure - see {@link AggregationStatement} },
+ * .
+ * .
+ * { aggregation structure - see {@link AggregationStatement} },
+ * ]
+ * }
+ * </pre>
+ */
+public class SearchStatement {
+
+ /**
+ * Defines the filters that should be applied before running the
+ * actual queries. This is optional.
+ */
+ private Filter filter;
+
+ /**
+ * The list of queries to be applied to the document store.
+ */
+ private Query[] queries;
+
+ /**
+ * The list of aggregations to be applied to the search
+ */
+ private Aggregation[] aggregations;
+
+ /**
+ * Defines the sort criteria to apply to the query result set.
+ * This is optional.
+ */
+ private Sort sort;
+
+ @JsonProperty("results-start")
+ private Integer resultsStart;
+
+ @JsonProperty("results-size")
+ private Integer size;
+
+ public Filter getFilter() {
+ return filter;
+ }
+
+ public void setFilter(Filter filter) {
+ this.filter = filter;
+ }
+
+ public Query[] getQueries() {
+ return queries;
+ }
+
+ public void setQueries(Query[] queries) {
+ this.queries = queries;
+ }
+
+ public Sort getSort() {
+ return sort;
+ }
+
+ public void setSort(Sort sort) {
+ this.sort = sort;
+ }
+
+ public boolean isFiltered() {
+ return filter != null;
+ }
+
+ public Aggregation[] getAggregations() {
+ return aggregations;
+ }
+
+ public void setAggregations(Aggregation[] aggregations) {
+ this.aggregations = aggregations;
+ }
+
+ public boolean hasAggregations() {
+ return aggregations != null && aggregations.length > 0;
+ }
+
+ public Integer getFrom() {
+ return resultsStart;
+ }
+
+ public void setFrom(Integer from) {
+ this.resultsStart = from;
+ }
+
+ public Integer getSize() {
+ return size;
+ }
+
+ public void setSize(Integer size) {
+ this.size = size;
+ }
+
+ /**
+ * This method returns a string which represents this statement in syntax
+ * that is understandable by ElasticSearch and is suitable for inclusion
+ * in an ElasticSearch query string.
+ *
+ * @return - ElasticSearch syntax string.
+ */
+ public String toElasticSearch() {
+
+ StringBuilder sb = new StringBuilder();
+ List<QueryStatement> notMatchQueries = new ArrayList<QueryStatement>();
+ List<QueryStatement> mustQueries = new ArrayList<QueryStatement>();
+ List<QueryStatement> shouldQueries = new ArrayList<QueryStatement>();
+
+ createQueryLists(queries, mustQueries, shouldQueries, notMatchQueries);
+
+ sb.append("{");
+
+ sb.append("\"version\": true,");
+
+ // If the client has specified an index into the results for the first
+ // document in the result set then include that in the ElasticSearch
+ // query.
+ if (resultsStart != null) {
+ sb.append("\"from\": ").append(resultsStart).append(", ");
+ }
+
+ // If the client has specified a maximum number of documents to be returned
+ // in the result set then include that in the ElasticSearch query.
+ if (size != null) {
+ sb.append("\"size\": ").append(size).append(", ");
+ }
+
+ sb.append("\"query\": {");
+ sb.append("\"bool\": {");
+
+ sb.append("\"must\": [");
+ AtomicBoolean firstQuery = new AtomicBoolean(true);
+ for (QueryStatement query : mustQueries) {
+
+ if (!firstQuery.compareAndSet(true, false)) {
+ sb.append(", ");
+ }
+
+ sb.append(query.toElasticSearch());
+ }
+ sb.append("], ");
+
+ sb.append("\"should\": [");
+
+ firstQuery = new AtomicBoolean(true);
+ for (QueryStatement query : shouldQueries) {
+
+ if (!firstQuery.compareAndSet(true, false)) {
+ sb.append(", ");
+ }
+
+ sb.append(query.toElasticSearch());
+ }
+
+ sb.append("],"); // close should list
+
+ sb.append("\"must_not\": [");
+ firstQuery.set(true);
+ for (QueryStatement query : notMatchQueries) {
+ sb.append(query.toElasticSearch());
+ }
+ sb.append("]");
+
+ // Add the filter stanza, if one is required.
+ if (isFiltered()) {
+ sb.append(", \"filter\": ").append(filter.toElasticSearch());
+ }
+
+ sb.append("}"); // close bool clause
+ sb.append("}"); // close query clause
+
+ // Add the sort directive, if one is required.
+ if (sort != null) {
+ sb.append(", \"sort\": ").append(sort.toElasticSearch());
+ }
+
+ // Add aggregations
+ if (hasAggregations()) {
+ sb.append(", \"aggs\": {");
+
+ for (int i = 0; i < aggregations.length; i++) {
+ if (i > 0) {
+ sb.append(",");
+ }
+ sb.append(aggregations[i].toElasticSearch());
+ }
+
+ sb.append("}");
+ }
+
+ sb.append("}");
+
+ Logger.debug("Generated raw ElasticSearch query statement: " + sb.toString());
+ return sb.toString();
+ }
+
+ private void createQueryLists(Query[] queries, List<QueryStatement> mustList,
+ List<QueryStatement> mayList, List<QueryStatement> mustNotList) {
+
+ for (Query query : queries) {
+
+ if (query.isMust()) {
+
+ if (query.getQueryStatement().isNotMatch()) {
+ mustNotList.add(query.getQueryStatement());
+ } else {
+ mustList.add(query.getQueryStatement());
+ }
+ } else {
+
+ if (query.getQueryStatement().isNotMatch()) {
+ mustNotList.add(query.getQueryStatement());
+ } else {
+ mayList.add(query.getQueryStatement());
+ }
+ }
+ }
+ }
+
+
+ @Override
+ public String toString() {
+
+ StringBuilder sb = new StringBuilder();
+
+ sb.append("SEARCH STATEMENT: {");
+
+ if (size != null) {
+ sb.append("from: ").append(resultsStart).append(", size: ").append(size).append(", ");
+ }
+
+ if (filter != null) {
+ sb.append("filter: ").append(filter.toString()).append(", ");
+ }
+
+ sb.append("queries: [");
+ AtomicBoolean firstQuery = new AtomicBoolean(true);
+ if (queries != null) {
+ for (Query query : queries) {
+
+ if (!firstQuery.compareAndSet(true, false)) {
+ sb.append(", ");
+ }
+ sb.append(query.toString());
+ }
+ }
+ sb.append("]");
+
+ sb.append("aggregations: [");
+ firstQuery = new AtomicBoolean(true);
+
+ if (aggregations != null) {
+ for (Aggregation agg : aggregations) {
+
+ if (!firstQuery.compareAndSet(true, false)) {
+ sb.append(", ");
+ }
+ sb.append(agg.toString());
+ }
+ }
+ sb.append("]");
+
+ sb.append("]}");
+
+ return sb.toString();
+ }
+
+}
diff --git a/src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/Sort.java b/src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/Sort.java
new file mode 100644
index 0000000..968a7ad
--- /dev/null
+++ b/src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/Sort.java
@@ -0,0 +1,76 @@
+/**
+ * ============LICENSE_START=======================================================
+ * Search Data Service
+ * ================================================================================
+ * Copyright © 2017 AT&T Intellectual Property.
+ * Copyright © 2017 Amdocs
+ * All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License ati
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ *
+ * ECOMP and OpenECOMP are trademarks
+ * and service marks of AT&T Intellectual Property.
+ */
+package org.openecomp.sa.searchdbabstraction.searchapi;
+
+public class Sort {
+
+ private String field;
+ private SortDirection order = null;
+
+ public enum SortDirection {
+ ascending,
+ descending
+ }
+
+ public String getField() {
+ return field;
+ }
+
+ public void setField(String field) {
+ this.field = field;
+ }
+
+ public SortDirection getOrder() {
+ return order;
+ }
+
+ public void setOrder(SortDirection order) {
+ this.order = order;
+ }
+
+ public String toElasticSearch() {
+
+ StringBuilder sb = new StringBuilder();
+
+ sb.append("{ \"").append(field).append("\": { \"order\": ");
+
+ // If a sort order wasn't explicitly supplied, default to 'ascending'.
+ if (order != null) {
+ switch (order) {
+ case ascending:
+ sb.append("\"asc\"}}");
+ break;
+ case descending:
+ sb.append("\"desc\"}}");
+ break;
+ default:
+ }
+ } else {
+ sb.append("\"asc\"}}");
+ }
+
+ return sb.toString();
+ }
+}
diff --git a/src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/TermQuery.java b/src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/TermQuery.java
new file mode 100644
index 0000000..e4dba59
--- /dev/null
+++ b/src/main/java/org/openecomp/sa/searchdbabstraction/searchapi/TermQuery.java
@@ -0,0 +1,349 @@
+/**
+ * ============LICENSE_START=======================================================
+ * Search Data Service
+ * ================================================================================
+ * Copyright © 2017 AT&T Intellectual Property.
+ * Copyright © 2017 Amdocs
+ * All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License ati
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ *
+ * ECOMP and OpenECOMP are trademarks
+ * and service marks of AT&T Intellectual Property.
+ */
+package org.openecomp.sa.searchdbabstraction.searchapi;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import edu.emory.mathcs.backport.java.util.Arrays;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * This class represents a simple term query.
+ *
+ * <p>A term query takes an operator, a field to apply the query to and a value to match
+ * against the query contents.
+ *
+ * <p>Valid operators include:
+ * <ul>
+ * <li> match - Field must contain the supplied value to produce a match. </li>
+ * <li> not-match - Field must NOT contain the supplied value to produce a match. </li>
+ * </ul>
+ * The following examples illustrate the structure of a few variants of the
+ * term query:
+ *
+ * <p><pre>
+ * // Single Field Match Query:
+ * {
+ * "match": {"field": "searchTags", "value": "abcd"}
+ * }
+ *
+ * // Single Field Not-Match query:
+ * {
+ * "not-match": {"field": "searchTags", "value": "efgh"}
+ * }
+ * </pre>
+ *
+ * <p><pre>
+ * // Multi Field Match Query With A Single Value:
+ * {
+ * "match": {"field": "entityType searchTags", "value": "pserver"}
+ * }
+ *
+ * // Multi Field Match Query With Multiple Values:
+ * {
+ * "match": {"field": "entityType searchTags", "value": "pserver tenant"}
+ * }
+ * </pre>
+ */
+public class TermQuery {
+
+ /**
+ * The name of the field to apply the term query to.
+ */
+ private String field;
+
+ /**
+ * The value which the field must contain in order to have a match.
+ */
+ private Object value;
+
+ /**
+ * For multi field queries only. Determines the rules for whether or not a document matches
+ * the query, as follows:
+ *
+ * <p>"and" - At least one occurrence of every supplied value must be present in any of the
+ * supplied fields.
+ *
+ * <p>"or" - At least one occurrence of any of the supplied values must be present in any of
+ * the supplied fields.
+ */
+ private String operator;
+
+ @JsonProperty("analyzer")
+ private String searchAnalyzer;
+
+
+ public String getField() {
+ return field;
+ }
+
+ public void setField(String field) {
+ this.field = field;
+ }
+
+ public Object getValue() {
+ return value;
+ }
+
+ public void setValue(Object value) {
+ this.value = value;
+ }
+
+ private boolean isNumericValue() {
+ return ((value instanceof Integer) || (value instanceof Double));
+ }
+
+ public String getOperator() {
+ return operator;
+ }
+
+ public void setOperator(String operator) {
+ this.operator = operator;
+ }
+
+ public String getSearchAnalyzer() {
+ return searchAnalyzer;
+ }
+
+ public void setSearchAnalyzer(String searchAnalyzer) {
+ this.searchAnalyzer = searchAnalyzer;
+ }
+
+ /**
+ * This method returns a string which represents this query in syntax
+ * that is understandable by ElasticSearch and is suitable for inclusion
+ * in an ElasticSearch query string.
+ *
+ * @return - ElasticSearch syntax string.
+ */
+ public String toElasticSearch() {
+
+ StringBuilder sb = new StringBuilder();
+
+ sb.append("{");
+
+ // Are we generating a multi field query?
+ if (isMultiFieldQuery()) {
+
+ // For multi field queries, we have to be careful about how we handle
+ // nested fields, so check to see if any of the specified fields are
+ // nested.
+ if (field.contains(".")) {
+
+ // Build the equivalent of a multi match query across one or more nested fields.
+ toElasticSearchNestedMultiMatchQuery(sb);
+
+ } else {
+
+ // Build a real multi match query, since we don't need to worry about nested fields.
+ toElasticSearchMultiFieldQuery(sb);
+ }
+ } else {
+
+ // Single field query.
+
+ // Add the necessary wrapping if this is a query against a nested field.
+ if (fieldIsNested(field)) {
+ sb.append("{\"nested\": { \"path\": \"").append(pathForNestedField(field))
+ .append("\", \"query\": ");
+ }
+
+ // Build the query.
+ toElasticSearchSingleFieldQuery(sb);
+
+ if (fieldIsNested(field)) {
+ sb.append("}}");
+ }
+ }
+
+ sb.append("}");
+
+ return sb.toString();
+ }
+
+
+ /**
+ * Determines whether or not the client has specified a term query with
+ * multiple fields.
+ *
+ * @return - true if the query is referencing multiple fields, false, otherwise.
+ */
+ private boolean isMultiFieldQuery() {
+
+ return (field.split(" ").length > 1);
+ }
+
+
+ /**
+ * Constructs a single field term query in ElasticSearch syntax.
+ *
+ * @param sb - The string builder to assemble the query string with.
+ * @return - The single term query.
+ */
+ private void toElasticSearchSingleFieldQuery(StringBuilder sb) {
+
+ sb.append("\"term\": {\"").append(field).append("\" : ");
+
+ // For numeric values, don't enclose the value in quotes.
+ if (!isNumericValue()) {
+ sb.append("\"").append(value).append("\"");
+ } else {
+ sb.append(value);
+ }
+
+ sb.append("}");
+ }
+
+
+ /**
+ * Constructs a multi field query in ElasticSearch syntax.
+ *
+ * @param sb - The string builder to assemble the query string with.
+ * @return - The multi field query.
+ */
+ private void toElasticSearchMultiFieldQuery(StringBuilder sb) {
+
+ sb.append("\"multi_match\": {");
+
+ sb.append("\"query\": \"").append(value).append("\", ");
+ sb.append("\"type\": \"cross_fields\",");
+ sb.append("\"fields\": [");
+
+ List<String> fields = Arrays.asList(field.split(" "));
+ AtomicBoolean firstField = new AtomicBoolean(true);
+ for (String f : fields) {
+ if (!firstField.compareAndSet(true, false)) {
+ sb.append(", ");
+ }
+ sb.append("\"").append(f.trim()).append("\"");
+ }
+ sb.append("],");
+
+ sb.append("\"operator\": \"").append((operator != null)
+ ? operator.toLowerCase() : "and").append("\"");
+
+ if (searchAnalyzer != null) {
+ sb.append(", \"analyzer\": \"").append(searchAnalyzer).append("\"");
+ }
+
+ sb.append("}");
+ }
+
+
+ /**
+ * Constructs the equivalent of an ElasticSearch multi match query across
+ * multiple nested fields.
+ *
+ * <p>Since ElasticSearch doesn't really let you do that, we have to be clever
+ * and construct an equivalent query using boolean operators to produce
+ * the same result.
+ *
+ * @param sb - The string builder to use to build the query.
+ */
+ public void toElasticSearchNestedMultiMatchQuery(StringBuilder sb) {
+
+ // Break out our whitespace delimited list of fields and values into a actual lists.
+ List<String> fields = Arrays.asList(field.split(" "));
+ List<String> values = Arrays.asList(((String) value).split(" ")); // GDF: revisit this cast.
+
+ sb.append("\"bool\": {");
+
+ if (operator != null) {
+
+ if (operator.toLowerCase().equals("and")) {
+ sb.append("\"must\": [");
+ } else if (operator.toLowerCase().equals("or")) {
+ sb.append("\"should\": [");
+ }
+
+ } else {
+ sb.append("\"must\": [");
+ }
+
+ AtomicBoolean firstField = new AtomicBoolean(true);
+ for (String f : fields) {
+
+ if (!firstField.compareAndSet(true, false)) {
+ sb.append(", ");
+ }
+
+ sb.append("{ ");
+
+ // Is this a nested field?
+ if (fieldIsNested(f)) {
+
+ sb.append("\"nested\": {");
+ sb.append("\"path\": \"").append(pathForNestedField(f)).append("\", ");
+ sb.append("\"query\": ");
+ }
+
+ sb.append("{\"bool\": {");
+ sb.append("\"should\": [");
+
+ AtomicBoolean firstValue = new AtomicBoolean(true);
+ for (String v : values) {
+ if (!firstValue.compareAndSet(true, false)) {
+ sb.append(", ");
+ }
+ sb.append("{\"match\": { \"");
+ sb.append(f).append("\": {\"query\": \"").append(v).append("\"");
+
+ if (searchAnalyzer != null) {
+ sb.append(", \"analyzer\": \"").append(searchAnalyzer).append("\"");
+ }
+ sb.append("}}}");
+ }
+
+ sb.append("]");
+ sb.append("}");
+
+ if (fieldIsNested(f)) {
+ sb.append("}");
+ sb.append("}");
+ }
+
+ sb.append("}");
+ }
+
+ sb.append("]");
+ sb.append("}");
+ }
+
+
+ @Override
+ public String toString() {
+ return "field: " + field + ", value: " + value + " (" + value.getClass().getName() + ")";
+ }
+
+ public boolean fieldIsNested(String field) {
+ return field.contains(".");
+ }
+
+ public String pathForNestedField(String field) {
+ int index = field.lastIndexOf('.');
+ return field.substring(0, index);
+ }
+}