Introduce SearchResponseSections base class (#24442)

SearchResponseSections is the common part extracted from InternalSearchResponse that can be shared between high level REST and elasticsearch. The only bits left out are around serialization which is not supported. This way it can accept Aggregations as a constructor argument, without requiring InternalAggregations, as the high level REST client uses its own objects for aggs parsing rather than internal ones.

This change also makes Aggregations implement ToXContent, and Aggregation extend ToXContent. Especially the latter is suboptimal but the best solution that allows to share as much code as possible between core and the client, that doesn't require messing with generics and making the api complicated. Also it doesn't have downsides as all of the current implementations of Aggregation do implement ToXContent already.
This commit is contained in:
Luca Cavanna 2017-05-03 10:45:31 +02:00 committed by GitHub
parent 60866da4b7
commit fbd793d9e6
6 changed files with 190 additions and 146 deletions

View File

@ -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();

View File

@ -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<String, ProfileShardResult> 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();
}
}

View File

@ -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

View File

@ -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<Aggregation> {
public class Aggregations implements Iterable<Aggregation>, ToXContent {
public static final String AGGREGATIONS_FIELD = "aggregations";
protected List<? extends Aggregation> aggregations = Collections.emptyList();
protected Map<String, Aggregation> aggregationsAsMap;
@ -98,4 +107,36 @@ public abstract class Aggregations implements Iterable<Aggregation> {
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<Aggregation> 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);
}
}

View File

@ -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);

View File

@ -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<String, ProfileShardResult> 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);