diff --git a/buildSrc/src/main/resources/checkstyle_suppressions.xml b/buildSrc/src/main/resources/checkstyle_suppressions.xml
index 491659031e9..ccd8fea0120 100644
--- a/buildSrc/src/main/resources/checkstyle_suppressions.xml
+++ b/buildSrc/src/main/resources/checkstyle_suppressions.xml
@@ -1,7 +1,7 @@
+ "-//Puppy Crawl//DTD Suppressions 1.1//EN"
+ "http://www.puppycrawl.com/dtds/suppressions_1_1.dtd">
@@ -795,4 +795,4 @@
-
+
\ No newline at end of file
diff --git a/core/src/main/java/org/elasticsearch/action/search/SearchResponse.java b/core/src/main/java/org/elasticsearch/action/search/SearchResponse.java
index 54d8eab99e7..4720a502e93 100644
--- a/core/src/main/java/org/elasticsearch/action/search/SearchResponse.java
+++ b/core/src/main/java/org/elasticsearch/action/search/SearchResponse.java
@@ -21,32 +21,46 @@ package org.elasticsearch.action.search;
import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.common.Nullable;
+import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.StatusToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.rest.action.RestActions;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.internal.InternalSearchResponse;
import org.elasticsearch.search.profile.ProfileShardResult;
+import org.elasticsearch.search.profile.SearchProfileShardResults;
import org.elasticsearch.search.suggest.Suggest;
import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Map;
import static org.elasticsearch.action.search.ShardSearchFailure.readShardSearchFailure;
-import static org.elasticsearch.search.internal.InternalSearchResponse.readInternalSearchResponse;
+import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken;
+import static org.elasticsearch.common.xcontent.XContentParserUtils.throwUnknownField;
+import static org.elasticsearch.common.xcontent.XContentParserUtils.throwUnknownToken;
+
/**
* A response of a search request.
*/
public class SearchResponse extends ActionResponse implements StatusToXContentObject {
- private InternalSearchResponse internalResponse;
+ private static final ParseField SCROLL_ID = new ParseField("_scroll_id");
+ private static final ParseField TOOK = new ParseField("took");
+ private static final ParseField TIMED_OUT = new ParseField("timed_out");
+ private static final ParseField TERMINATED_EARLY = new ParseField("terminated_early");
+ private static final ParseField NUM_REDUCE_PHASES = new ParseField("num_reduce_phases");
+
+ private SearchResponseSections internalResponse;
private String scrollId;
@@ -61,7 +75,7 @@ public class SearchResponse extends ActionResponse implements StatusToXContentOb
public SearchResponse() {
}
- public SearchResponse(InternalSearchResponse internalResponse, String scrollId, int totalShards, int successfulShards,
+ public SearchResponse(SearchResponseSections internalResponse, String scrollId, int totalShards, int successfulShards,
long tookInMillis, ShardSearchFailure[] shardFailures) {
this.internalResponse = internalResponse;
this.scrollId = scrollId;
@@ -176,7 +190,8 @@ public class SearchResponse extends ActionResponse implements StatusToXContentOb
*
* @return The profile results or an empty map
*/
- @Nullable public Map getProfileResults() {
+ @Nullable
+ public Map getProfileResults() {
return internalResponse.profile();
}
@@ -190,15 +205,15 @@ public class SearchResponse extends ActionResponse implements StatusToXContentOb
public XContentBuilder innerToXContent(XContentBuilder builder, Params params) throws IOException {
if (scrollId != null) {
- builder.field("_scroll_id", scrollId);
+ builder.field(SCROLL_ID.getPreferredName(), scrollId);
}
- builder.field("took", tookInMillis);
- builder.field("timed_out", isTimedOut());
+ builder.field(TOOK.getPreferredName(), tookInMillis);
+ builder.field(TIMED_OUT.getPreferredName(), isTimedOut());
if (isTerminatedEarly() != null) {
- builder.field("terminated_early", isTerminatedEarly());
+ builder.field(TERMINATED_EARLY.getPreferredName(), isTerminatedEarly());
}
if (getNumReducePhases() != 1) {
- builder.field("num_reduce_phases", getNumReducePhases());
+ builder.field(NUM_REDUCE_PHASES.getPreferredName(), getNumReducePhases());
}
RestActions.buildBroadcastShardsHeader(builder, params, getTotalShards(), getSuccessfulShards(), getFailedShards(),
getShardFailures());
@@ -206,10 +221,89 @@ public class SearchResponse extends ActionResponse implements StatusToXContentOb
return builder;
}
+ public static SearchResponse fromXContent(XContentParser parser) throws IOException {
+ ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser::getTokenLocation);
+ XContentParser.Token token;
+ String currentFieldName = null;
+ SearchHits hits = null;
+ Aggregations aggs = null;
+ Suggest suggest = null;
+ SearchProfileShardResults profile = null;
+ boolean timedOut = false;
+ Boolean terminatedEarly = null;
+ int numReducePhases = 1;
+ long tookInMillis = -1;
+ int successfulShards = -1;
+ int totalShards = -1;
+ String scrollId = null;
+ List failures = new ArrayList<>();
+ while((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
+ if (token == XContentParser.Token.FIELD_NAME) {
+ currentFieldName = parser.currentName();
+ } else if (token.isValue()) {
+ if (SCROLL_ID.match(currentFieldName)) {
+ scrollId = parser.text();
+ } else if (TOOK.match(currentFieldName)) {
+ tookInMillis = parser.longValue();
+ } else if (TIMED_OUT.match(currentFieldName)) {
+ timedOut = parser.booleanValue();
+ } else if (TERMINATED_EARLY.match(currentFieldName)) {
+ terminatedEarly = parser.booleanValue();
+ } else if (NUM_REDUCE_PHASES.match(currentFieldName)) {
+ numReducePhases = parser.intValue();
+ } else {
+ throwUnknownField(currentFieldName, parser.getTokenLocation());
+ }
+ } else if (token == XContentParser.Token.START_OBJECT) {
+ if (SearchHits.Fields.HITS.equals(currentFieldName)) {
+ hits = SearchHits.fromXContent(parser);
+ } else if (Aggregations.AGGREGATIONS_FIELD.equals(currentFieldName)) {
+ aggs = Aggregations.fromXContent(parser);
+ } else if (Suggest.NAME.equals(currentFieldName)) {
+ suggest = Suggest.fromXContent(parser);
+ } else if (SearchProfileShardResults.PROFILE_FIELD.equals(currentFieldName)) {
+ profile = SearchProfileShardResults.fromXContent(parser);
+ } else if (RestActions._SHARDS_FIELD.match(currentFieldName)) {
+ while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
+ if (token == XContentParser.Token.FIELD_NAME) {
+ currentFieldName = parser.currentName();
+ } else if (token.isValue()) {
+ if (RestActions.FAILED_FIELD.match(currentFieldName)) {
+ parser.intValue(); // we don't need it but need to consume it
+ } else if (RestActions.SUCCESSFUL_FIELD.match(currentFieldName)) {
+ successfulShards = parser.intValue();
+ } else if (RestActions.TOTAL_FIELD.match(currentFieldName)) {
+ totalShards = parser.intValue();
+ } else {
+ throwUnknownField(currentFieldName, parser.getTokenLocation());
+ }
+ } else if (token == XContentParser.Token.START_ARRAY) {
+ if (RestActions.FAILURES_FIELD.match(currentFieldName)) {
+ while((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
+ failures.add(ShardSearchFailure.fromXContent(parser));
+ }
+ } else {
+ throwUnknownField(currentFieldName, parser.getTokenLocation());
+ }
+ } else {
+ throwUnknownToken(token, parser.getTokenLocation());
+ }
+ }
+ } else {
+ throwUnknownField(currentFieldName, parser.getTokenLocation());
+ }
+ }
+ }
+ SearchResponseSections searchResponseSections = new SearchResponseSections(hits, aggs, suggest, timedOut, terminatedEarly,
+ profile, numReducePhases);
+ return new SearchResponse(searchResponseSections, scrollId, totalShards, successfulShards, tookInMillis,
+ failures.toArray(new ShardSearchFailure[failures.size()]));
+ }
+
@Override
public void readFrom(StreamInput in) throws IOException {
super.readFrom(in);
- internalResponse = readInternalSearchResponse(in);
+ internalResponse = new InternalSearchResponse(in);
totalShards = in.readVInt();
successfulShards = in.readVInt();
int size = in.readVInt();
diff --git a/core/src/main/java/org/elasticsearch/action/search/SearchResponseSections.java b/core/src/main/java/org/elasticsearch/action/search/SearchResponseSections.java
new file mode 100644
index 00000000000..1757acbfd6d
--- /dev/null
+++ b/core/src/main/java/org/elasticsearch/action/search/SearchResponseSections.java
@@ -0,0 +1,122 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you 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 at
+ *
+ * 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.
+ */
+
+package org.elasticsearch.action.search;
+
+import org.elasticsearch.common.io.stream.StreamOutput;
+import org.elasticsearch.common.xcontent.ToXContent;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.search.SearchHits;
+import org.elasticsearch.search.aggregations.Aggregations;
+import org.elasticsearch.search.profile.ProfileShardResult;
+import org.elasticsearch.search.profile.SearchProfileShardResults;
+import org.elasticsearch.search.suggest.Suggest;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * Base class that holds the various sections which a search response is
+ * composed of (hits, aggs, suggestions etc.) and allows to retrieve them.
+ *
+ * The reason why this class exists is that the high level REST client uses its own classes
+ * to parse aggregations into, which are not serializable. This is the common part that can be
+ * shared between core and client.
+ */
+public class SearchResponseSections implements ToXContent {
+
+ protected final SearchHits hits;
+ protected final Aggregations aggregations;
+ protected final Suggest suggest;
+ protected final SearchProfileShardResults profileResults;
+ protected final boolean timedOut;
+ protected final Boolean terminatedEarly;
+ protected final int numReducePhases;
+
+ public SearchResponseSections(SearchHits hits, Aggregations aggregations, Suggest suggest, boolean timedOut, Boolean terminatedEarly,
+ SearchProfileShardResults profileResults, int numReducePhases) {
+ this.hits = hits;
+ this.aggregations = aggregations;
+ this.suggest = suggest;
+ this.profileResults = profileResults;
+ this.timedOut = timedOut;
+ this.terminatedEarly = terminatedEarly;
+ this.numReducePhases = numReducePhases;
+ }
+
+ public final boolean timedOut() {
+ return this.timedOut;
+ }
+
+ public final Boolean terminatedEarly() {
+ return this.terminatedEarly;
+ }
+
+ public final SearchHits hits() {
+ return hits;
+ }
+
+ public final Aggregations aggregations() {
+ return aggregations;
+ }
+
+ public final Suggest suggest() {
+ return suggest;
+ }
+
+ /**
+ * Returns the number of reduce phases applied to obtain this search response
+ */
+ public final int getNumReducePhases() {
+ return numReducePhases;
+ }
+
+ /**
+ * Returns the profile results for this search response (including all shards).
+ * An empty map is returned if profiling was not enabled
+ *
+ * @return Profile results
+ */
+ public final Map profile() {
+ if (profileResults == null) {
+ return Collections.emptyMap();
+ }
+ return profileResults.getShardResults();
+ }
+
+ @Override
+ public final XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+ hits.toXContent(builder, params);
+ if (aggregations != null) {
+ aggregations.toXContent(builder, params);
+ }
+ if (suggest != null) {
+ suggest.toXContent(builder, params);
+ }
+ if (profileResults != null) {
+ profileResults.toXContent(builder, params);
+ }
+ return builder;
+ }
+
+ protected void writeTo(StreamOutput out) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/core/src/main/java/org/elasticsearch/common/xcontent/ObjectParser.java b/core/src/main/java/org/elasticsearch/common/xcontent/ObjectParser.java
index 6dc6697594a..ed1d85b5a76 100644
--- a/core/src/main/java/org/elasticsearch/common/xcontent/ObjectParser.java
+++ b/core/src/main/java/org/elasticsearch/common/xcontent/ObjectParser.java
@@ -395,6 +395,7 @@ public final class ObjectParser extends AbstractObjectParser extends AbstractObjectParser tokens;
diff --git a/core/src/main/java/org/elasticsearch/common/xcontent/XContentParserUtils.java b/core/src/main/java/org/elasticsearch/common/xcontent/XContentParserUtils.java
index 169202e40d7..30199afa98c 100644
--- a/core/src/main/java/org/elasticsearch/common/xcontent/XContentParserUtils.java
+++ b/core/src/main/java/org/elasticsearch/common/xcontent/XContentParserUtils.java
@@ -111,10 +111,9 @@ public final class XContentParserUtils {
}
/**
- * This method expects that the current token is a {@code XContentParser.Token.FIELD_NAME} and
- * that the current field name is the concatenation of a type, delimiter and name (ex: terms#foo
- * where "terms" refers to the type of a registered {@link NamedXContentRegistry.Entry}, "#" is
- * the delimiter and "foo" the name of the object to parse).
+ * This method expects that the current field name is the concatenation of a type, a delimiter and a name
+ * (ex: terms#foo where "terms" refers to the type of a registered {@link NamedXContentRegistry.Entry},
+ * "#" is the delimiter and "foo" the name of the object to parse).
*
* The method splits the field's name to extract the type and name and then parses the object
* using the {@link XContentParser#namedObject(Class, String, Object)} method.
@@ -128,7 +127,6 @@ public final class XContentParserUtils {
* from the field's name
*/
public static T parseTypedKeysObject(XContentParser parser, String delimiter, Class objectClass) throws IOException {
- ensureExpectedToken(XContentParser.Token.FIELD_NAME, parser.currentToken(), parser::getTokenLocation);
String currentFieldName = parser.currentName();
if (Strings.hasLength(currentFieldName)) {
int position = currentFieldName.indexOf(delimiter);
diff --git a/core/src/main/java/org/elasticsearch/rest/action/RestActions.java b/core/src/main/java/org/elasticsearch/rest/action/RestActions.java
index 74836b1dc2d..15199c5a926 100644
--- a/core/src/main/java/org/elasticsearch/rest/action/RestActions.java
+++ b/core/src/main/java/org/elasticsearch/rest/action/RestActions.java
@@ -25,6 +25,7 @@ import org.elasticsearch.action.ShardOperationFailedException;
import org.elasticsearch.action.support.broadcast.BroadcastResponse;
import org.elasticsearch.action.support.nodes.BaseNodeResponse;
import org.elasticsearch.action.support.nodes.BaseNodesResponse;
+import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.lucene.uid.Versions;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.ToXContent.Params;
@@ -46,6 +47,12 @@ import java.util.List;
public class RestActions {
+ public static final ParseField _SHARDS_FIELD = new ParseField("_shards");
+ public static final ParseField TOTAL_FIELD = new ParseField("total");
+ public static final ParseField SUCCESSFUL_FIELD = new ParseField("successful");
+ public static final ParseField FAILED_FIELD = new ParseField("failed");
+ public static final ParseField FAILURES_FIELD = new ParseField("failures");
+
public static long parseVersion(RestRequest request) {
if (request.hasParam("version")) {
return request.paramAsLong("version", Versions.MATCH_ANY);
@@ -71,12 +78,12 @@ public class RestActions {
public static void buildBroadcastShardsHeader(XContentBuilder builder, Params params,
int total, int successful, int failed,
ShardOperationFailedException[] shardFailures) throws IOException {
- builder.startObject("_shards");
- builder.field("total", total);
- builder.field("successful", successful);
- builder.field("failed", failed);
+ builder.startObject(_SHARDS_FIELD.getPreferredName());
+ builder.field(TOTAL_FIELD.getPreferredName(), total);
+ builder.field(SUCCESSFUL_FIELD.getPreferredName(), successful);
+ builder.field(FAILED_FIELD.getPreferredName(), failed);
if (shardFailures != null && shardFailures.length > 0) {
- builder.startArray("failures");
+ builder.startArray(FAILURES_FIELD.getPreferredName());
final boolean group = params.paramAsBoolean("group_shard_failures", true); // we group by default
for (ShardOperationFailedException shardFailure : group ? ExceptionsHelper.groupBy(shardFailures) : shardFailures) {
builder.startObject();
diff --git a/core/src/main/java/org/elasticsearch/search/SearchHits.java b/core/src/main/java/org/elasticsearch/search/SearchHits.java
index ada09d2e38b..0b49ba8ec12 100644
--- a/core/src/main/java/org/elasticsearch/search/SearchHits.java
+++ b/core/src/main/java/org/elasticsearch/search/SearchHits.java
@@ -105,10 +105,10 @@ public final class SearchHits implements Streamable, ToXContent, Iterable {
+public class Aggregations implements Iterable, ToXContent {
+
+ public static final String AGGREGATIONS_FIELD = "aggregations";
protected List extends Aggregation> aggregations = Collections.emptyList();
protected Map aggregationsAsMap;
@@ -38,7 +47,7 @@ public abstract class Aggregations implements Iterable {
protected Aggregations() {
}
- protected Aggregations(List extends Aggregation> aggregations) {
+ public Aggregations(List extends Aggregation> aggregations) {
this.aggregations = aggregations;
}
@@ -98,4 +107,35 @@ public abstract class Aggregations implements Iterable {
public final int hashCode() {
return Objects.hash(getClass(), aggregations);
}
+
+ @Override
+ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+ if (aggregations.isEmpty()) {
+ return builder;
+ }
+ builder.startObject(AGGREGATIONS_FIELD);
+ toXContentInternal(builder, params);
+ return builder.endObject();
+ }
+
+ /**
+ * Directly write all the aggregations without their bounding object. Used by sub-aggregations (non top level aggs)
+ */
+ public XContentBuilder toXContentInternal(XContentBuilder builder, Params params) throws IOException {
+ for (Aggregation aggregation : aggregations) {
+ aggregation.toXContent(builder, params);
+ }
+ return builder;
+ }
+
+ public static Aggregations fromXContent(XContentParser parser) throws IOException {
+ final List aggregations = new ArrayList<>();
+ XContentParser.Token token;
+ while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
+ if (token == XContentParser.Token.START_OBJECT) {
+ aggregations.add(XContentParserUtils.parseTypedKeysObject(parser, Aggregation.TYPED_KEYS_DELIMITER, Aggregation.class));
+ }
+ }
+ return new Aggregations(aggregations);
+ }
}
diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/InternalAggregation.java b/core/src/main/java/org/elasticsearch/search/aggregations/InternalAggregation.java
index 0618e5cb29d..2143e4f14ab 100644
--- a/core/src/main/java/org/elasticsearch/search/aggregations/InternalAggregation.java
+++ b/core/src/main/java/org/elasticsearch/search/aggregations/InternalAggregation.java
@@ -169,12 +169,8 @@ public abstract class InternalAggregation implements Aggregation, ToXContent, Na
return pipelineAggregators;
}
- /**
- * Returns a string representing the type of the aggregation. This type is added to
- * the aggregation name in the response, so that it can later be used by REST clients
- * to determine the internal type of the aggregation.
- */
- protected String getType() {
+ @Override
+ public String getType() {
return getWriteableName();
}
diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/InternalAggregations.java b/core/src/main/java/org/elasticsearch/search/aggregations/InternalAggregations.java
index 64c9e4794c3..846dc1bb3b0 100644
--- a/core/src/main/java/org/elasticsearch/search/aggregations/InternalAggregations.java
+++ b/core/src/main/java/org/elasticsearch/search/aggregations/InternalAggregations.java
@@ -22,7 +22,6 @@ import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Streamable;
import org.elasticsearch.common.xcontent.ToXContent;
-import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.search.aggregations.InternalAggregation.ReduceContext;
import java.io.IOException;
@@ -32,6 +31,7 @@ import java.util.List;
import java.util.Map;
import static java.util.Collections.emptyMap;
+
/**
* An internal implementation of {@link Aggregations}.
*/
@@ -80,27 +80,6 @@ public final class InternalAggregations extends Aggregations implements ToXConte
return new InternalAggregations(reducedAggregations);
}
- @Override
- public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
- if (aggregations.isEmpty()) {
- return builder;
- }
- builder.startObject("aggregations");
- toXContentInternal(builder, params);
- return builder.endObject();
- }
-
- /**
- * Directly write all the aggregations without their bounding object. Used by sub-aggregations (non top level aggs)
- */
- public XContentBuilder toXContentInternal(XContentBuilder builder, Params params) throws IOException {
- for (Aggregation aggregation : aggregations) {
- ((InternalAggregation)aggregation).toXContent(builder, params);
- }
- return builder;
- }
-
-
public static InternalAggregations readAggregations(StreamInput in) throws IOException {
InternalAggregations result = new InternalAggregations();
result.readFrom(in);
diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/ParsedAggregation.java b/core/src/main/java/org/elasticsearch/search/aggregations/ParsedAggregation.java
new file mode 100644
index 00000000000..d79baac06b0
--- /dev/null
+++ b/core/src/main/java/org/elasticsearch/search/aggregations/ParsedAggregation.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you 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 at
+ *
+ * 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.
+ */
+
+package org.elasticsearch.search.aggregations;
+
+import org.elasticsearch.common.xcontent.ObjectParser;
+import org.elasticsearch.common.xcontent.ToXContent;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.common.xcontent.XContentParser.Token;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * An implementation of {@link Aggregation} that is parsed from a REST response.
+ * Serves as a base class for all aggregation implementations that are parsed from REST.
+ */
+public abstract class ParsedAggregation implements Aggregation, ToXContent {
+
+ protected static void declareAggregationFields(ObjectParser extends ParsedAggregation, Void> objectParser) {
+ objectParser.declareObject((parsedAgg, metadata) -> parsedAgg.metadata = Collections.unmodifiableMap(metadata),
+ (parser, context) -> parser.map(), InternalAggregation.CommonFields.META);
+ }
+
+ private String name;
+ protected Map metadata;
+
+ @Override
+ public final String getName() {
+ return name;
+ }
+
+ protected void setName(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public final Map getMetaData() {
+ return metadata;
+ }
+
+ @Override
+ public XContentBuilder toXContent(XContentBuilder builder, ToXContent.Params params) throws IOException {
+ // Concatenates the type and the name of the aggregation (ex: top_hits#foo)
+ builder.startObject(String.join(InternalAggregation.TYPED_KEYS_DELIMITER, getType(), name));
+ if (this.metadata != null) {
+ builder.field(InternalAggregation.CommonFields.META.getPreferredName());
+ builder.map(this.metadata);
+ }
+ doXContentBody(builder, params);
+ builder.endObject();
+ return builder;
+ }
+
+ protected abstract XContentBuilder doXContentBody(XContentBuilder builder, Params params) throws IOException;
+
+ /**
+ * Parse a token of type XContentParser.Token.VALUE_NUMBER or XContentParser.Token.STRING to a double.
+ * In other cases the default value is returned instead.
+ */
+ protected static double parseDouble(XContentParser parser, double defaultNullValue) throws IOException {
+ Token currentToken = parser.currentToken();
+ if (currentToken == XContentParser.Token.VALUE_NUMBER || currentToken == XContentParser.Token.VALUE_STRING) {
+ return parser.doubleValue();
+ } else {
+ return defaultNullValue;
+ }
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/ParsedMultiBucketAggregation.java b/core/src/main/java/org/elasticsearch/search/aggregations/ParsedMultiBucketAggregation.java
new file mode 100644
index 00000000000..df80ada8ddd
--- /dev/null
+++ b/core/src/main/java/org/elasticsearch/search/aggregations/ParsedMultiBucketAggregation.java
@@ -0,0 +1,181 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you 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 at
+ *
+ * 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.
+ */
+
+package org.elasticsearch.search.aggregations;
+
+import org.elasticsearch.common.CheckedBiConsumer;
+import org.elasticsearch.common.CheckedFunction;
+import org.elasticsearch.common.xcontent.ObjectParser;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.common.xcontent.XContentParserUtils;
+import org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Supplier;
+
+import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken;
+
+public abstract class ParsedMultiBucketAggregation
+ extends ParsedAggregation implements MultiBucketsAggregation {
+
+ protected final List buckets = new ArrayList<>();
+ protected boolean keyed = false;
+
+ @Override
+ protected XContentBuilder doXContentBody(XContentBuilder builder, Params params) throws IOException {
+ if (keyed) {
+ builder.startObject(CommonFields.BUCKETS.getPreferredName());
+ } else {
+ builder.startArray(CommonFields.BUCKETS.getPreferredName());
+ }
+ for (B bucket : buckets) {
+ bucket.toXContent(builder, params);
+ }
+ if (keyed) {
+ builder.endObject();
+ } else {
+ builder.endArray();
+ }
+ return builder;
+ }
+
+ protected static void declareMultiBucketAggregationFields(final ObjectParser extends ParsedMultiBucketAggregation, Void> objectParser,
+ final CheckedFunction bucketParser,
+ final CheckedFunction keyedBucketParser) {
+ declareAggregationFields(objectParser);
+ objectParser.declareField((parser, aggregation, context) -> {
+ XContentParser.Token token = parser.currentToken();
+ if (token == XContentParser.Token.START_OBJECT) {
+ aggregation.keyed = true;
+ while (parser.nextToken() != XContentParser.Token.END_OBJECT) {
+ aggregation.buckets.add(keyedBucketParser.apply(parser));
+ }
+ } else if (token == XContentParser.Token.START_ARRAY) {
+ aggregation.keyed = false;
+ while (parser.nextToken() != XContentParser.Token.END_ARRAY) {
+ aggregation.buckets.add(bucketParser.apply(parser));
+ }
+ }
+ }, CommonFields.BUCKETS, ObjectParser.ValueType.OBJECT_ARRAY);
+ }
+
+ public abstract static class ParsedBucket implements MultiBucketsAggregation.Bucket {
+
+ private Aggregations aggregations;
+ private String keyAsString;
+ private long docCount;
+ private boolean keyed;
+
+ protected void setKeyAsString(String keyAsString) {
+ this.keyAsString = keyAsString;
+ }
+
+ @Override
+ public String getKeyAsString() {
+ return keyAsString;
+ }
+
+ protected void setDocCount(long docCount) {
+ this.docCount = docCount;
+ }
+
+ @Override
+ public long getDocCount() {
+ return docCount;
+ }
+
+ public void setKeyed(boolean keyed) {
+ this.keyed = keyed;
+ }
+
+ protected boolean isKeyed() {
+ return keyed;
+ }
+
+ protected void setAggregations(Aggregations aggregations) {
+ this.aggregations = aggregations;
+ }
+
+ @Override
+ public Aggregations getAggregations() {
+ return aggregations;
+ }
+
+ @Override
+ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+ if (keyed) {
+ // Subclasses can override the getKeyAsString method to handle specific cases like
+ // keyed bucket with RAW doc value format where the key_as_string field is not printed
+ // out but we still need to have a string version of the key to use as the bucket's name.
+ builder.startObject(getKeyAsString());
+ } else {
+ builder.startObject();
+ }
+ if (keyAsString != null) {
+ builder.field(CommonFields.KEY_AS_STRING.getPreferredName(), getKeyAsString());
+ }
+ keyToXContent(builder);
+ builder.field(CommonFields.DOC_COUNT.getPreferredName(), docCount);
+ aggregations.toXContentInternal(builder, params);
+ builder.endObject();
+ return builder;
+ }
+
+ protected XContentBuilder keyToXContent(XContentBuilder builder) throws IOException {
+ return builder.field(CommonFields.KEY.getPreferredName(), getKey());
+ }
+
+ protected static B parseXContent(final XContentParser parser,
+ final boolean keyed,
+ final Supplier bucketSupplier,
+ final CheckedBiConsumer keyConsumer)
+ throws IOException {
+ final B bucket = bucketSupplier.get();
+ bucket.setKeyed(keyed);
+ XContentParser.Token token = parser.currentToken();
+ String currentFieldName = parser.currentName();
+ if (keyed) {
+ ensureExpectedToken(XContentParser.Token.FIELD_NAME, token, parser::getTokenLocation);
+ ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser::getTokenLocation);
+ }
+
+ List aggregations = new ArrayList<>();
+ while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
+ if (token == XContentParser.Token.FIELD_NAME) {
+ currentFieldName = parser.currentName();
+ } else if (token.isValue()) {
+ if (CommonFields.KEY_AS_STRING.getPreferredName().equals(currentFieldName)) {
+ bucket.setKeyAsString(parser.text());
+ } else if (CommonFields.KEY.getPreferredName().equals(currentFieldName)) {
+ keyConsumer.accept(parser, bucket);
+ } else if (CommonFields.DOC_COUNT.getPreferredName().equals(currentFieldName)) {
+ bucket.setDocCount(parser.longValue());
+ }
+ } else if (token == XContentParser.Token.START_OBJECT) {
+ aggregations.add(XContentParserUtils.parseTypedKeysObject(parser, Aggregation.TYPED_KEYS_DELIMITER, Aggregation.class));
+ }
+ }
+ bucket.setAggregations(new Aggregations(aggregations));
+ return bucket;
+ }
+ }
+}
diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/ParsedSingleBucketAggregation.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/ParsedSingleBucketAggregation.java
new file mode 100644
index 00000000000..99d9bfa1955
--- /dev/null
+++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/ParsedSingleBucketAggregation.java
@@ -0,0 +1,93 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you 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 at
+ *
+ * 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.
+ */
+package org.elasticsearch.search.aggregations.bucket;
+
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.common.xcontent.XContentParserUtils;
+import org.elasticsearch.search.aggregations.Aggregation;
+import org.elasticsearch.search.aggregations.Aggregations;
+import org.elasticsearch.search.aggregations.ParsedAggregation;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken;
+
+/**
+ * A base class for all the single bucket aggregations.
+ */
+public abstract class ParsedSingleBucketAggregation extends ParsedAggregation implements SingleBucketAggregation {
+
+ private long docCount;
+ protected Aggregations aggregations = new Aggregations(Collections.emptyList());
+
+ @Override
+ public long getDocCount() {
+ return docCount;
+ }
+
+ protected void setDocCount(long docCount) {
+ this.docCount = docCount;
+ }
+
+ @Override
+ public Aggregations getAggregations() {
+ return aggregations;
+ }
+
+ @Override
+ public XContentBuilder doXContentBody(XContentBuilder builder, Params params) throws IOException {
+ builder.field(CommonFields.DOC_COUNT.getPreferredName(), docCount);
+ aggregations.toXContentInternal(builder, params);
+ return builder;
+ }
+
+ protected static T parseXContent(final XContentParser parser, T aggregation, String name)
+ throws IOException {
+ aggregation.setName(name);
+ XContentParser.Token token = parser.currentToken();
+ String currentFieldName = parser.currentName();
+ if (token == XContentParser.Token.FIELD_NAME) {
+ token = parser.nextToken();
+ }
+ ensureExpectedToken(XContentParser.Token.START_OBJECT, token, parser::getTokenLocation);
+
+ List aggregations = new ArrayList<>();
+ while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
+ if (token == XContentParser.Token.FIELD_NAME) {
+ currentFieldName = parser.currentName();
+ } else if (token.isValue()) {
+ if (CommonFields.DOC_COUNT.getPreferredName().equals(currentFieldName)) {
+ aggregation.setDocCount(parser.longValue());
+ }
+ } else if (token == XContentParser.Token.START_OBJECT) {
+ if (CommonFields.META.getPreferredName().equals(currentFieldName)) {
+ aggregation.metadata = parser.map();
+ } else {
+ aggregations.add(XContentParserUtils.parseTypedKeysObject(parser, Aggregation.TYPED_KEYS_DELIMITER, Aggregation.class));
+ }
+ }
+ }
+ aggregation.aggregations = new Aggregations(aggregations);
+ return aggregation;
+ }
+}
diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/adjacency/ParsedAdjacencyMatrix.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/adjacency/ParsedAdjacencyMatrix.java
new file mode 100644
index 00000000000..1fb356d45c2
--- /dev/null
+++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/adjacency/ParsedAdjacencyMatrix.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you 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 at
+ *
+ * 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.
+ */
+
+package org.elasticsearch.search.aggregations.bucket.adjacency;
+
+import org.elasticsearch.common.xcontent.ObjectParser;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.search.aggregations.ParsedMultiBucketAggregation;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class ParsedAdjacencyMatrix extends ParsedMultiBucketAggregation implements AdjacencyMatrix {
+
+ private Map bucketMap;
+
+ @Override
+ public String getType() {
+ return AdjacencyMatrixAggregationBuilder.NAME;
+ }
+
+ @Override
+ public List extends AdjacencyMatrix.Bucket> getBuckets() {
+ return buckets;
+ }
+
+ @Override
+ public ParsedBucket getBucketByKey(String key) {
+ if (bucketMap == null) {
+ bucketMap = new HashMap<>(buckets.size());
+ for (ParsedBucket bucket : buckets) {
+ bucketMap.put(bucket.getKey(), bucket);
+ }
+ }
+ return bucketMap.get(key);
+ }
+
+ private static ObjectParser PARSER =
+ new ObjectParser<>(ParsedAdjacencyMatrix.class.getSimpleName(), true, ParsedAdjacencyMatrix::new);
+ static {
+ declareMultiBucketAggregationFields(PARSER,
+ parser -> ParsedBucket.fromXContent(parser),
+ parser -> ParsedBucket.fromXContent(parser));
+ }
+
+ public static ParsedAdjacencyMatrix fromXContent(XContentParser parser, String name) throws IOException {
+ ParsedAdjacencyMatrix aggregation = PARSER.parse(parser, null);
+ aggregation.setName(name);
+ return aggregation;
+ }
+
+ public static class ParsedBucket extends ParsedMultiBucketAggregation.ParsedBucket implements AdjacencyMatrix.Bucket {
+
+ private String key;
+
+ @Override
+ public String getKey() {
+ return key;
+ }
+
+ @Override
+ public String getKeyAsString() {
+ return key;
+ }
+
+ static ParsedBucket fromXContent(XContentParser parser) throws IOException {
+ return parseXContent(parser, false, ParsedBucket::new, (p, bucket) -> bucket.key = p.text());
+ }
+ }
+}
diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/filter/ParsedFilter.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/filter/ParsedFilter.java
new file mode 100644
index 00000000000..5f5cf104498
--- /dev/null
+++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/filter/ParsedFilter.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you 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 at
+ *
+ * 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.
+ */
+package org.elasticsearch.search.aggregations.bucket.filter;
+
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.search.aggregations.bucket.ParsedSingleBucketAggregation;
+
+import java.io.IOException;
+
+public class ParsedFilter extends ParsedSingleBucketAggregation implements Filter {
+
+ @Override
+ public String getType() {
+ return FilterAggregationBuilder.NAME;
+ }
+
+ public static ParsedFilter fromXContent(XContentParser parser, final String name) throws IOException {
+ return parseXContent(parser, new ParsedFilter(), name);
+ }
+}
diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/filters/ParsedFilters.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/filters/ParsedFilters.java
new file mode 100644
index 00000000000..a77577a3ccc
--- /dev/null
+++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/filters/ParsedFilters.java
@@ -0,0 +1,141 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you 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 at
+ *
+ * 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.
+ */
+
+package org.elasticsearch.search.aggregations.bucket.filters;
+
+import org.elasticsearch.common.xcontent.ObjectParser;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.common.xcontent.XContentParserUtils;
+import org.elasticsearch.search.aggregations.Aggregation;
+import org.elasticsearch.search.aggregations.Aggregations;
+import org.elasticsearch.search.aggregations.ParsedMultiBucketAggregation;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken;
+
+public class ParsedFilters extends ParsedMultiBucketAggregation implements Filters {
+
+ private Map bucketMap;
+
+ @Override
+ public String getType() {
+ return FiltersAggregationBuilder.NAME;
+ }
+
+ @Override
+ public List extends Filters.Bucket> getBuckets() {
+ return buckets;
+ }
+
+ @Override
+ public ParsedBucket getBucketByKey(String key) {
+ if (bucketMap == null) {
+ bucketMap = new HashMap<>(buckets.size());
+ for (ParsedBucket bucket : buckets) {
+ bucketMap.put(bucket.getKey(), bucket);
+ }
+ }
+ return bucketMap.get(key);
+ }
+
+ private static ObjectParser PARSER =
+ new ObjectParser<>(ParsedFilters.class.getSimpleName(), true, ParsedFilters::new);
+ static {
+ declareMultiBucketAggregationFields(PARSER,
+ parser -> ParsedBucket.fromXContent(parser, false),
+ parser -> ParsedBucket.fromXContent(parser, true));
+ }
+
+ public static ParsedFilters fromXContent(XContentParser parser, String name) throws IOException {
+ ParsedFilters aggregation = PARSER.parse(parser, null);
+ aggregation.setName(name);
+ // in case this is not a keyed aggregation, we need to add numeric keys to the buckets
+ if (aggregation.keyed == false) {
+ int i = 0;
+ for (ParsedBucket bucket : aggregation.buckets) {
+ assert bucket.key == null;
+ bucket.key = String.valueOf(i);
+ i++;
+ }
+ }
+ return aggregation;
+ }
+
+ public static class ParsedBucket extends ParsedMultiBucketAggregation.ParsedBucket implements Filters.Bucket {
+
+ private String key;
+
+ @Override
+ public String getKey() {
+ return key;
+ }
+
+ @Override
+ public String getKeyAsString() {
+ return key;
+ }
+
+ @Override
+ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+ if (isKeyed()) {
+ builder.startObject(key);
+ } else {
+ builder.startObject();
+ }
+ builder.field(CommonFields.DOC_COUNT.getPreferredName(), getDocCount());
+ getAggregations().toXContentInternal(builder, params);
+ builder.endObject();
+ return builder;
+ }
+
+
+ static ParsedBucket fromXContent(XContentParser parser, boolean keyed) throws IOException {
+ final ParsedBucket bucket = new ParsedBucket();
+ bucket.setKeyed(keyed);
+ XContentParser.Token token = parser.currentToken();
+ String currentFieldName = parser.currentName();
+ if (keyed) {
+ ensureExpectedToken(XContentParser.Token.FIELD_NAME, token, parser::getTokenLocation);
+ bucket.key = currentFieldName;
+ ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser::getTokenLocation);
+ }
+
+ List aggregations = new ArrayList<>();
+ while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
+ if (token == XContentParser.Token.FIELD_NAME) {
+ currentFieldName = parser.currentName();
+ } else if (token.isValue()) {
+ if (CommonFields.DOC_COUNT.getPreferredName().equals(currentFieldName)) {
+ bucket.setDocCount(parser.longValue());
+ }
+ } else if (token == XContentParser.Token.START_OBJECT) {
+ aggregations.add(XContentParserUtils.parseTypedKeysObject(parser, Aggregation.TYPED_KEYS_DELIMITER, Aggregation.class));
+ }
+ }
+ bucket.setAggregations(new Aggregations(aggregations));
+ return bucket;
+ }
+ }
+}
diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/ParsedGeoHashGrid.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/ParsedGeoHashGrid.java
new file mode 100644
index 00000000000..4551523e0fc
--- /dev/null
+++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/ParsedGeoHashGrid.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you 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 at
+ *
+ * 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.
+ */
+
+package org.elasticsearch.search.aggregations.bucket.geogrid;
+
+import org.elasticsearch.common.geo.GeoPoint;
+import org.elasticsearch.common.xcontent.ObjectParser;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.search.aggregations.ParsedMultiBucketAggregation;
+
+import java.io.IOException;
+import java.util.List;
+
+public class ParsedGeoHashGrid extends ParsedMultiBucketAggregation implements GeoHashGrid {
+
+ @Override
+ public String getType() {
+ return GeoGridAggregationBuilder.NAME;
+ }
+
+ @Override
+ public List extends GeoHashGrid.Bucket> getBuckets() {
+ return buckets;
+ }
+
+ private static ObjectParser PARSER =
+ new ObjectParser<>(ParsedGeoHashGrid.class.getSimpleName(), true, ParsedGeoHashGrid::new);
+ static {
+ declareMultiBucketAggregationFields(PARSER, ParsedBucket::fromXContent, ParsedBucket::fromXContent);
+ }
+
+ public static ParsedGeoHashGrid fromXContent(XContentParser parser, String name) throws IOException {
+ ParsedGeoHashGrid aggregation = PARSER.parse(parser, null);
+ aggregation.setName(name);
+ return aggregation;
+ }
+
+ public static class ParsedBucket extends ParsedMultiBucketAggregation.ParsedBucket implements GeoHashGrid.Bucket {
+
+ private String geohashAsString;
+
+ @Override
+ public GeoPoint getKey() {
+ return GeoPoint.fromGeohash(geohashAsString);
+ }
+
+ @Override
+ public String getKeyAsString() {
+ return geohashAsString;
+ }
+
+ @Override
+ protected XContentBuilder keyToXContent(XContentBuilder builder) throws IOException {
+ return builder.field(CommonFields.KEY.getPreferredName(), geohashAsString);
+ }
+
+ static ParsedBucket fromXContent(XContentParser parser) throws IOException {
+ return parseXContent(parser, false, ParsedBucket::new, (p, bucket) -> bucket.geohashAsString = p.textOrNull());
+ }
+ }
+}
diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/global/ParsedGlobal.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/global/ParsedGlobal.java
new file mode 100644
index 00000000000..062752805b1
--- /dev/null
+++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/global/ParsedGlobal.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you 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 at
+ *
+ * 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.
+ */
+package org.elasticsearch.search.aggregations.bucket.global;
+
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.search.aggregations.bucket.ParsedSingleBucketAggregation;
+
+import java.io.IOException;
+
+public class ParsedGlobal extends ParsedSingleBucketAggregation implements Global {
+
+ @Override
+ public String getType() {
+ return GlobalAggregationBuilder.NAME;
+ }
+
+ public static ParsedGlobal fromXContent(XContentParser parser, final String name) throws IOException {
+ return parseXContent(parser, new ParsedGlobal(), name);
+ }
+}
diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/histogram/ParsedDateHistogram.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/histogram/ParsedDateHistogram.java
new file mode 100644
index 00000000000..ace0cb59907
--- /dev/null
+++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/histogram/ParsedDateHistogram.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you 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 at
+ *
+ * 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.
+ */
+
+package org.elasticsearch.search.aggregations.bucket.histogram;
+
+import org.elasticsearch.common.xcontent.ObjectParser;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.search.aggregations.ParsedMultiBucketAggregation;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+
+import java.io.IOException;
+import java.util.List;
+
+public class ParsedDateHistogram extends ParsedMultiBucketAggregation implements Histogram {
+
+ @Override
+ public String getType() {
+ return DateHistogramAggregationBuilder.NAME;
+ }
+
+ @Override
+ public List extends Histogram.Bucket> getBuckets() {
+ return buckets;
+ }
+
+ private static ObjectParser PARSER =
+ new ObjectParser<>(ParsedDateHistogram.class.getSimpleName(), true, ParsedDateHistogram::new);
+ static {
+ declareMultiBucketAggregationFields(PARSER,
+ parser -> ParsedBucket.fromXContent(parser, false),
+ parser -> ParsedBucket.fromXContent(parser, true));
+ }
+
+ public static ParsedDateHistogram fromXContent(XContentParser parser, String name) throws IOException {
+ ParsedDateHistogram aggregation = PARSER.parse(parser, null);
+ aggregation.setName(name);
+ return aggregation;
+ }
+
+ public static class ParsedBucket extends ParsedMultiBucketAggregation.ParsedBucket implements Histogram.Bucket {
+
+ private Long key;
+
+ @Override
+ public Object getKey() {
+ if (key != null) {
+ return new DateTime(key, DateTimeZone.UTC);
+ }
+ return null;
+ }
+
+ @Override
+ public String getKeyAsString() {
+ String keyAsString = super.getKeyAsString();
+ if (keyAsString != null) {
+ return keyAsString;
+ }
+ if (key != null) {
+ return Long.toString(key);
+ }
+ return null;
+ }
+
+ @Override
+ protected XContentBuilder keyToXContent(XContentBuilder builder) throws IOException {
+ return builder.field(CommonFields.KEY.getPreferredName(), key);
+ }
+
+ static ParsedBucket fromXContent(XContentParser parser, boolean keyed) throws IOException {
+ return parseXContent(parser, keyed, ParsedBucket::new, (p, bucket) -> bucket.key = p.longValue());
+ }
+ }
+}
diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/histogram/ParsedHistogram.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/histogram/ParsedHistogram.java
new file mode 100644
index 00000000000..6037c155886
--- /dev/null
+++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/histogram/ParsedHistogram.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you 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 at
+ *
+ * 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.
+ */
+
+package org.elasticsearch.search.aggregations.bucket.histogram;
+
+import org.elasticsearch.common.xcontent.ObjectParser;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.search.aggregations.ParsedMultiBucketAggregation;
+
+import java.io.IOException;
+import java.util.List;
+
+public class ParsedHistogram extends ParsedMultiBucketAggregation implements Histogram {
+
+ @Override
+ public String getType() {
+ return HistogramAggregationBuilder.NAME;
+ }
+
+ @Override
+ public List extends Histogram.Bucket> getBuckets() {
+ return buckets;
+ }
+
+ private static ObjectParser PARSER =
+ new ObjectParser<>(ParsedHistogram.class.getSimpleName(), true, ParsedHistogram::new);
+ static {
+ declareMultiBucketAggregationFields(PARSER,
+ parser -> ParsedBucket.fromXContent(parser, false),
+ parser -> ParsedBucket.fromXContent(parser, true));
+ }
+
+ public static ParsedHistogram fromXContent(XContentParser parser, String name) throws IOException {
+ ParsedHistogram aggregation = PARSER.parse(parser, null);
+ aggregation.setName(name);
+ return aggregation;
+ }
+
+ static class ParsedBucket extends ParsedMultiBucketAggregation.ParsedBucket implements Histogram.Bucket {
+
+ private Double key;
+
+ @Override
+ public Object getKey() {
+ return key;
+ }
+
+ @Override
+ public String getKeyAsString() {
+ String keyAsString = super.getKeyAsString();
+ if (keyAsString != null) {
+ return keyAsString;
+ }
+ if (key != null) {
+ return Double.toString(key);
+ }
+ return null;
+ }
+
+ static ParsedBucket fromXContent(XContentParser parser, boolean keyed) throws IOException {
+ return parseXContent(parser, keyed, ParsedBucket::new, (p, bucket) -> bucket.key = p.doubleValue());
+ }
+ }
+}
diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/missing/ParsedMissing.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/missing/ParsedMissing.java
new file mode 100644
index 00000000000..2897372df89
--- /dev/null
+++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/missing/ParsedMissing.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you 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 at
+ *
+ * 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.
+ */
+package org.elasticsearch.search.aggregations.bucket.missing;
+
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.search.aggregations.bucket.ParsedSingleBucketAggregation;
+
+import java.io.IOException;
+
+public class ParsedMissing extends ParsedSingleBucketAggregation implements Missing {
+
+ @Override
+ public String getType() {
+ return MissingAggregationBuilder.NAME;
+ }
+
+ public static ParsedMissing fromXContent(XContentParser parser, final String name) throws IOException {
+ return parseXContent(parser, new ParsedMissing(), name);
+ }
+}
diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/nested/ParsedNested.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/nested/ParsedNested.java
new file mode 100644
index 00000000000..f241675678c
--- /dev/null
+++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/nested/ParsedNested.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you 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 at
+ *
+ * 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.
+ */
+package org.elasticsearch.search.aggregations.bucket.nested;
+
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.search.aggregations.bucket.ParsedSingleBucketAggregation;
+
+import java.io.IOException;
+
+public class ParsedNested extends ParsedSingleBucketAggregation implements Nested {
+
+ @Override
+ public String getType() {
+ return NestedAggregationBuilder.NAME;
+ }
+
+ public static ParsedNested fromXContent(XContentParser parser, final String name) throws IOException {
+ return parseXContent(parser, new ParsedNested(), name);
+ }
+}
diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/nested/ParsedReverseNested.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/nested/ParsedReverseNested.java
new file mode 100644
index 00000000000..dec15c3eded
--- /dev/null
+++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/nested/ParsedReverseNested.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you 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 at
+ *
+ * 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.
+ */
+package org.elasticsearch.search.aggregations.bucket.nested;
+
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.search.aggregations.bucket.ParsedSingleBucketAggregation;
+
+import java.io.IOException;
+
+public class ParsedReverseNested extends ParsedSingleBucketAggregation implements Nested {
+
+ @Override
+ public String getType() {
+ return ReverseNestedAggregationBuilder.NAME;
+ }
+
+ public static ParsedReverseNested fromXContent(XContentParser parser, final String name) throws IOException {
+ return parseXContent(parser, new ParsedReverseNested(), name);
+ }
+}
diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/InternalBinaryRange.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/InternalBinaryRange.java
index 640b1cfb467..60fb5b99fb9 100644
--- a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/InternalBinaryRange.java
+++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/InternalBinaryRange.java
@@ -44,6 +44,7 @@ import static java.util.Collections.unmodifiableList;
public final class InternalBinaryRange
extends InternalMultiBucketAggregation
implements Range {
+
public static class Bucket extends InternalMultiBucketAggregation.InternalBucket implements Range.Bucket {
private final transient DocValueFormat format;
diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/ParsedBinaryRange.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/ParsedBinaryRange.java
new file mode 100644
index 00000000000..760bd23c092
--- /dev/null
+++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/ParsedBinaryRange.java
@@ -0,0 +1,168 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you 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 at
+ *
+ * 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.
+ */
+
+package org.elasticsearch.search.aggregations.bucket.range;
+
+import org.elasticsearch.common.xcontent.ObjectParser;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.common.xcontent.XContentParserUtils;
+import org.elasticsearch.search.aggregations.Aggregation;
+import org.elasticsearch.search.aggregations.Aggregations;
+import org.elasticsearch.search.aggregations.ParsedMultiBucketAggregation;
+import org.elasticsearch.search.aggregations.bucket.range.ip.IpRangeAggregationBuilder;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken;
+
+public class ParsedBinaryRange extends ParsedMultiBucketAggregation implements Range {
+
+ @Override
+ public String getType() {
+ return IpRangeAggregationBuilder.NAME;
+ }
+
+ @Override
+ public List extends Range.Bucket> getBuckets() {
+ return buckets;
+ }
+
+ private static ObjectParser PARSER =
+ new ObjectParser<>(ParsedBinaryRange.class.getSimpleName(), true, ParsedBinaryRange::new);
+ static {
+ declareMultiBucketAggregationFields(PARSER,
+ parser -> ParsedBucket.fromXContent(parser, false),
+ parser -> ParsedBucket.fromXContent(parser, true));
+ }
+
+ public static ParsedBinaryRange fromXContent(XContentParser parser, String name) throws IOException {
+ ParsedBinaryRange aggregation = PARSER.parse(parser, null);
+ aggregation.setName(name);
+ return aggregation;
+ }
+
+ public static class ParsedBucket extends ParsedMultiBucketAggregation.ParsedBucket implements Range.Bucket {
+
+ private String key;
+ private String from;
+ private String to;
+
+ @Override
+ public Object getKey() {
+ return key;
+ }
+
+ @Override
+ public String getKeyAsString() {
+ return key;
+ }
+
+ @Override
+ public Object getFrom() {
+ return from;
+ }
+
+ @Override
+ public String getFromAsString() {
+ return from;
+ }
+
+ @Override
+ public Object getTo() {
+ return to;
+ }
+
+ @Override
+ public String getToAsString() {
+ return to;
+ }
+
+ @Override
+ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+ if (isKeyed()) {
+ builder.startObject(key != null ? key : rangeKey(from, to));
+ } else {
+ builder.startObject();
+ if (key != null) {
+ builder.field(CommonFields.KEY.getPreferredName(), key);
+ }
+ }
+ if (from != null) {
+ builder.field(CommonFields.FROM.getPreferredName(), getFrom());
+ }
+ if (to != null) {
+ builder.field(CommonFields.TO.getPreferredName(), getTo());
+ }
+ builder.field(CommonFields.DOC_COUNT.getPreferredName(), getDocCount());
+ getAggregations().toXContentInternal(builder, params);
+ builder.endObject();
+ return builder;
+ }
+
+ static ParsedBucket fromXContent(final XContentParser parser, final boolean keyed) throws IOException {
+ final ParsedBucket bucket = new ParsedBucket();
+ bucket.setKeyed(keyed);
+ XContentParser.Token token = parser.currentToken();
+ String currentFieldName = parser.currentName();
+
+ String rangeKey = null;
+ if (keyed) {
+ ensureExpectedToken(XContentParser.Token.FIELD_NAME, token, parser::getTokenLocation);
+ rangeKey = currentFieldName;
+ ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser::getTokenLocation);
+ }
+
+ List aggregations = new ArrayList<>();
+ while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
+ if (token == XContentParser.Token.FIELD_NAME) {
+ currentFieldName = parser.currentName();
+ } else if (token.isValue()) {
+ if (CommonFields.KEY.getPreferredName().equals(currentFieldName)) {
+ bucket.key = parser.text();
+ } else if (CommonFields.DOC_COUNT.getPreferredName().equals(currentFieldName)) {
+ bucket.setDocCount(parser.longValue());
+ } else if (CommonFields.FROM.getPreferredName().equals(currentFieldName)) {
+ bucket.from = parser.text();
+ } else if (CommonFields.TO.getPreferredName().equals(currentFieldName)) {
+ bucket.to = parser.text();
+ }
+ } else if (token == XContentParser.Token.START_OBJECT) {
+ aggregations.add(XContentParserUtils.parseTypedKeysObject(parser, Aggregation.TYPED_KEYS_DELIMITER, Aggregation.class));
+ }
+ }
+ bucket.setAggregations(new Aggregations(aggregations));
+
+ if (keyed) {
+ if (rangeKey(bucket.from, bucket.to).equals(rangeKey)) {
+ bucket.key = null;
+ } else {
+ bucket.key = rangeKey;
+ }
+ }
+ return bucket;
+ }
+
+ private static String rangeKey(String from, String to) {
+ return (from == null ? "*" : from) + '-' + (to == null ? "*" : to);
+ }
+ }
+}
diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/ParsedRange.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/ParsedRange.java
new file mode 100644
index 00000000000..85066296d5f
--- /dev/null
+++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/ParsedRange.java
@@ -0,0 +1,193 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you 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 at
+ *
+ * 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.
+ */
+
+package org.elasticsearch.search.aggregations.bucket.range;
+
+import org.elasticsearch.common.CheckedFunction;
+import org.elasticsearch.common.xcontent.ObjectParser;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.common.xcontent.XContentParserUtils;
+import org.elasticsearch.search.aggregations.Aggregation;
+import org.elasticsearch.search.aggregations.Aggregations;
+import org.elasticsearch.search.aggregations.ParsedMultiBucketAggregation;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Supplier;
+
+import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken;
+
+public class ParsedRange extends ParsedMultiBucketAggregation implements Range {
+
+ @Override
+ public String getType() {
+ return RangeAggregationBuilder.NAME;
+ }
+
+ @Override
+ public List extends Range.Bucket> getBuckets() {
+ return buckets;
+ }
+
+ protected static void declareParsedRangeFields(final ObjectParser extends ParsedRange, Void> objectParser,
+ final CheckedFunction bucketParser,
+ final CheckedFunction keyedBucketParser) {
+ declareMultiBucketAggregationFields(objectParser, bucketParser::apply, keyedBucketParser::apply);
+ }
+
+ private static ObjectParser PARSER =
+ new ObjectParser<>(ParsedRange.class.getSimpleName(), true, ParsedRange::new);
+ static {
+ declareParsedRangeFields(PARSER,
+ parser -> ParsedBucket.fromXContent(parser, false),
+ parser -> ParsedBucket.fromXContent(parser, true));
+ }
+
+ public static ParsedRange fromXContent(XContentParser parser, String name) throws IOException {
+ ParsedRange aggregation = PARSER.parse(parser, null);
+ aggregation.setName(name);
+ return aggregation;
+ }
+
+ public static class ParsedBucket extends ParsedMultiBucketAggregation.ParsedBucket implements Range.Bucket {
+
+ protected String key;
+ protected double from = Double.NEGATIVE_INFINITY;
+ protected String fromAsString;
+ protected double to = Double.POSITIVE_INFINITY;
+ protected String toAsString;
+
+ @Override
+ public String getKey() {
+ return getKeyAsString();
+ }
+
+ @Override
+ public String getKeyAsString() {
+ String keyAsString = super.getKeyAsString();
+ if (keyAsString != null) {
+ return keyAsString;
+ }
+ return key;
+ }
+
+ @Override
+ public Object getFrom() {
+ return from;
+ }
+
+ @Override
+ public String getFromAsString() {
+ if (fromAsString != null) {
+ return fromAsString;
+ }
+ return doubleAsString(from);
+ }
+
+ @Override
+ public Object getTo() {
+ return to;
+ }
+
+ @Override
+ public String getToAsString() {
+ if (toAsString != null) {
+ return toAsString;
+ }
+ return doubleAsString(to);
+ }
+
+ @Override
+ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+ if (isKeyed()) {
+ builder.startObject(key);
+ } else {
+ builder.startObject();
+ builder.field(CommonFields.KEY.getPreferredName(), key);
+ }
+ if (Double.isInfinite(from) == false) {
+ builder.field(CommonFields.FROM.getPreferredName(), from);
+ if (fromAsString != null) {
+ builder.field(CommonFields.FROM_AS_STRING.getPreferredName(), fromAsString);
+ }
+ }
+ if (Double.isInfinite(to) == false) {
+ builder.field(CommonFields.TO.getPreferredName(), to);
+ if (toAsString != null) {
+ builder.field(CommonFields.TO_AS_STRING.getPreferredName(), toAsString);
+ }
+ }
+ builder.field(CommonFields.DOC_COUNT.getPreferredName(), getDocCount());
+ getAggregations().toXContentInternal(builder, params);
+ builder.endObject();
+ return builder;
+ }
+
+ private static String doubleAsString(double d) {
+ return Double.isInfinite(d) ? null : Double.toString(d);
+ }
+
+ protected static B parseRangeBucketXContent(final XContentParser parser,
+ final Supplier bucketSupplier,
+ final boolean keyed) throws IOException {
+ final B bucket = bucketSupplier.get();
+ bucket.setKeyed(keyed);
+ XContentParser.Token token = parser.currentToken();
+ String currentFieldName = parser.currentName();
+ if (keyed) {
+ ensureExpectedToken(XContentParser.Token.FIELD_NAME, token, parser::getTokenLocation);
+ bucket.key = currentFieldName;
+ ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser::getTokenLocation);
+ }
+
+ List aggregations = new ArrayList<>();
+ while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
+ if (token == XContentParser.Token.FIELD_NAME) {
+ currentFieldName = parser.currentName();
+ } else if (token.isValue()) {
+ if (CommonFields.KEY_AS_STRING.getPreferredName().equals(currentFieldName)) {
+ bucket.setKeyAsString(parser.text());
+ } else if (CommonFields.KEY.getPreferredName().equals(currentFieldName)) {
+ bucket.key = parser.text();
+ } else if (CommonFields.DOC_COUNT.getPreferredName().equals(currentFieldName)) {
+ bucket.setDocCount(parser.longValue());
+ } else if (CommonFields.FROM.getPreferredName().equals(currentFieldName)) {
+ bucket.from = parser.doubleValue();
+ } else if (CommonFields.FROM_AS_STRING.getPreferredName().equals(currentFieldName)) {
+ bucket.fromAsString = parser.text();
+ } else if (CommonFields.TO.getPreferredName().equals(currentFieldName)) {
+ bucket.to = parser.doubleValue();
+ } else if (CommonFields.TO_AS_STRING.getPreferredName().equals(currentFieldName)) {
+ bucket.toAsString = parser.text();
+ }
+ } else if (token == XContentParser.Token.START_OBJECT) {
+ aggregations.add(XContentParserUtils.parseTypedKeysObject(parser, Aggregation.TYPED_KEYS_DELIMITER, Aggregation.class));
+ }
+ }
+ bucket.setAggregations(new Aggregations(aggregations));
+ return bucket;
+ }
+
+ static ParsedBucket fromXContent(final XContentParser parser, final boolean keyed) throws IOException {
+ return parseRangeBucketXContent(parser, ParsedBucket::new, keyed);
+ }
+ }
+}
diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/date/ParsedDateRange.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/date/ParsedDateRange.java
new file mode 100644
index 00000000000..b8f2f008cec
--- /dev/null
+++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/date/ParsedDateRange.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you 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 at
+ *
+ * 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.
+ */
+
+package org.elasticsearch.search.aggregations.bucket.range.date;
+
+import org.elasticsearch.common.xcontent.ObjectParser;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.search.aggregations.bucket.range.ParsedRange;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+
+import java.io.IOException;
+
+public class ParsedDateRange extends ParsedRange {
+
+ @Override
+ public String getType() {
+ return DateRangeAggregationBuilder.NAME;
+ }
+
+ private static ObjectParser PARSER =
+ new ObjectParser<>(ParsedDateRange.class.getSimpleName(), true, ParsedDateRange::new);
+ static {
+ declareParsedRangeFields(PARSER,
+ parser -> ParsedBucket.fromXContent(parser, false),
+ parser -> ParsedBucket.fromXContent(parser, true));
+ }
+
+ public static ParsedDateRange fromXContent(XContentParser parser, String name) throws IOException {
+ ParsedDateRange aggregation = PARSER.parse(parser, null);
+ aggregation.setName(name);
+ return aggregation;
+ }
+
+ public static class ParsedBucket extends ParsedRange.ParsedBucket {
+
+ @Override
+ public Object getFrom() {
+ return doubleAsDateTime(from);
+ }
+
+ @Override
+ public Object getTo() {
+ return doubleAsDateTime(to);
+ }
+
+ private static DateTime doubleAsDateTime(Double d) {
+ if (d == null || Double.isInfinite(d)) {
+ return null;
+ }
+ return new DateTime(d.longValue(), DateTimeZone.UTC);
+ }
+
+ static ParsedBucket fromXContent(final XContentParser parser, final boolean keyed) throws IOException {
+ return parseRangeBucketXContent(parser, ParsedBucket::new, keyed);
+ }
+ }
+}
diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/geodistance/ParsedGeoDistance.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/geodistance/ParsedGeoDistance.java
new file mode 100644
index 00000000000..a926499e924
--- /dev/null
+++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/geodistance/ParsedGeoDistance.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you 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 at
+ *
+ * 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.
+ */
+
+package org.elasticsearch.search.aggregations.bucket.range.geodistance;
+
+import org.elasticsearch.common.xcontent.ObjectParser;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.search.aggregations.bucket.range.ParsedRange;
+
+import java.io.IOException;
+
+public class ParsedGeoDistance extends ParsedRange {
+
+ @Override
+ public String getType() {
+ return GeoDistanceAggregationBuilder.NAME;
+ }
+
+ private static ObjectParser PARSER =
+ new ObjectParser<>(ParsedGeoDistance.class.getSimpleName(), true, ParsedGeoDistance::new);
+ static {
+ declareParsedRangeFields(PARSER,
+ parser -> ParsedBucket.fromXContent(parser, false),
+ parser -> ParsedBucket.fromXContent(parser, true));
+ }
+
+ public static ParsedGeoDistance fromXContent(XContentParser parser, String name) throws IOException {
+ ParsedGeoDistance aggregation = PARSER.parse(parser, null);
+ aggregation.setName(name);
+ return aggregation;
+ }
+
+ public static class ParsedBucket extends ParsedRange.ParsedBucket {
+
+ static ParsedBucket fromXContent(final XContentParser parser, final boolean keyed) throws IOException {
+ return parseRangeBucketXContent(parser, ParsedBucket::new, keyed);
+ }
+ }
+}
diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/sampler/InternalSampler.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/sampler/InternalSampler.java
index 0c02f1ae935..1a04133e82b 100644
--- a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/sampler/InternalSampler.java
+++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/sampler/InternalSampler.java
@@ -29,6 +29,8 @@ import java.util.Map;
public class InternalSampler extends InternalSingleBucketAggregation implements Sampler {
public static final String NAME = "mapped_sampler";
+ // InternalSampler and UnmappedSampler share the same parser name, so we use this when identifying the aggregation type
+ public static final String PARSER_NAME = "sampler";
InternalSampler(String name, long docCount, InternalAggregations subAggregations, List pipelineAggregators,
Map metaData) {
@@ -48,8 +50,8 @@ public class InternalSampler extends InternalSingleBucketAggregation implements
}
@Override
- protected String getType() {
- return "sampler";
+ public String getType() {
+ return PARSER_NAME;
}
@Override
diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/sampler/ParsedSampler.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/sampler/ParsedSampler.java
new file mode 100644
index 00000000000..3d5e946bead
--- /dev/null
+++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/sampler/ParsedSampler.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you 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 at
+ *
+ * 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.
+ */
+package org.elasticsearch.search.aggregations.bucket.sampler;
+
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.search.aggregations.bucket.ParsedSingleBucketAggregation;
+
+import java.io.IOException;
+
+public class ParsedSampler extends ParsedSingleBucketAggregation implements Sampler {
+
+ @Override
+ public String getType() {
+ return InternalSampler.PARSER_NAME;
+ }
+
+ public static ParsedSampler fromXContent(XContentParser parser, final String name) throws IOException {
+ return parseXContent(parser, new ParsedSampler(), name);
+ }
+}
diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/significant/InternalSignificantTerms.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/significant/InternalSignificantTerms.java
index acb655b19b4..ac60829c4ca 100644
--- a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/significant/InternalSignificantTerms.java
+++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/significant/InternalSignificantTerms.java
@@ -44,8 +44,8 @@ import java.util.Objects;
public abstract class InternalSignificantTerms, B extends InternalSignificantTerms.Bucket>
extends InternalMultiBucketAggregation implements SignificantTerms, ToXContent {
- private static final String SCORE = "score";
- private static final String BG_COUNT = "bg_count";
+ public static final String SCORE = "score";
+ public static final String BG_COUNT = "bg_count";
@SuppressWarnings("PMD.ConstructorCallsOverridableMethod")
public abstract static class Bucket> extends InternalMultiBucketAggregation.InternalBucket
diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/significant/ParsedSignificantLongTerms.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/significant/ParsedSignificantLongTerms.java
new file mode 100644
index 00000000000..2d512632f50
--- /dev/null
+++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/significant/ParsedSignificantLongTerms.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you 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 at
+ *
+ * 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.
+ */
+
+package org.elasticsearch.search.aggregations.bucket.significant;
+
+import org.elasticsearch.common.xcontent.ObjectParser;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentParser;
+
+import java.io.IOException;
+
+public class ParsedSignificantLongTerms extends ParsedSignificantTerms {
+
+ @Override
+ public String getType() {
+ return SignificantLongTerms.NAME;
+ }
+
+ private static ObjectParser PARSER =
+ new ObjectParser<>(ParsedSignificantLongTerms.class.getSimpleName(), true, ParsedSignificantLongTerms::new);
+ static {
+ declareParsedSignificantTermsFields(PARSER, ParsedBucket::fromXContent);
+ }
+
+ public static ParsedSignificantLongTerms fromXContent(XContentParser parser, String name) throws IOException {
+ ParsedSignificantLongTerms aggregation = PARSER.parse(parser, null);
+ aggregation.setName(name);
+ return aggregation;
+ }
+
+ public static class ParsedBucket extends ParsedSignificantTerms.ParsedBucket {
+
+ private Long key;
+
+ @Override
+ public Object getKey() {
+ return key;
+ }
+
+ @Override
+ public String getKeyAsString() {
+ String keyAsString = super.getKeyAsString();
+ if (keyAsString != null) {
+ return keyAsString;
+ }
+ return Long.toString(key);
+ }
+
+ public Number getKeyAsNumber() {
+ return key;
+ }
+
+ @Override
+ protected XContentBuilder keyToXContent(XContentBuilder builder) throws IOException {
+ builder.field(CommonFields.KEY.getPreferredName(), key);
+ if (super.getKeyAsString() != null) {
+ builder.field(CommonFields.KEY_AS_STRING.getPreferredName(), getKeyAsString());
+ }
+ return builder;
+ }
+
+ static ParsedBucket fromXContent(XContentParser parser) throws IOException {
+ return parseSignificantTermsBucketXContent(parser, new ParsedBucket(), (p, bucket) -> bucket.key = p.longValue());
+ }
+ }
+}
diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/significant/ParsedSignificantStringTerms.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/significant/ParsedSignificantStringTerms.java
new file mode 100644
index 00000000000..fb1c7728e0c
--- /dev/null
+++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/significant/ParsedSignificantStringTerms.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you 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 at
+ *
+ * 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.
+ */
+
+package org.elasticsearch.search.aggregations.bucket.significant;
+
+import org.apache.lucene.util.BytesRef;
+import org.elasticsearch.common.xcontent.ObjectParser;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentParser;
+
+import java.io.IOException;
+
+public class ParsedSignificantStringTerms extends ParsedSignificantTerms {
+
+ @Override
+ public String getType() {
+ return SignificantStringTerms.NAME;
+ }
+
+ private static ObjectParser PARSER =
+ new ObjectParser<>(ParsedSignificantStringTerms.class.getSimpleName(), true, ParsedSignificantStringTerms::new);
+ static {
+ declareParsedSignificantTermsFields(PARSER, ParsedBucket::fromXContent);
+ }
+
+ public static ParsedSignificantStringTerms fromXContent(XContentParser parser, String name) throws IOException {
+ ParsedSignificantStringTerms aggregation = PARSER.parse(parser, null);
+ aggregation.setName(name);
+ return aggregation;
+ }
+
+ public static class ParsedBucket extends ParsedSignificantTerms.ParsedBucket {
+
+ private BytesRef key;
+
+ @Override
+ public Object getKey() {
+ return getKeyAsString();
+ }
+
+ @Override
+ public String getKeyAsString() {
+ String keyAsString = super.getKeyAsString();
+ if (keyAsString != null) {
+ return keyAsString;
+ }
+ return key.utf8ToString();
+ }
+
+ public Number getKeyAsNumber() {
+ return Double.parseDouble(key.utf8ToString());
+ }
+
+ @Override
+ protected XContentBuilder keyToXContent(XContentBuilder builder) throws IOException {
+ return builder.field(CommonFields.KEY.getPreferredName(), getKey());
+ }
+
+ static ParsedBucket fromXContent(XContentParser parser) throws IOException {
+ return parseSignificantTermsBucketXContent(parser, new ParsedBucket(), (p, bucket) -> bucket.key = p.utf8BytesOrNull());
+ }
+ }
+}
diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/significant/ParsedSignificantTerms.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/significant/ParsedSignificantTerms.java
new file mode 100644
index 00000000000..56be0aa6071
--- /dev/null
+++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/significant/ParsedSignificantTerms.java
@@ -0,0 +1,166 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you 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 at
+ *
+ * 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.
+ */
+
+package org.elasticsearch.search.aggregations.bucket.significant;
+
+import org.elasticsearch.common.CheckedBiConsumer;
+import org.elasticsearch.common.CheckedFunction;
+import org.elasticsearch.common.xcontent.ObjectParser;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.common.xcontent.XContentParserUtils;
+import org.elasticsearch.search.aggregations.Aggregation;
+import org.elasticsearch.search.aggregations.Aggregations;
+import org.elasticsearch.search.aggregations.ParsedMultiBucketAggregation;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+public abstract class ParsedSignificantTerms extends ParsedMultiBucketAggregation
+ implements SignificantTerms {
+
+ private Map bucketMap;
+ protected long subsetSize;
+
+ protected long getSubsetSize() {
+ return subsetSize;
+ }
+
+ @Override
+ public List extends SignificantTerms.Bucket> getBuckets() {
+ return buckets;
+ }
+
+ @Override
+ public SignificantTerms.Bucket getBucketByKey(String term) {
+ if (bucketMap == null) {
+ bucketMap = buckets.stream().collect(Collectors.toMap(SignificantTerms.Bucket::getKeyAsString, Function.identity()));
+ }
+ return bucketMap.get(term);
+ }
+
+ @Override
+ public Iterator iterator() {
+ return buckets.stream().map(bucket -> (SignificantTerms.Bucket) bucket).collect(Collectors.toList()).iterator();
+ }
+
+ @Override
+ protected XContentBuilder doXContentBody(XContentBuilder builder, Params params) throws IOException {
+ builder.field(CommonFields.DOC_COUNT.getPreferredName(), subsetSize);
+ builder.startArray(CommonFields.BUCKETS.getPreferredName());
+ for (SignificantTerms.Bucket bucket : buckets) {
+ bucket.toXContent(builder, params);
+ }
+ builder.endArray();
+ return builder;
+ }
+
+ static void declareParsedSignificantTermsFields(final ObjectParser extends ParsedSignificantTerms, Void> objectParser,
+ final CheckedFunction bucketParser) {
+ declareMultiBucketAggregationFields(objectParser, bucketParser::apply, bucketParser::apply);
+ objectParser.declareLong((parsedTerms, value) -> parsedTerms.subsetSize = value , CommonFields.DOC_COUNT);
+ }
+
+ public abstract static class ParsedBucket extends ParsedMultiBucketAggregation.ParsedBucket implements SignificantTerms.Bucket {
+
+ protected long subsetDf;
+ protected long supersetDf;
+ protected double score;
+
+ @Override
+ public long getDocCount() {
+ return getSubsetDf();
+ }
+
+ @Override
+ public long getSubsetDf() {
+ return subsetDf;
+ }
+
+ @Override
+ public long getSupersetDf() {
+ return supersetDf;
+ }
+
+ @Override
+ public double getSignificanceScore() {
+ return score;
+ }
+
+ @Override
+ public long getSupersetSize() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public long getSubsetSize() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public final XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+ builder.startObject();
+ keyToXContent(builder);
+ builder.field(CommonFields.DOC_COUNT.getPreferredName(), getDocCount());
+ builder.field(InternalSignificantTerms.SCORE, getSignificanceScore());
+ builder.field(InternalSignificantTerms.BG_COUNT, getSupersetDf());
+ getAggregations().toXContentInternal(builder, params);
+ builder.endObject();
+ return builder;
+ }
+
+ protected abstract XContentBuilder keyToXContent(XContentBuilder builder) throws IOException;
+
+ static B parseSignificantTermsBucketXContent(final XContentParser parser, final B bucket,
+ final CheckedBiConsumer keyConsumer) throws IOException {
+
+ final List aggregations = new ArrayList<>();
+ XContentParser.Token token;
+ String currentFieldName = parser.currentName();
+ while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
+ if (token == XContentParser.Token.FIELD_NAME) {
+ currentFieldName = parser.currentName();
+ } else if (token.isValue()) {
+ if (CommonFields.KEY_AS_STRING.getPreferredName().equals(currentFieldName)) {
+ bucket.setKeyAsString(parser.text());
+ } else if (CommonFields.KEY.getPreferredName().equals(currentFieldName)) {
+ keyConsumer.accept(parser, bucket);
+ } else if (CommonFields.DOC_COUNT.getPreferredName().equals(currentFieldName)) {
+ long value = parser.longValue();
+ bucket.subsetDf = value;
+ bucket.setDocCount(value);
+ } else if (InternalSignificantTerms.SCORE.equals(currentFieldName)) {
+ bucket.score = parser.longValue();
+ } else if (InternalSignificantTerms.BG_COUNT.equals(currentFieldName)) {
+ bucket.supersetDf = parser.longValue();
+ }
+ } else if (token == XContentParser.Token.START_OBJECT) {
+ aggregations.add(XContentParserUtils.parseTypedKeysObject(parser, Aggregation.TYPED_KEYS_DELIMITER, Aggregation.class));
+ }
+ }
+ bucket.setAggregations(new Aggregations(aggregations));
+ return bucket;
+ }
+ }
+}
diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/significant/UnmappedSignificantTerms.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/significant/UnmappedSignificantTerms.java
index 7252909bf19..66fc171bbe3 100644
--- a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/significant/UnmappedSignificantTerms.java
+++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/significant/UnmappedSignificantTerms.java
@@ -79,7 +79,7 @@ public class UnmappedSignificantTerms extends InternalSignificantTerms PARSER =
+ new ObjectParser<>(ParsedDoubleTerms.class.getSimpleName(), true, ParsedDoubleTerms::new);
+ static {
+ declareParsedTermsFields(PARSER, ParsedBucket::fromXContent);
+ }
+
+ public static ParsedDoubleTerms fromXContent(XContentParser parser, String name) throws IOException {
+ ParsedDoubleTerms aggregation = PARSER.parse(parser, null);
+ aggregation.setName(name);
+ return aggregation;
+ }
+
+ public static class ParsedBucket extends ParsedTerms.ParsedBucket {
+
+ private Double key;
+
+ @Override
+ public Object getKey() {
+ return key;
+ }
+
+ @Override
+ public String getKeyAsString() {
+ String keyAsString = super.getKeyAsString();
+ if (keyAsString != null) {
+ return keyAsString;
+ }
+ if (key != null) {
+ return Double.toString(key);
+ }
+ return null;
+ }
+
+ public Number getKeyAsNumber() {
+ return key;
+ }
+
+ @Override
+ protected XContentBuilder keyToXContent(XContentBuilder builder) throws IOException {
+ builder.field(CommonFields.KEY.getPreferredName(), key);
+ if (super.getKeyAsString() != null) {
+ builder.field(CommonFields.KEY_AS_STRING.getPreferredName(), getKeyAsString());
+ }
+ return builder;
+ }
+
+ static ParsedBucket fromXContent(XContentParser parser) throws IOException {
+ return parseTermsBucketXContent(parser, ParsedBucket::new, (p, bucket) -> bucket.key = p.doubleValue());
+ }
+ }
+}
diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/terms/ParsedLongTerms.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/terms/ParsedLongTerms.java
new file mode 100644
index 00000000000..b5869fc6ee2
--- /dev/null
+++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/terms/ParsedLongTerms.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you 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 at
+ *
+ * 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.
+ */
+
+package org.elasticsearch.search.aggregations.bucket.terms;
+
+import org.elasticsearch.common.xcontent.ObjectParser;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentParser;
+
+import java.io.IOException;
+
+public class ParsedLongTerms extends ParsedTerms {
+
+ @Override
+ public String getType() {
+ return LongTerms.NAME;
+ }
+
+ private static ObjectParser PARSER =
+ new ObjectParser<>(ParsedLongTerms.class.getSimpleName(), true, ParsedLongTerms::new);
+ static {
+ declareParsedTermsFields(PARSER, ParsedBucket::fromXContent);
+ }
+
+ public static ParsedLongTerms fromXContent(XContentParser parser, String name) throws IOException {
+ ParsedLongTerms aggregation = PARSER.parse(parser, null);
+ aggregation.setName(name);
+ return aggregation;
+ }
+
+ public static class ParsedBucket extends ParsedTerms.ParsedBucket {
+
+ private Long key;
+
+ @Override
+ public Object getKey() {
+ return key;
+ }
+
+ @Override
+ public String getKeyAsString() {
+ String keyAsString = super.getKeyAsString();
+ if (keyAsString != null) {
+ return keyAsString;
+ }
+ if (key != null) {
+ return Long.toString(key);
+ }
+ return null;
+ }
+
+ public Number getKeyAsNumber() {
+ return key;
+ }
+
+ @Override
+ protected XContentBuilder keyToXContent(XContentBuilder builder) throws IOException {
+ builder.field(CommonFields.KEY.getPreferredName(), key);
+ if (super.getKeyAsString() != null) {
+ builder.field(CommonFields.KEY_AS_STRING.getPreferredName(), getKeyAsString());
+ }
+ return builder;
+ }
+
+ static ParsedBucket fromXContent(XContentParser parser) throws IOException {
+ return parseTermsBucketXContent(parser, ParsedBucket::new, (p, bucket) -> bucket.key = p.longValue());
+ }
+ }
+}
diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/terms/ParsedStringTerms.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/terms/ParsedStringTerms.java
new file mode 100644
index 00000000000..792365d6b1a
--- /dev/null
+++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/terms/ParsedStringTerms.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you 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 at
+ *
+ * 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.
+ */
+
+package org.elasticsearch.search.aggregations.bucket.terms;
+
+import org.apache.lucene.util.BytesRef;
+import org.elasticsearch.common.xcontent.ObjectParser;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentParser;
+
+import java.io.IOException;
+
+public class ParsedStringTerms extends ParsedTerms {
+
+ @Override
+ public String getType() {
+ return StringTerms.NAME;
+ }
+
+ private static ObjectParser PARSER =
+ new ObjectParser<>(ParsedStringTerms.class.getSimpleName(), true, ParsedStringTerms::new);
+ static {
+ declareParsedTermsFields(PARSER, ParsedBucket::fromXContent);
+ }
+
+ public static ParsedStringTerms fromXContent(XContentParser parser, String name) throws IOException {
+ ParsedStringTerms aggregation = PARSER.parse(parser, null);
+ aggregation.setName(name);
+ return aggregation;
+ }
+
+ public static class ParsedBucket extends ParsedTerms.ParsedBucket {
+
+ private BytesRef key;
+
+ @Override
+ public Object getKey() {
+ return getKeyAsString();
+ }
+
+ @Override
+ public String getKeyAsString() {
+ String keyAsString = super.getKeyAsString();
+ if (keyAsString != null) {
+ return keyAsString;
+ }
+ if (key != null) {
+ return key.utf8ToString();
+ }
+ return null;
+ }
+
+ public Number getKeyAsNumber() {
+ if (key != null) {
+ return Double.parseDouble(key.utf8ToString());
+ }
+ return null;
+ }
+
+ @Override
+ protected XContentBuilder keyToXContent(XContentBuilder builder) throws IOException {
+ return builder.field(CommonFields.KEY.getPreferredName(), getKey());
+ }
+
+ static ParsedBucket fromXContent(XContentParser parser) throws IOException {
+ return parseTermsBucketXContent(parser, ParsedBucket::new, (p, bucket) -> bucket.key = p.utf8BytesOrNull());
+ }
+ }
+}
diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/terms/ParsedTerms.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/terms/ParsedTerms.java
new file mode 100644
index 00000000000..821bb000e33
--- /dev/null
+++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/terms/ParsedTerms.java
@@ -0,0 +1,146 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you 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 at
+ *
+ * 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.
+ */
+
+package org.elasticsearch.search.aggregations.bucket.terms;
+
+import org.elasticsearch.common.CheckedBiConsumer;
+import org.elasticsearch.common.CheckedFunction;
+import org.elasticsearch.common.xcontent.ObjectParser;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.common.xcontent.XContentParserUtils;
+import org.elasticsearch.search.aggregations.Aggregation;
+import org.elasticsearch.search.aggregations.Aggregations;
+import org.elasticsearch.search.aggregations.ParsedMultiBucketAggregation;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Supplier;
+
+import static org.elasticsearch.search.aggregations.bucket.terms.InternalTerms.DOC_COUNT_ERROR_UPPER_BOUND_FIELD_NAME;
+import static org.elasticsearch.search.aggregations.bucket.terms.InternalTerms.SUM_OF_OTHER_DOC_COUNTS;
+
+public abstract class ParsedTerms extends ParsedMultiBucketAggregation implements Terms {
+
+ protected long docCountErrorUpperBound;
+ protected long sumOtherDocCount;
+
+ @Override
+ public long getDocCountError() {
+ return docCountErrorUpperBound;
+ }
+
+ @Override
+ public long getSumOfOtherDocCounts() {
+ return sumOtherDocCount;
+ }
+
+ @Override
+ public List extends Terms.Bucket> getBuckets() {
+ return buckets;
+ }
+
+ @Override
+ public Terms.Bucket getBucketByKey(String term) {
+ for (Terms.Bucket bucket : getBuckets()) {
+ if (bucket.getKeyAsString().equals(term)) {
+ return bucket;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ protected XContentBuilder doXContentBody(XContentBuilder builder, Params params) throws IOException {
+ builder.field(DOC_COUNT_ERROR_UPPER_BOUND_FIELD_NAME.getPreferredName(), getDocCountError());
+ builder.field(SUM_OF_OTHER_DOC_COUNTS.getPreferredName(), getSumOfOtherDocCounts());
+ builder.startArray(CommonFields.BUCKETS.getPreferredName());
+ for (Terms.Bucket bucket : getBuckets()) {
+ bucket.toXContent(builder, params);
+ }
+ builder.endArray();
+ return builder;
+ }
+
+ static void declareParsedTermsFields(final ObjectParser extends ParsedTerms, Void> objectParser,
+ final CheckedFunction bucketParser) {
+ declareMultiBucketAggregationFields(objectParser, bucketParser::apply, bucketParser::apply);
+ objectParser.declareLong((parsedTerms, value) -> parsedTerms.docCountErrorUpperBound = value ,
+ DOC_COUNT_ERROR_UPPER_BOUND_FIELD_NAME);
+ objectParser.declareLong((parsedTerms, value) -> parsedTerms.sumOtherDocCount = value,
+ SUM_OF_OTHER_DOC_COUNTS);
+ }
+
+ public abstract static class ParsedBucket extends ParsedMultiBucketAggregation.ParsedBucket implements Terms.Bucket {
+
+ boolean showDocCountError = false;
+ protected long docCountError;
+
+ @Override
+ public long getDocCountError() {
+ return docCountError;
+ }
+
+ @Override
+ public final XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+ builder.startObject();
+ keyToXContent(builder);
+ builder.field(CommonFields.DOC_COUNT.getPreferredName(), getDocCount());
+ if (showDocCountError) {
+ builder.field(DOC_COUNT_ERROR_UPPER_BOUND_FIELD_NAME.getPreferredName(), getDocCountError());
+ }
+ getAggregations().toXContentInternal(builder, params);
+ builder.endObject();
+ return builder;
+ }
+
+
+ static B parseTermsBucketXContent(final XContentParser parser, final Supplier bucketSupplier,
+ final CheckedBiConsumer keyConsumer)
+ throws IOException {
+
+ final B bucket = bucketSupplier.get();
+ final List aggregations = new ArrayList<>();
+
+ XContentParser.Token token;
+ String currentFieldName = parser.currentName();
+ while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
+ if (token == XContentParser.Token.FIELD_NAME) {
+ currentFieldName = parser.currentName();
+ } else if (token.isValue()) {
+ if (CommonFields.KEY_AS_STRING.getPreferredName().equals(currentFieldName)) {
+ bucket.setKeyAsString(parser.text());
+ } else if (CommonFields.KEY.getPreferredName().equals(currentFieldName)) {
+ keyConsumer.accept(parser, bucket);
+ } else if (CommonFields.DOC_COUNT.getPreferredName().equals(currentFieldName)) {
+ bucket.setDocCount(parser.longValue());
+ } else if (DOC_COUNT_ERROR_UPPER_BOUND_FIELD_NAME.getPreferredName().equals(currentFieldName)) {
+ bucket.docCountError = parser.longValue();
+ bucket.showDocCountError = true;
+ }
+ } else if (token == XContentParser.Token.START_OBJECT) {
+ aggregations.add(XContentParserUtils.parseTypedKeysObject(parser, Aggregation.TYPED_KEYS_DELIMITER, Aggregation.class));
+ }
+ }
+ bucket.setAggregations(new Aggregations(aggregations));
+ return bucket;
+ }
+ }
+}
diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/terms/UnmappedTerms.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/terms/UnmappedTerms.java
index 6362f8c347b..595991dac06 100644
--- a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/terms/UnmappedTerms.java
+++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/terms/UnmappedTerms.java
@@ -74,7 +74,7 @@ public class UnmappedTerms extends InternalTerms objectParser,
+ double defaultNullValue) {
+ declareAggregationFields(objectParser);
+ objectParser.declareField(ParsedSingleValueNumericMetricsAggregation::setValue,
+ (parser, context) -> parseDouble(parser, defaultNullValue), CommonFields.VALUE, ValueType.DOUBLE_OR_NULL);
+ objectParser.declareString(ParsedSingleValueNumericMetricsAggregation::setValueAsString, CommonFields.VALUE_AS_STRING);
+ }
+}
diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/metrics/avg/ParsedAvg.java b/core/src/main/java/org/elasticsearch/search/aggregations/metrics/avg/ParsedAvg.java
new file mode 100644
index 00000000000..16d91bd08f0
--- /dev/null
+++ b/core/src/main/java/org/elasticsearch/search/aggregations/metrics/avg/ParsedAvg.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you 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 at
+ *
+ * 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.
+ */
+
+package org.elasticsearch.search.aggregations.metrics.avg;
+
+import org.elasticsearch.common.xcontent.ObjectParser;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.search.aggregations.metrics.ParsedSingleValueNumericMetricsAggregation;
+
+import java.io.IOException;
+
+public class ParsedAvg extends ParsedSingleValueNumericMetricsAggregation implements Avg {
+
+ @Override
+ public double getValue() {
+ return value();
+ }
+
+ @Override
+ public String getType() {
+ return AvgAggregationBuilder.NAME;
+ }
+
+ @Override
+ protected XContentBuilder doXContentBody(XContentBuilder builder, Params params) throws IOException {
+ // InternalAvg renders value only if the avg normalizer (count) is not 0.
+ // We parse back `null` as Double.POSITIVE_INFINITY so we check for that value here to get the same xContent output
+ boolean hasValue = value != Double.POSITIVE_INFINITY;
+ builder.field(CommonFields.VALUE.getPreferredName(), hasValue ? value : null);
+ if (hasValue && valueAsString != null) {
+ builder.field(CommonFields.VALUE_AS_STRING.getPreferredName(), valueAsString);
+ }
+ return builder;
+ }
+
+ private static final ObjectParser PARSER = new ObjectParser<>(ParsedAvg.class.getSimpleName(), true, ParsedAvg::new);
+
+ static {
+ declareSingleValueFields(PARSER, Double.POSITIVE_INFINITY);
+ }
+
+ public static ParsedAvg fromXContent(XContentParser parser, final String name) {
+ ParsedAvg avg = PARSER.apply(parser, null);
+ avg.setName(name);
+ return avg;
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/metrics/cardinality/InternalCardinality.java b/core/src/main/java/org/elasticsearch/search/aggregations/metrics/cardinality/InternalCardinality.java
index 028e97a69ff..ce1e9fc8939 100644
--- a/core/src/main/java/org/elasticsearch/search/aggregations/metrics/cardinality/InternalCardinality.java
+++ b/core/src/main/java/org/elasticsearch/search/aggregations/metrics/cardinality/InternalCardinality.java
@@ -128,3 +128,4 @@ public final class InternalCardinality extends InternalNumericMetricsAggregation
return counts;
}
}
+
diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/metrics/cardinality/ParsedCardinality.java b/core/src/main/java/org/elasticsearch/search/aggregations/metrics/cardinality/ParsedCardinality.java
new file mode 100644
index 00000000000..5a615f61a4a
--- /dev/null
+++ b/core/src/main/java/org/elasticsearch/search/aggregations/metrics/cardinality/ParsedCardinality.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you 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 at
+ *
+ * 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.
+ */
+
+package org.elasticsearch.search.aggregations.metrics.cardinality;
+
+import org.elasticsearch.common.xcontent.ObjectParser;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.search.aggregations.ParsedAggregation;
+
+import java.io.IOException;
+
+public class ParsedCardinality extends ParsedAggregation implements Cardinality {
+
+ private long cardinalityValue;
+
+ @Override
+ public String getValueAsString() {
+ return Double.toString((double) cardinalityValue);
+ }
+
+ @Override
+ public double value() {
+ return getValue();
+ }
+
+ @Override
+ public long getValue() {
+ return cardinalityValue;
+ }
+
+ @Override
+ public String getType() {
+ return CardinalityAggregationBuilder.NAME;
+ }
+
+ private static final ObjectParser PARSER = new ObjectParser<>(
+ ParsedCardinality.class.getSimpleName(), true, ParsedCardinality::new);
+
+ static {
+ declareAggregationFields(PARSER);
+ PARSER.declareLong((agg, value) -> agg.cardinalityValue = value, CommonFields.VALUE);
+ }
+
+ public static ParsedCardinality fromXContent(XContentParser parser, final String name) {
+ ParsedCardinality cardinality = PARSER.apply(parser, null);
+ cardinality.setName(name);
+ return cardinality;
+ }
+
+ @Override
+ protected XContentBuilder doXContentBody(XContentBuilder builder, Params params)
+ throws IOException {
+ builder.field(CommonFields.VALUE.getPreferredName(), cardinalityValue);
+ return builder;
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/metrics/geobounds/InternalGeoBounds.java b/core/src/main/java/org/elasticsearch/search/aggregations/metrics/geobounds/InternalGeoBounds.java
index 2a3d03e43e6..a64115447be 100644
--- a/core/src/main/java/org/elasticsearch/search/aggregations/metrics/geobounds/InternalGeoBounds.java
+++ b/core/src/main/java/org/elasticsearch/search/aggregations/metrics/geobounds/InternalGeoBounds.java
@@ -19,6 +19,7 @@
package org.elasticsearch.search.aggregations.metrics.geobounds;
+import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
@@ -32,6 +33,14 @@ import java.util.Map;
import java.util.Objects;
public class InternalGeoBounds extends InternalAggregation implements GeoBounds {
+
+ static final ParseField BOUNDS_FIELD = new ParseField("bounds");
+ static final ParseField TOP_LEFT_FIELD = new ParseField("top_left");
+ static final ParseField BOTTOM_RIGHT_FIELD = new ParseField("bottom_right");
+ static final ParseField LAT_FIELD = new ParseField("lat");
+ static final ParseField LON_FIELD = new ParseField("lon");
+
+
final double top;
final double bottom;
final double posLeft;
@@ -170,14 +179,14 @@ public class InternalGeoBounds extends InternalAggregation implements GeoBounds
GeoPoint topLeft = topLeft();
GeoPoint bottomRight = bottomRight();
if (topLeft != null) {
- builder.startObject("bounds");
- builder.startObject("top_left");
- builder.field("lat", topLeft.lat());
- builder.field("lon", topLeft.lon());
+ builder.startObject(BOUNDS_FIELD.getPreferredName());
+ builder.startObject(TOP_LEFT_FIELD.getPreferredName());
+ builder.field(LAT_FIELD.getPreferredName(), topLeft.lat());
+ builder.field(LON_FIELD.getPreferredName(), topLeft.lon());
builder.endObject();
- builder.startObject("bottom_right");
- builder.field("lat", bottomRight.lat());
- builder.field("lon", bottomRight.lon());
+ builder.startObject(BOTTOM_RIGHT_FIELD.getPreferredName());
+ builder.field(LAT_FIELD.getPreferredName(), bottomRight.lat());
+ builder.field(LON_FIELD.getPreferredName(), bottomRight.lon());
builder.endObject();
builder.endObject();
}
diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/metrics/geobounds/ParsedGeoBounds.java b/core/src/main/java/org/elasticsearch/search/aggregations/metrics/geobounds/ParsedGeoBounds.java
new file mode 100644
index 00000000000..04d2b2448d2
--- /dev/null
+++ b/core/src/main/java/org/elasticsearch/search/aggregations/metrics/geobounds/ParsedGeoBounds.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you 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 at
+ *
+ * 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.
+ */
+
+package org.elasticsearch.search.aggregations.metrics.geobounds;
+
+import org.elasticsearch.common.collect.Tuple;
+import org.elasticsearch.common.geo.GeoPoint;
+import org.elasticsearch.common.xcontent.ConstructingObjectParser;
+import org.elasticsearch.common.xcontent.ObjectParser;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.search.aggregations.ParsedAggregation;
+
+import java.io.IOException;
+
+import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;
+import static org.elasticsearch.search.aggregations.metrics.geobounds.InternalGeoBounds.BOTTOM_RIGHT_FIELD;
+import static org.elasticsearch.search.aggregations.metrics.geobounds.InternalGeoBounds.BOUNDS_FIELD;
+import static org.elasticsearch.search.aggregations.metrics.geobounds.InternalGeoBounds.LAT_FIELD;
+import static org.elasticsearch.search.aggregations.metrics.geobounds.InternalGeoBounds.LON_FIELD;
+import static org.elasticsearch.search.aggregations.metrics.geobounds.InternalGeoBounds.TOP_LEFT_FIELD;
+
+public class ParsedGeoBounds extends ParsedAggregation implements GeoBounds {
+ private GeoPoint topLeft;
+ private GeoPoint bottomRight;
+
+ @Override
+ public String getType() {
+ return GeoBoundsAggregationBuilder.NAME;
+ }
+
+ @Override
+ public XContentBuilder doXContentBody(XContentBuilder builder, Params params) throws IOException {
+ if (topLeft != null) {
+ builder.startObject("bounds");
+ builder.startObject("top_left");
+ builder.field("lat", topLeft.getLat());
+ builder.field("lon", topLeft.getLon());
+ builder.endObject();
+ builder.startObject("bottom_right");
+ builder.field("lat", bottomRight.getLat());
+ builder.field("lon", bottomRight.getLon());
+ builder.endObject();
+ builder.endObject();
+ }
+ return builder;
+ }
+
+ @Override
+ public GeoPoint topLeft() {
+ return topLeft;
+ }
+
+ @Override
+ public GeoPoint bottomRight() {
+ return bottomRight;
+ }
+
+ private static final ObjectParser PARSER = new ObjectParser<>(ParsedGeoBounds.class.getSimpleName(), true,
+ ParsedGeoBounds::new);
+
+ private static final ConstructingObjectParser, Void> BOUNDS_PARSER =
+ new ConstructingObjectParser<>(ParsedGeoBounds.class.getSimpleName() + "_BOUNDS", true,
+ args -> new Tuple<>((GeoPoint) args[0], (GeoPoint) args[1]));
+
+ private static final ObjectParser GEO_POINT_PARSER = new ObjectParser<>(
+ ParsedGeoBounds.class.getSimpleName() + "_POINT", true, GeoPoint::new);
+
+ static {
+ declareAggregationFields(PARSER);
+ PARSER.declareObject((agg, bbox) -> {
+ agg.topLeft = bbox.v1();
+ agg.bottomRight = bbox.v2();
+ }, BOUNDS_PARSER, BOUNDS_FIELD);
+
+ BOUNDS_PARSER.declareObject(constructorArg(), GEO_POINT_PARSER, TOP_LEFT_FIELD);
+ BOUNDS_PARSER.declareObject(constructorArg(), GEO_POINT_PARSER, BOTTOM_RIGHT_FIELD);
+
+ GEO_POINT_PARSER.declareDouble(GeoPoint::resetLat, LAT_FIELD);
+ GEO_POINT_PARSER.declareDouble(GeoPoint::resetLon, LON_FIELD);
+ }
+
+ public static ParsedGeoBounds fromXContent(XContentParser parser, final String name) {
+ ParsedGeoBounds geoBounds = PARSER.apply(parser, null);
+ geoBounds.setName(name);
+ return geoBounds;
+ }
+
+}
diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/metrics/geocentroid/InternalGeoCentroid.java b/core/src/main/java/org/elasticsearch/search/aggregations/metrics/geocentroid/InternalGeoCentroid.java
index accf210345d..b8d317ff787 100644
--- a/core/src/main/java/org/elasticsearch/search/aggregations/metrics/geocentroid/InternalGeoCentroid.java
+++ b/core/src/main/java/org/elasticsearch/search/aggregations/metrics/geocentroid/InternalGeoCentroid.java
@@ -149,9 +149,9 @@ public class InternalGeoCentroid extends InternalAggregation implements GeoCentr
static class Fields {
static final ParseField CENTROID = new ParseField("location");
+ static final ParseField COUNT = new ParseField("count");
static final ParseField CENTROID_LAT = new ParseField("lat");
static final ParseField CENTROID_LON = new ParseField("lon");
- static final ParseField COUNT = new ParseField("count");
}
@Override
diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/metrics/geocentroid/ParsedGeoCentroid.java b/core/src/main/java/org/elasticsearch/search/aggregations/metrics/geocentroid/ParsedGeoCentroid.java
new file mode 100644
index 00000000000..7ce1f5d86fe
--- /dev/null
+++ b/core/src/main/java/org/elasticsearch/search/aggregations/metrics/geocentroid/ParsedGeoCentroid.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you 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 at
+ *
+ * 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.
+ */
+
+package org.elasticsearch.search.aggregations.metrics.geocentroid;
+
+import org.elasticsearch.common.geo.GeoPoint;
+import org.elasticsearch.common.xcontent.ObjectParser;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.search.aggregations.ParsedAggregation;
+import org.elasticsearch.search.aggregations.metrics.geocentroid.InternalGeoCentroid.Fields;
+
+import java.io.IOException;
+
+/**
+ * Serialization and merge logic for {@link GeoCentroidAggregator}.
+ */
+public class ParsedGeoCentroid extends ParsedAggregation implements GeoCentroid {
+ private GeoPoint centroid;
+ private long count;
+
+ @Override
+ public GeoPoint centroid() {
+ return centroid;
+ }
+
+ @Override
+ public long count() {
+ return count;
+ }
+
+ @Override
+ public String getType() {
+ return GeoCentroidAggregationBuilder.NAME;
+ }
+
+ @Override
+ public XContentBuilder doXContentBody(XContentBuilder builder, Params params) throws IOException {
+ if (centroid != null) {
+ builder.startObject(Fields.CENTROID.getPreferredName());
+ {
+ builder.field(Fields.CENTROID_LAT.getPreferredName(), centroid.lat());
+ builder.field(Fields.CENTROID_LON.getPreferredName(), centroid.lon());
+ }
+ builder.endObject();
+ }
+ builder.field(Fields.COUNT.getPreferredName(), count);
+ return builder;
+ }
+
+ private static final ObjectParser PARSER = new ObjectParser<>(ParsedGeoCentroid.class.getSimpleName(), true,
+ ParsedGeoCentroid::new);
+
+ private static final ObjectParser GEO_POINT_PARSER = new ObjectParser<>(
+ ParsedGeoCentroid.class.getSimpleName() + "_POINT", true, GeoPoint::new);
+
+ static {
+ declareAggregationFields(PARSER);
+ PARSER.declareObject((agg, centroid) -> agg.centroid = centroid, GEO_POINT_PARSER, Fields.CENTROID);
+ PARSER.declareLong((agg, count) -> agg.count = count, Fields.COUNT);
+
+ GEO_POINT_PARSER.declareDouble(GeoPoint::resetLat, Fields.CENTROID_LAT);
+ GEO_POINT_PARSER.declareDouble(GeoPoint::resetLon, Fields.CENTROID_LON);
+ }
+
+ public static ParsedGeoCentroid fromXContent(XContentParser parser, final String name) {
+ ParsedGeoCentroid geoCentroid = PARSER.apply(parser, null);
+ geoCentroid.setName(name);
+ return geoCentroid;
+ }
+}
diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/metrics/max/ParsedMax.java b/core/src/main/java/org/elasticsearch/search/aggregations/metrics/max/ParsedMax.java
new file mode 100644
index 00000000000..f6a3190cd04
--- /dev/null
+++ b/core/src/main/java/org/elasticsearch/search/aggregations/metrics/max/ParsedMax.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you 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 at
+ *
+ * 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.
+ */
+
+package org.elasticsearch.search.aggregations.metrics.max;
+
+import org.elasticsearch.common.xcontent.ObjectParser;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.search.aggregations.metrics.ParsedSingleValueNumericMetricsAggregation;
+
+import java.io.IOException;
+
+public class ParsedMax extends ParsedSingleValueNumericMetricsAggregation implements Max {
+
+ @Override
+ public double getValue() {
+ return value();
+ }
+
+ @Override
+ public String getType() {
+ return MaxAggregationBuilder.NAME;
+ }
+
+ @Override
+ protected XContentBuilder doXContentBody(XContentBuilder builder, Params params) throws IOException {
+ boolean hasValue = !Double.isInfinite(value);
+ builder.field(CommonFields.VALUE.getPreferredName(), hasValue ? value : null);
+ if (hasValue && valueAsString != null) {
+ builder.field(CommonFields.VALUE_AS_STRING.getPreferredName(), valueAsString);
+ }
+ return builder;
+ }
+
+ private static final ObjectParser PARSER = new ObjectParser<>(ParsedMax.class.getSimpleName(), true, ParsedMax::new);
+
+ static {
+ declareSingleValueFields(PARSER, Double.NEGATIVE_INFINITY);
+ }
+
+ public static ParsedMax fromXContent(XContentParser parser, final String name) {
+ ParsedMax max = PARSER.apply(parser, null);
+ max.setName(name);
+ return max;
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/metrics/min/ParsedMin.java b/core/src/main/java/org/elasticsearch/search/aggregations/metrics/min/ParsedMin.java
new file mode 100644
index 00000000000..9b214bb3462
--- /dev/null
+++ b/core/src/main/java/org/elasticsearch/search/aggregations/metrics/min/ParsedMin.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you 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 at
+ *
+ * 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.
+ */
+
+package org.elasticsearch.search.aggregations.metrics.min;
+
+import org.elasticsearch.common.xcontent.ObjectParser;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.search.aggregations.metrics.ParsedSingleValueNumericMetricsAggregation;
+
+import java.io.IOException;
+
+public class ParsedMin extends ParsedSingleValueNumericMetricsAggregation implements Min {
+
+ @Override
+ public double getValue() {
+ return value();
+ }
+
+ @Override
+ public String getType() {
+ return MinAggregationBuilder.NAME;
+ }
+
+ @Override
+ protected XContentBuilder doXContentBody(XContentBuilder builder, Params params) throws IOException {
+ boolean hasValue = !Double.isInfinite(value);
+ builder.field(CommonFields.VALUE.getPreferredName(), hasValue ? value : null);
+ if (hasValue && valueAsString != null) {
+ builder.field(CommonFields.VALUE_AS_STRING.getPreferredName(), valueAsString);
+ }
+ return builder;
+ }
+
+ private static final ObjectParser PARSER = new ObjectParser<>(ParsedMin.class.getSimpleName(), true, ParsedMin::new);
+
+ static {
+ declareSingleValueFields(PARSER, Double.POSITIVE_INFINITY);
+ }
+
+ public static ParsedMin fromXContent(XContentParser parser, final String name) {
+ ParsedMin min = PARSER.apply(parser, null);
+ min.setName(name);
+ return min;
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/metrics/percentiles/ParsedPercentileRanks.java b/core/src/main/java/org/elasticsearch/search/aggregations/metrics/percentiles/ParsedPercentileRanks.java
new file mode 100644
index 00000000000..2c80d0328dd
--- /dev/null
+++ b/core/src/main/java/org/elasticsearch/search/aggregations/metrics/percentiles/ParsedPercentileRanks.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you 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 at
+ *
+ * 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.
+ */
+
+package org.elasticsearch.search.aggregations.metrics.percentiles;
+
+public abstract class ParsedPercentileRanks extends ParsedPercentiles implements PercentileRanks {
+
+ @Override
+ public double percent(double value) {
+ return getPercentile(value);
+ }
+
+ @Override
+ public String percentAsString(double value) {
+ return getPercentileAsString(value);
+ }
+}
diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/metrics/percentiles/ParsedPercentiles.java b/core/src/main/java/org/elasticsearch/search/aggregations/metrics/percentiles/ParsedPercentiles.java
new file mode 100644
index 00000000000..48f3dccecef
--- /dev/null
+++ b/core/src/main/java/org/elasticsearch/search/aggregations/metrics/percentiles/ParsedPercentiles.java
@@ -0,0 +1,179 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you 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 at
+ *
+ * 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.
+ */
+
+package org.elasticsearch.search.aggregations.metrics.percentiles;
+
+import org.elasticsearch.common.xcontent.ObjectParser;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.search.aggregations.ParsedAggregation;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public abstract class ParsedPercentiles extends ParsedAggregation implements Iterable {
+
+ protected final Map percentiles = new LinkedHashMap<>();
+ protected final Map percentilesAsString = new HashMap<>();
+
+ private boolean keyed;
+
+ void addPercentile(Double key, Double value) {
+ percentiles.put(key, value);
+ }
+
+ void addPercentileAsString(Double key, String valueAsString) {
+ percentilesAsString.put(key, valueAsString);
+ }
+
+ protected Double getPercentile(double percent) {
+ if (percentiles.isEmpty()) {
+ return Double.NaN;
+ }
+ return percentiles.get(percent);
+ }
+
+ protected String getPercentileAsString(double percent) {
+ String valueAsString = percentilesAsString.get(percent);
+ if (valueAsString != null) {
+ return valueAsString;
+ }
+ Double value = getPercentile(percent);
+ if (value != null) {
+ return Double.toString(value);
+ }
+ return null;
+ }
+
+ void setKeyed(boolean keyed) {
+ this.keyed = keyed;
+ }
+
+ @Override
+ public Iterator iterator() {
+ return new Iterator() {
+ final Iterator> iterator = percentiles.entrySet().iterator();
+ @Override
+ public boolean hasNext() {
+ return iterator.hasNext();
+ }
+
+ @Override
+ public Percentile next() {
+ Map.Entry next = iterator.next();
+ return new Percentile(next.getKey(), next.getValue());
+ }
+ };
+ }
+
+ @Override
+ protected XContentBuilder doXContentBody(XContentBuilder builder, Params params) throws IOException {
+ final boolean valuesAsString = (percentilesAsString.isEmpty() == false);
+ if (keyed) {
+ builder.startObject(CommonFields.VALUES.getPreferredName());
+ for (Map.Entry percentile : percentiles.entrySet()) {
+ Double key = percentile.getKey();
+ builder.field(String.valueOf(key), percentile.getValue());
+
+ if (valuesAsString) {
+ builder.field(key + "_as_string", getPercentileAsString(key));
+ }
+ }
+ builder.endObject();
+ } else {
+ builder.startArray(CommonFields.VALUES.getPreferredName());
+ for (Map.Entry percentile : percentiles.entrySet()) {
+ Double key = percentile.getKey();
+ builder.startObject();
+ {
+ builder.field(CommonFields.KEY.getPreferredName(), key);
+ builder.field(CommonFields.VALUE.getPreferredName(), percentile.getValue());
+ if (valuesAsString) {
+ builder.field(CommonFields.VALUE_AS_STRING.getPreferredName(), getPercentileAsString(key));
+ }
+ }
+ builder.endObject();
+ }
+ builder.endArray();
+ }
+ return builder;
+ }
+
+ protected static void declarePercentilesFields(ObjectParser extends ParsedPercentiles, Void> objectParser) {
+ ParsedAggregation.declareAggregationFields(objectParser);
+
+ objectParser.declareField((parser, aggregation, context) -> {
+ XContentParser.Token token = parser.currentToken();
+ if (token == XContentParser.Token.START_OBJECT) {
+ aggregation.setKeyed(true);
+ while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
+ if (token.isValue()) {
+ if (token == XContentParser.Token.VALUE_NUMBER) {
+ aggregation.addPercentile(Double.valueOf(parser.currentName()), parser.doubleValue());
+ } else if (token == XContentParser.Token.VALUE_STRING) {
+ int i = parser.currentName().indexOf("_as_string");
+ if (i > 0) {
+ double key = Double.valueOf(parser.currentName().substring(0, i));
+ aggregation.addPercentileAsString(key, parser.text());
+ } else {
+ aggregation.addPercentile(Double.valueOf(parser.currentName()), Double.valueOf(parser.text()));
+ }
+ }
+ } else if (token == XContentParser.Token.VALUE_NULL) {
+ aggregation.addPercentile(Double.valueOf(parser.currentName()), Double.NaN);
+ }
+ }
+ } else if (token == XContentParser.Token.START_ARRAY) {
+ aggregation.setKeyed(false);
+
+ String currentFieldName = null;
+ while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
+ Double key = null;
+ Double value = null;
+ String valueAsString = null;
+
+ while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
+ if (token == XContentParser.Token.FIELD_NAME) {
+ currentFieldName = parser.currentName();
+ } else if (token.isValue()) {
+ if (CommonFields.KEY.getPreferredName().equals(currentFieldName)) {
+ key = parser.doubleValue();
+ } else if (CommonFields.VALUE.getPreferredName().equals(currentFieldName)) {
+ value = parser.doubleValue();
+ } else if (CommonFields.VALUE_AS_STRING.getPreferredName().equals(currentFieldName)) {
+ valueAsString = parser.text();
+ }
+ } else if (token == XContentParser.Token.VALUE_NULL) {
+ value = Double.NaN;
+ }
+ }
+ if (key != null) {
+ aggregation.addPercentile(key, value);
+ if (valueAsString != null) {
+ aggregation.addPercentileAsString(key, valueAsString);
+ }
+ }
+ }
+ }
+ }, CommonFields.VALUES, ObjectParser.ValueType.OBJECT_ARRAY);
+ }
+}
diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/metrics/percentiles/hdr/ParsedHDRPercentileRanks.java b/core/src/main/java/org/elasticsearch/search/aggregations/metrics/percentiles/hdr/ParsedHDRPercentileRanks.java
new file mode 100644
index 00000000000..f5fd7717e04
--- /dev/null
+++ b/core/src/main/java/org/elasticsearch/search/aggregations/metrics/percentiles/hdr/ParsedHDRPercentileRanks.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you 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 at
+ *
+ * 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.
+ */
+
+package org.elasticsearch.search.aggregations.metrics.percentiles.hdr;
+
+import org.elasticsearch.common.xcontent.ObjectParser;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.search.aggregations.metrics.percentiles.ParsedPercentiles;
+import org.elasticsearch.search.aggregations.metrics.percentiles.ParsedPercentileRanks;
+import org.elasticsearch.search.aggregations.metrics.percentiles.Percentile;
+
+import java.io.IOException;
+import java.util.Iterator;
+
+public class ParsedHDRPercentileRanks extends ParsedPercentileRanks {
+
+ @Override
+ public String getType() {
+ return InternalHDRPercentileRanks.NAME;
+ }
+
+ @Override
+ public Iterator iterator() {
+ final Iterator iterator = super.iterator();
+ return new Iterator() {
+ @Override
+ public boolean hasNext() {
+ return iterator.hasNext();
+ }
+
+ @Override
+ public Percentile next() {
+ Percentile percentile = iterator.next();
+ return new Percentile(percentile.getValue(), percentile.getPercent());
+ }
+ };
+ }
+
+ private static ObjectParser PARSER =
+ new ObjectParser<>(ParsedHDRPercentileRanks.class.getSimpleName(), true, ParsedHDRPercentileRanks::new);
+ static {
+ ParsedPercentiles.declarePercentilesFields(PARSER);
+ }
+
+ public static ParsedHDRPercentileRanks fromXContent(XContentParser parser, String name) throws IOException {
+ ParsedHDRPercentileRanks aggregation = PARSER.parse(parser, null);
+ aggregation.setName(name);
+ return aggregation;
+ }
+}
diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/metrics/percentiles/hdr/ParsedHDRPercentiles.java b/core/src/main/java/org/elasticsearch/search/aggregations/metrics/percentiles/hdr/ParsedHDRPercentiles.java
new file mode 100644
index 00000000000..1b1ba906aa0
--- /dev/null
+++ b/core/src/main/java/org/elasticsearch/search/aggregations/metrics/percentiles/hdr/ParsedHDRPercentiles.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you 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 at
+ *
+ * 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.
+ */
+
+package org.elasticsearch.search.aggregations.metrics.percentiles.hdr;
+
+import org.elasticsearch.common.xcontent.ObjectParser;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.search.aggregations.metrics.percentiles.ParsedPercentiles;
+import org.elasticsearch.search.aggregations.metrics.percentiles.Percentiles;
+
+import java.io.IOException;
+
+public class ParsedHDRPercentiles extends ParsedPercentiles implements Percentiles {
+
+ @Override
+ public String getType() {
+ return InternalHDRPercentiles.NAME;
+ }
+
+ @Override
+ public double percentile(double percent) {
+ return getPercentile(percent);
+ }
+
+ @Override
+ public String percentileAsString(double percent) {
+ return getPercentileAsString(percent);
+ }
+
+ private static ObjectParser PARSER =
+ new ObjectParser<>(ParsedHDRPercentiles.class.getSimpleName(), true, ParsedHDRPercentiles::new);
+ static {
+ ParsedPercentiles.declarePercentilesFields(PARSER);
+ }
+
+ public static ParsedHDRPercentiles fromXContent(XContentParser parser, String name) throws IOException {
+ ParsedHDRPercentiles aggregation = PARSER.parse(parser, null);
+ aggregation.setName(name);
+ return aggregation;
+ }
+}
diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/metrics/percentiles/tdigest/ParsedTDigestPercentileRanks.java b/core/src/main/java/org/elasticsearch/search/aggregations/metrics/percentiles/tdigest/ParsedTDigestPercentileRanks.java
new file mode 100644
index 00000000000..01929f374d4
--- /dev/null
+++ b/core/src/main/java/org/elasticsearch/search/aggregations/metrics/percentiles/tdigest/ParsedTDigestPercentileRanks.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you 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 at
+ *
+ * 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.
+ */
+
+package org.elasticsearch.search.aggregations.metrics.percentiles.tdigest;
+
+import org.elasticsearch.common.xcontent.ObjectParser;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.search.aggregations.metrics.percentiles.ParsedPercentiles;
+import org.elasticsearch.search.aggregations.metrics.percentiles.ParsedPercentileRanks;
+import org.elasticsearch.search.aggregations.metrics.percentiles.Percentile;
+
+import java.io.IOException;
+import java.util.Iterator;
+
+public class ParsedTDigestPercentileRanks extends ParsedPercentileRanks {
+
+ @Override
+ public String getType() {
+ return InternalTDigestPercentileRanks.NAME;
+ }
+
+ @Override
+ public Iterator iterator() {
+ final Iterator iterator = super.iterator();
+ return new Iterator() {
+ @Override
+ public boolean hasNext() {
+ return iterator.hasNext();
+ }
+
+ @Override
+ public Percentile next() {
+ Percentile percentile = iterator.next();
+ return new Percentile(percentile.getValue(), percentile.getPercent());
+ }
+ };
+ }
+
+ private static ObjectParser PARSER =
+ new ObjectParser<>(ParsedTDigestPercentileRanks.class.getSimpleName(), true, ParsedTDigestPercentileRanks::new);
+ static {
+ ParsedPercentiles.declarePercentilesFields(PARSER);
+ }
+
+ public static ParsedTDigestPercentileRanks fromXContent(XContentParser parser, String name) throws IOException {
+ ParsedTDigestPercentileRanks aggregation = PARSER.parse(parser, null);
+ aggregation.setName(name);
+ return aggregation;
+ }
+}
diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/metrics/percentiles/tdigest/ParsedTDigestPercentiles.java b/core/src/main/java/org/elasticsearch/search/aggregations/metrics/percentiles/tdigest/ParsedTDigestPercentiles.java
new file mode 100644
index 00000000000..cbae25d61e0
--- /dev/null
+++ b/core/src/main/java/org/elasticsearch/search/aggregations/metrics/percentiles/tdigest/ParsedTDigestPercentiles.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you 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 at
+ *
+ * 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.
+ */
+
+package org.elasticsearch.search.aggregations.metrics.percentiles.tdigest;
+
+import org.elasticsearch.common.xcontent.ObjectParser;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.search.aggregations.metrics.percentiles.ParsedPercentiles;
+import org.elasticsearch.search.aggregations.metrics.percentiles.Percentiles;
+
+import java.io.IOException;
+
+public class ParsedTDigestPercentiles extends ParsedPercentiles implements Percentiles {
+
+ @Override
+ public String getType() {
+ return InternalTDigestPercentiles.NAME;
+ }
+
+ @Override
+ public double percentile(double percent) {
+ return getPercentile(percent);
+ }
+
+ @Override
+ public String percentileAsString(double percent) {
+ return getPercentileAsString(percent);
+ }
+
+ private static ObjectParser PARSER =
+ new ObjectParser<>(ParsedTDigestPercentiles.class.getSimpleName(), true, ParsedTDigestPercentiles::new);
+ static {
+ ParsedPercentiles.declarePercentilesFields(PARSER);
+ }
+
+ public static ParsedTDigestPercentiles fromXContent(XContentParser parser, String name) throws IOException {
+ ParsedTDigestPercentiles aggregation = PARSER.parse(parser, null);
+ aggregation.setName(name);
+ return aggregation;
+ }
+}
diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/metrics/scripted/InternalScriptedMetric.java b/core/src/main/java/org/elasticsearch/search/aggregations/metrics/scripted/InternalScriptedMetric.java
index 73975b25778..ea0fa4ce196 100644
--- a/core/src/main/java/org/elasticsearch/search/aggregations/metrics/scripted/InternalScriptedMetric.java
+++ b/core/src/main/java/org/elasticsearch/search/aggregations/metrics/scripted/InternalScriptedMetric.java
@@ -124,7 +124,7 @@ public class InternalScriptedMetric extends InternalAggregation implements Scrip
@Override
public XContentBuilder doXContentBody(XContentBuilder builder, Params params) throws IOException {
- return builder.field("value", aggregation());
+ return builder.field(CommonFields.VALUE.getPreferredName(), aggregation());
}
@Override
diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/metrics/scripted/ParsedScriptedMetric.java b/core/src/main/java/org/elasticsearch/search/aggregations/metrics/scripted/ParsedScriptedMetric.java
new file mode 100644
index 00000000000..f2aae9f5e8a
--- /dev/null
+++ b/core/src/main/java/org/elasticsearch/search/aggregations/metrics/scripted/ParsedScriptedMetric.java
@@ -0,0 +1,92 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you 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 at
+ *
+ * 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.
+ */
+
+package org.elasticsearch.search.aggregations.metrics.scripted;
+
+import org.elasticsearch.common.bytes.BytesArray;
+import org.elasticsearch.common.xcontent.ObjectParser;
+import org.elasticsearch.common.xcontent.ObjectParser.ValueType;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.common.xcontent.XContentParser.Token;
+import org.elasticsearch.search.aggregations.ParsedAggregation;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+
+public class ParsedScriptedMetric extends ParsedAggregation implements ScriptedMetric {
+ private List