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..3e83996c7c6 100644 --- a/core/src/main/java/org/elasticsearch/action/search/SearchResponse.java +++ b/core/src/main/java/org/elasticsearch/action/search/SearchResponse.java @@ -39,14 +39,13 @@ import java.io.IOException; import java.util.Map; import static org.elasticsearch.action.search.ShardSearchFailure.readShardSearchFailure; -import static org.elasticsearch.search.internal.InternalSearchResponse.readInternalSearchResponse; /** * A response of a search request. */ public class SearchResponse extends ActionResponse implements StatusToXContentObject { - private InternalSearchResponse internalResponse; + private SearchResponseSections internalResponse; private String scrollId; @@ -61,7 +60,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; @@ -209,7 +208,7 @@ public class SearchResponse extends ActionResponse implements StatusToXContentOb @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/search/aggregations/Aggregation.java b/core/src/main/java/org/elasticsearch/search/aggregations/Aggregation.java index 991243ce0d4..eb55f1e7abc 100644 --- a/core/src/main/java/org/elasticsearch/search/aggregations/Aggregation.java +++ b/core/src/main/java/org/elasticsearch/search/aggregations/Aggregation.java @@ -19,13 +19,14 @@ package org.elasticsearch.search.aggregations; import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.xcontent.ToXContent; import java.util.Map; /** - * An aggregation + * An aggregation. Extends {@link ToXContent} as it makes it easier to print out its content. */ -public interface Aggregation { +public interface Aggregation extends ToXContent { /** * Delimiter used when prefixing aggregation names with their type diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/Aggregations.java b/core/src/main/java/org/elasticsearch/search/aggregations/Aggregations.java index ef84edfb729..c7f84fe6577 100644 --- a/core/src/main/java/org/elasticsearch/search/aggregations/Aggregations.java +++ b/core/src/main/java/org/elasticsearch/search/aggregations/Aggregations.java @@ -18,6 +18,13 @@ */ package org.elasticsearch.search.aggregations; +import org.elasticsearch.common.xcontent.ToXContent; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.common.xcontent.XContentParserUtils; + +import java.io.IOException; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; @@ -30,7 +37,9 @@ import static java.util.Collections.unmodifiableMap; /** * Represents a set of {@link Aggregation}s */ -public abstract class Aggregations implements Iterable { +public class Aggregations implements Iterable, ToXContent { + + public static final String AGGREGATIONS_FIELD = "aggregations"; protected List aggregations = Collections.emptyList(); protected Map aggregationsAsMap; @@ -98,4 +107,36 @@ 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; + } + + //TODO add tests for this method + 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/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/internal/InternalSearchResponse.java b/core/src/main/java/org/elasticsearch/search/internal/InternalSearchResponse.java index 391f6efe18b..baa153e3470 100644 --- a/core/src/main/java/org/elasticsearch/search/internal/InternalSearchResponse.java +++ b/core/src/main/java/org/elasticsearch/search/internal/InternalSearchResponse.java @@ -19,148 +19,50 @@ package org.elasticsearch.search.internal; +import org.elasticsearch.action.search.SearchResponseSections; 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.io.stream.Writeable; 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.aggregations.InternalAggregations; -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; -public class InternalSearchResponse implements Streamable, ToXContent { +/** + * {@link SearchResponseSections} subclass that can be serialized over the wire. + */ +public class InternalSearchResponse extends SearchResponseSections implements Writeable, ToXContent { public static InternalSearchResponse empty() { return new InternalSearchResponse(SearchHits.empty(), null, null, null, false, null, 1); } - private SearchHits hits; - - private InternalAggregations aggregations; - - private Suggest suggest; - - private SearchProfileShardResults profileResults; - - private boolean timedOut; - - private Boolean terminatedEarly = null; - - private int numReducePhases = 1; - - private InternalSearchResponse() { - } - public InternalSearchResponse(SearchHits hits, InternalAggregations aggregations, Suggest suggest, SearchProfileShardResults profileResults, boolean timedOut, Boolean terminatedEarly, int numReducePhases) { - this.hits = hits; - this.aggregations = aggregations; - this.suggest = suggest; - this.profileResults = profileResults; - this.timedOut = timedOut; - this.terminatedEarly = terminatedEarly; - this.numReducePhases = numReducePhases; + super(hits, aggregations, suggest, timedOut, terminatedEarly, profileResults, numReducePhases); } - public boolean timedOut() { - return this.timedOut; - } - - public Boolean terminatedEarly() { - return this.terminatedEarly; - } - - public SearchHits hits() { - return hits; - } - - public Aggregations aggregations() { - return aggregations; - } - - public Suggest suggest() { - return suggest; - } - - /** - * Returns the number of reduce phases applied to obtain this search response - */ - public 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 Map profile() { - if (profileResults == null) { - return Collections.emptyMap(); - } - return profileResults.getShardResults(); - } - - @Override - public 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; - } - - public static InternalSearchResponse readInternalSearchResponse(StreamInput in) throws IOException { - InternalSearchResponse response = new InternalSearchResponse(); - response.readFrom(in); - return response; - } - - @Override - public void readFrom(StreamInput in) throws IOException { - hits = SearchHits.readSearchHits(in); - if (in.readBoolean()) { - aggregations = InternalAggregations.readAggregations(in); - } - if (in.readBoolean()) { - suggest = Suggest.readSuggest(in); - } - timedOut = in.readBoolean(); - terminatedEarly = in.readOptionalBoolean(); - profileResults = in.readOptionalWriteable(SearchProfileShardResults::new); - numReducePhases = in.readVInt(); + public InternalSearchResponse(StreamInput in) throws IOException { + super( + SearchHits.readSearchHits(in), + in.readBoolean() ? InternalAggregations.readAggregations(in) : null, + in.readBoolean() ? Suggest.readSuggest(in) : null, + in.readBoolean(), + in.readOptionalBoolean(), + in.readOptionalWriteable(SearchProfileShardResults::new), + in.readVInt() + ); } @Override public void writeTo(StreamOutput out) throws IOException { hits.writeTo(out); - if (aggregations == null) { - out.writeBoolean(false); - } else { - out.writeBoolean(true); - aggregations.writeTo(out); - } - if (suggest == null) { - out.writeBoolean(false); - } else { - out.writeBoolean(true); - suggest.writeTo(out); - } + out.writeOptionalStreamable((InternalAggregations)aggregations); + out.writeOptionalStreamable(suggest); out.writeBoolean(timedOut); out.writeOptionalBoolean(terminatedEarly); out.writeOptionalWriteable(profileResults);