diff --git a/CHANGELOG.md b/CHANGELOG.md index 27c01e60ed..1732293710 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ### Changed - Improve time taken by Github actions by using cache ([#439](https://github.com/opensearch-project/opensearch-java/pull/439)) +- Introduce intermediary SearchResult for SearchResponse and SearchTemplateResponse classes, enabling similar response handling for both ([#462](https://github.com/opensearch-project/opensearch-java/pull/462)) ### Deprecated diff --git a/java-client/src/main/java/org/opensearch/client/opensearch/core/SearchResponse.java b/java-client/src/main/java/org/opensearch/client/opensearch/core/SearchResponse.java index b54daf6bf7..06f99dcf7b 100644 --- a/java-client/src/main/java/org/opensearch/client/opensearch/core/SearchResponse.java +++ b/java-client/src/main/java/org/opensearch/client/opensearch/core/SearchResponse.java @@ -36,331 +36,39 @@ package org.opensearch.client.opensearch.core; -import org.opensearch.client.opensearch._types.ClusterStatistics; -import org.opensearch.client.opensearch._types.ShardStatistics; -import org.opensearch.client.opensearch._types.aggregations.Aggregate; -import org.opensearch.client.opensearch.core.search.HitsMetadata; -import org.opensearch.client.opensearch.core.search.Profile; -import org.opensearch.client.opensearch.core.search.Suggestion; -import org.opensearch.client.json.ExternallyTaggedUnion; -import org.opensearch.client.json.JsonData; import org.opensearch.client.json.JsonpDeserializable; import org.opensearch.client.json.JsonpDeserializer; -import org.opensearch.client.json.JsonpMapper; -import org.opensearch.client.json.JsonpSerializable; -import org.opensearch.client.json.JsonpSerializer; -import org.opensearch.client.json.JsonpUtils; import org.opensearch.client.json.NamedDeserializer; import org.opensearch.client.json.ObjectBuilderDeserializer; import org.opensearch.client.json.ObjectDeserializer; -import org.opensearch.client.util.ApiTypeHelper; +import org.opensearch.client.opensearch.core.search.SearchResult; import org.opensearch.client.util.ObjectBuilder; -import org.opensearch.client.util.ObjectBuilderBase; -import jakarta.json.stream.JsonGenerator; -import java.util.List; -import java.util.Map; import java.util.function.Function; import java.util.function.Supplier; -import javax.annotation.Nullable; // typedef: _global.search.Response - @JsonpDeserializable -public class SearchResponse implements JsonpSerializable { - private final long took; - - private final boolean timedOut; - - private final ShardStatistics shards; - - private final HitsMetadata hits; - - private final Map aggregations; - - @Nullable - private final ClusterStatistics clusters; - - private final List documents; - - private final Map fields; - - @Nullable - private final Double maxScore; - - @Nullable - private final Long numReducePhases; - - @Nullable - private final Profile profile; - - @Nullable - private final String pitId; - - @Nullable - private final String scrollId; - - private final Map>> suggest; - - @Nullable - private final Boolean terminatedEarly; - - @Nullable - private final JsonpSerializer tDocumentSerializer; - +public class SearchResponse extends SearchResult { // --------------------------------------------------------------------------------------------- - protected SearchResponse(AbstractBuilder builder) { - - this.took = ApiTypeHelper.requireNonNull(builder.took, this, "took"); - this.timedOut = ApiTypeHelper.requireNonNull(builder.timedOut, this, "timedOut"); - this.shards = ApiTypeHelper.requireNonNull(builder.shards, this, "shards"); - this.hits = ApiTypeHelper.requireNonNull(builder.hits, this, "hits"); - this.aggregations = ApiTypeHelper.unmodifiable(builder.aggregations); - this.clusters = builder.clusters; - this.documents = ApiTypeHelper.unmodifiable(builder.documents); - this.fields = ApiTypeHelper.unmodifiable(builder.fields); - this.maxScore = builder.maxScore; - this.numReducePhases = builder.numReducePhases; - this.profile = builder.profile; - this.pitId = builder.pitId; - this.scrollId = builder.scrollId; - this.suggest = ApiTypeHelper.unmodifiable(builder.suggest); - this.terminatedEarly = builder.terminatedEarly; - this.tDocumentSerializer = builder.tDocumentSerializer; - - } - - public static SearchResponse searchResponseOf( - Function, ObjectBuilder>> fn) { - return fn.apply(new Builder<>()).build(); - } - - /** - * Required - API name: {@code took} - */ - public final long took() { - return this.took; - } - - /** - * Required - API name: {@code timed_out} - */ - public final boolean timedOut() { - return this.timedOut; - } - - /** - * Required - API name: {@code _shards} - */ - public final ShardStatistics shards() { - return this.shards; - } - - /** - * Required - API name: {@code hits} - */ - public final HitsMetadata hits() { - return this.hits; - } - - /** - * API name: {@code aggregations} - */ - public final Map aggregations() { - return this.aggregations; - } - - /** - * API name: {@code _clusters} - */ - @Nullable - public final ClusterStatistics clusters() { - return this.clusters; - } - - /** - * API name: {@code documents} - */ - public final List documents() { - return this.documents; - } - - /** - * API name: {@code fields} - */ - public final Map fields() { - return this.fields; - } - - /** - * API name: {@code max_score} - */ - @Nullable - public final Double maxScore() { - return this.maxScore; - } - - /** - * API name: {@code num_reduce_phases} - */ - @Nullable - public final Long numReducePhases() { - return this.numReducePhases; - } - - /** - * API name: {@code profile} - */ - @Nullable - public final Profile profile() { - return this.profile; - } - - /** - * API name: {@code pit_id} - */ - @Nullable - public final String pitId() { - return this.pitId; - } - - /** - * API name: {@code _scroll_id} - */ - @Nullable - public final String scrollId() { - return this.scrollId; - } - - /** - * API name: {@code suggest} - */ - public final Map>> suggest() { - return this.suggest; - } + protected SearchResponse(SearchResult.AbstractBuilder builder) { + super(builder); - /** - * API name: {@code terminated_early} - */ - @Nullable - public final Boolean terminatedEarly() { - return this.terminatedEarly; } - /** - * Serialize this object to JSON. - */ - public void serialize(JsonGenerator generator, JsonpMapper mapper) { - generator.writeStartObject(); - serializeInternal(generator, mapper); - generator.writeEnd(); - } - - protected void serializeInternal(JsonGenerator generator, JsonpMapper mapper) { - - generator.writeKey("took"); - generator.write(this.took); - - generator.writeKey("timed_out"); - generator.write(this.timedOut); - - generator.writeKey("_shards"); - this.shards.serialize(generator, mapper); - - generator.writeKey("hits"); - this.hits.serialize(generator, mapper); - - if (ApiTypeHelper.isDefined(this.aggregations)) { - generator.writeKey("aggregations"); - ExternallyTaggedUnion.serializeTypedKeys(this.aggregations, generator, mapper); - - } - if (this.clusters != null) { - generator.writeKey("_clusters"); - this.clusters.serialize(generator, mapper); - - } - if (ApiTypeHelper.isDefined(this.documents)) { - generator.writeKey("documents"); - generator.writeStartArray(); - for (TDocument item0 : this.documents) { - JsonpUtils.serialize(item0, generator, tDocumentSerializer, mapper); - - } - generator.writeEnd(); - - } - if (ApiTypeHelper.isDefined(this.fields)) { - generator.writeKey("fields"); - generator.writeStartObject(); - for (Map.Entry item0 : this.fields.entrySet()) { - generator.writeKey(item0.getKey()); - item0.getValue().serialize(generator, mapper); - - } - generator.writeEnd(); - - } - if (this.maxScore != null) { - generator.writeKey("max_score"); - generator.write(this.maxScore); - - } - if (this.numReducePhases != null) { - generator.writeKey("num_reduce_phases"); - generator.write(this.numReducePhases); - - } - if (this.profile != null) { - generator.writeKey("profile"); - this.profile.serialize(generator, mapper); - - } - if (this.pitId != null) { - generator.writeKey("pit_id"); - generator.write(this.pitId); - - } - if (this.scrollId != null) { - generator.writeKey("_scroll_id"); - generator.write(this.scrollId); - - } - if (ApiTypeHelper.isDefined(this.suggest)) { - generator.writeKey("suggest"); - generator.writeStartObject(); - for (Map.Entry>> item0 : this.suggest.entrySet()) { - generator.writeKey(item0.getKey()); - generator.writeStartArray(); - if (item0.getValue() != null) { - for (Suggestion item1 : item0.getValue()) { - item1.serialize(generator, mapper); - - } - } - generator.writeEnd(); - - } - generator.writeEnd(); - - } - if (this.terminatedEarly != null) { - generator.writeKey("terminated_early"); - generator.write(this.terminatedEarly); - - } - - } + public static SearchResponse searchResponseOf( + Function, ObjectBuilder>> fn) { + return fn.apply(new Builder<>()).build(); + } // --------------------------------------------------------------------------------------------- /** * Builder for {@link SearchResponse}. */ - - public static class Builder extends SearchResponse.AbstractBuilder> + public static class Builder extends SearchResult.AbstractBuilder> implements ObjectBuilder> { @Override @@ -381,272 +89,6 @@ public SearchResponse build() { } } - protected abstract static class AbstractBuilder> - extends - ObjectBuilderBase { - private Long took; - - private Boolean timedOut; - - private ShardStatistics shards; - - private HitsMetadata hits; - - @Nullable - private Map aggregations; - - @Nullable - private ClusterStatistics clusters; - - @Nullable - private List documents; - - @Nullable - private Map fields; - - @Nullable - private Double maxScore; - - @Nullable - private Long numReducePhases; - - @Nullable - private Profile profile; - - @Nullable - private String pitId; - - @Nullable - private String scrollId; - - @Nullable - private Map>> suggest; - - @Nullable - private Boolean terminatedEarly; - - @Nullable - private JsonpSerializer tDocumentSerializer; - - /** - * Required - API name: {@code took} - */ - public final BuilderT took(long value) { - this.took = value; - return self(); - } - - /** - * Required - API name: {@code timed_out} - */ - public final BuilderT timedOut(boolean value) { - this.timedOut = value; - return self(); - } - - /** - * Required - API name: {@code _shards} - */ - public final BuilderT shards(ShardStatistics value) { - this.shards = value; - return self(); - } - - /** - * Required - API name: {@code _shards} - */ - public final BuilderT shards(Function> fn) { - return this.shards(fn.apply(new ShardStatistics.Builder()).build()); - } - - /** - * Required - API name: {@code hits} - */ - public final BuilderT hits(HitsMetadata value) { - this.hits = value; - return self(); - } - - /** - * Required - API name: {@code hits} - */ - public final BuilderT hits( - Function, ObjectBuilder>> fn) { - return this.hits(fn.apply(new HitsMetadata.Builder()).build()); - } - - /** - * API name: {@code aggregations} - *

- * Adds all entries of map to aggregations. - */ - public final BuilderT aggregations(Map map) { - this.aggregations = _mapPutAll(this.aggregations, map); - return self(); - } - - /** - * API name: {@code aggregations} - *

- * Adds an entry to aggregations. - */ - public final BuilderT aggregations(String key, Aggregate value) { - this.aggregations = _mapPut(this.aggregations, key, value); - return self(); - } - - /** - * API name: {@code aggregations} - *

- * Adds an entry to aggregations using a builder lambda. - */ - public final BuilderT aggregations(String key, Function> fn) { - return aggregations(key, fn.apply(new Aggregate.Builder()).build()); - } - - /** - * API name: {@code _clusters} - */ - public final BuilderT clusters(@Nullable ClusterStatistics value) { - this.clusters = value; - return self(); - } - - /** - * API name: {@code _clusters} - */ - public final BuilderT clusters(Function> fn) { - return this.clusters(fn.apply(new ClusterStatistics.Builder()).build()); - } - - /** - * API name: {@code documents} - *

- * Adds all elements of list to documents. - */ - public final BuilderT documents(List list) { - this.documents = _listAddAll(this.documents, list); - return self(); - } - - /** - * API name: {@code documents} - *

- * Adds one or more values to documents. - */ - public final BuilderT documents(TDocument value, TDocument... values) { - this.documents = _listAdd(this.documents, value, values); - return self(); - } - - /** - * API name: {@code fields} - *

- * Adds all entries of map to fields. - */ - public final BuilderT fields(Map map) { - this.fields = _mapPutAll(this.fields, map); - return self(); - } - - /** - * API name: {@code fields} - *

- * Adds an entry to fields. - */ - public final BuilderT fields(String key, JsonData value) { - this.fields = _mapPut(this.fields, key, value); - return self(); - } - - /** - * API name: {@code max_score} - */ - public final BuilderT maxScore(@Nullable Double value) { - this.maxScore = value; - return self(); - } - - /** - * API name: {@code num_reduce_phases} - */ - public final BuilderT numReducePhases(@Nullable Long value) { - this.numReducePhases = value; - return self(); - } - - /** - * API name: {@code profile} - */ - public final BuilderT profile(@Nullable Profile value) { - this.profile = value; - return self(); - } - - /** - * API name: {@code profile} - */ - public final BuilderT profile(Function> fn) { - return this.profile(fn.apply(new Profile.Builder()).build()); - } - - /** - * API name: {@code pit_id} - */ - public final BuilderT pitId(@Nullable String value) { - this.pitId = value; - return self(); - } - - /** - * API name: {@code _scroll_id} - */ - public final BuilderT scrollId(@Nullable String value) { - this.scrollId = value; - return self(); - } - - /** - * API name: {@code suggest} - *

- * Adds all entries of map to suggest. - */ - public final BuilderT suggest(Map>> map) { - this.suggest = _mapPutAll(this.suggest, map); - return self(); - } - - /** - * API name: {@code suggest} - *

- * Adds an entry to suggest. - */ - public final BuilderT suggest(String key, List> value) { - this.suggest = _mapPut(this.suggest, key, value); - return self(); - } - - /** - * API name: {@code terminated_early} - */ - public final BuilderT terminatedEarly(@Nullable Boolean value) { - this.terminatedEarly = value; - return self(); - } - - /** - * Serializer for TDocument. If not set, an attempt will be made to find a - * serializer from the JSON context. - */ - public final BuilderT tDocumentSerializer(@Nullable JsonpSerializer value) { - this.tDocumentSerializer = value; - return self(); - } - - protected abstract BuilderT self(); - - } - // --------------------------------------------------------------------------------------------- /** @@ -665,27 +107,10 @@ public static JsonpDeserializer> createSea public static final JsonpDeserializer> _DESERIALIZER = createSearchResponseDeserializer( new NamedDeserializer<>("org.opensearch.client:Deserializer:_global.search.TDocument")); - protected static > void setupSearchResponseDeserializer( + protected static > void setupSearchResponseDeserializer( ObjectDeserializer op, JsonpDeserializer tDocumentDeserializer) { - - op.add(AbstractBuilder::took, JsonpDeserializer.longDeserializer(), "took"); - op.add(AbstractBuilder::timedOut, JsonpDeserializer.booleanDeserializer(), "timed_out"); - op.add(AbstractBuilder::shards, ShardStatistics._DESERIALIZER, "_shards"); - op.add(AbstractBuilder::hits, HitsMetadata.createHitsMetadataDeserializer(tDocumentDeserializer), "hits"); - op.add(AbstractBuilder::aggregations, Aggregate._TYPED_KEYS_DESERIALIZER, "aggregations"); - op.add(AbstractBuilder::clusters, ClusterStatistics._DESERIALIZER, "_clusters"); - op.add(AbstractBuilder::documents, JsonpDeserializer.arrayDeserializer(tDocumentDeserializer), "documents"); - op.add(AbstractBuilder::fields, JsonpDeserializer.stringMapDeserializer(JsonData._DESERIALIZER), "fields"); - op.add(AbstractBuilder::maxScore, JsonpDeserializer.doubleDeserializer(), "max_score"); - op.add(AbstractBuilder::numReducePhases, JsonpDeserializer.longDeserializer(), "num_reduce_phases"); - op.add(AbstractBuilder::profile, Profile._DESERIALIZER, "profile"); - op.add(AbstractBuilder::pitId, JsonpDeserializer.stringDeserializer(), "pit_id"); - op.add(AbstractBuilder::scrollId, JsonpDeserializer.stringDeserializer(), "_scroll_id"); - op.add(AbstractBuilder::suggest, JsonpDeserializer.stringMapDeserializer( - JsonpDeserializer.arrayDeserializer(Suggestion.createSuggestionDeserializer(tDocumentDeserializer))), - "suggest"); - op.add(AbstractBuilder::terminatedEarly, JsonpDeserializer.booleanDeserializer(), "terminated_early"); + SearchResult.setupSearchResultDeserializer(op, tDocumentDeserializer); } - + } diff --git a/java-client/src/main/java/org/opensearch/client/opensearch/core/SearchTemplateResponse.java b/java-client/src/main/java/org/opensearch/client/opensearch/core/SearchTemplateResponse.java index 0e1df80476..674dff5dee 100644 --- a/java-client/src/main/java/org/opensearch/client/opensearch/core/SearchTemplateResponse.java +++ b/java-client/src/main/java/org/opensearch/client/opensearch/core/SearchTemplateResponse.java @@ -36,49 +36,25 @@ package org.opensearch.client.opensearch.core; -import org.opensearch.client.opensearch._types.ShardStatistics; -import org.opensearch.client.opensearch.core.search.HitsMetadata; import org.opensearch.client.json.JsonpDeserializable; import org.opensearch.client.json.JsonpDeserializer; -import org.opensearch.client.json.JsonpMapper; -import org.opensearch.client.json.JsonpSerializable; -import org.opensearch.client.json.JsonpSerializer; import org.opensearch.client.json.NamedDeserializer; import org.opensearch.client.json.ObjectBuilderDeserializer; import org.opensearch.client.json.ObjectDeserializer; -import org.opensearch.client.util.ApiTypeHelper; +import org.opensearch.client.opensearch.core.search.SearchResult; import org.opensearch.client.util.ObjectBuilder; -import org.opensearch.client.util.ObjectBuilderBase; -import jakarta.json.stream.JsonGenerator; import java.util.function.Function; import java.util.function.Supplier; -import javax.annotation.Nullable; // typedef: _global.search_template.Response @JsonpDeserializable -public class SearchTemplateResponse implements JsonpSerializable { - private final ShardStatistics shards; - - private final boolean timedOut; - - private final int took; - - private final HitsMetadata hits; - - @Nullable - private final JsonpSerializer tDocumentSerializer; - +public class SearchTemplateResponse extends SearchResult { // --------------------------------------------------------------------------------------------- private SearchTemplateResponse(Builder builder) { - - this.shards = ApiTypeHelper.requireNonNull(builder.shards, this, "shards"); - this.timedOut = ApiTypeHelper.requireNonNull(builder.timedOut, this, "timedOut"); - this.took = ApiTypeHelper.requireNonNull(builder.took, this, "took"); - this.hits = ApiTypeHelper.requireNonNull(builder.hits, this, "hits"); - this.tDocumentSerializer = builder.tDocumentSerializer; + super(builder); } @@ -87,132 +63,16 @@ public static SearchTemplateResponse of( return fn.apply(new Builder<>()).build(); } - /** - * Required - API name: {@code _shards} - */ - public final ShardStatistics shards() { - return this.shards; - } - - /** - * Required - API name: {@code timed_out} - */ - public final boolean timedOut() { - return this.timedOut; - } - - /** - * Required - API name: {@code took} - */ - public final int took() { - return this.took; - } - - /** - * Required - API name: {@code hits} - */ - public final HitsMetadata hits() { - return this.hits; - } - - /** - * Serialize this object to JSON. - */ - public void serialize(JsonGenerator generator, JsonpMapper mapper) { - generator.writeStartObject(); - serializeInternal(generator, mapper); - generator.writeEnd(); - } - - protected void serializeInternal(JsonGenerator generator, JsonpMapper mapper) { - - generator.writeKey("_shards"); - this.shards.serialize(generator, mapper); - - generator.writeKey("timed_out"); - generator.write(this.timedOut); - - generator.writeKey("took"); - generator.write(this.took); - - generator.writeKey("hits"); - this.hits.serialize(generator, mapper); - - } - // --------------------------------------------------------------------------------------------- /** * Builder for {@link SearchTemplateResponse}. */ - - public static class Builder extends ObjectBuilderBase + public static class Builder extends SearchResult.AbstractBuilder> implements ObjectBuilder> { - private ShardStatistics shards; - - private Boolean timedOut; - - private Integer took; - - private HitsMetadata hits; - - @Nullable - private JsonpSerializer tDocumentSerializer; - - /** - * Required - API name: {@code _shards} - */ - public final Builder shards(ShardStatistics value) { - this.shards = value; - return this; - } - - /** - * Required - API name: {@code _shards} - */ - public final Builder shards(Function> fn) { - return this.shards(fn.apply(new ShardStatistics.Builder()).build()); - } - - /** - * Required - API name: {@code timed_out} - */ - public final Builder timedOut(boolean value) { - this.timedOut = value; - return this; - } - - /** - * Required - API name: {@code took} - */ - public final Builder took(int value) { - this.took = value; - return this; - } - - /** - * Required - API name: {@code hits} - */ - public final Builder hits(HitsMetadata value) { - this.hits = value; - return this; - } - - /** - * Required - API name: {@code hits} - */ - public final Builder hits( - Function, ObjectBuilder>> fn) { - return this.hits(fn.apply(new HitsMetadata.Builder()).build()); - } - - /** - * Serializer for TDocument. If not set, an attempt will be made to find a - * serializer from the JSON context. - */ - public final Builder tDocumentSerializer(@Nullable JsonpSerializer value) { - this.tDocumentSerializer = value; + @Override + protected Builder self() { return this; } @@ -250,11 +110,7 @@ public static JsonpDeserializer> c protected static void setupSearchTemplateResponseDeserializer( ObjectDeserializer> op, JsonpDeserializer tDocumentDeserializer) { - - op.add(Builder::shards, ShardStatistics._DESERIALIZER, "_shards"); - op.add(Builder::timedOut, JsonpDeserializer.booleanDeserializer(), "timed_out"); - op.add(Builder::took, JsonpDeserializer.integerDeserializer(), "took"); - op.add(Builder::hits, HitsMetadata.createHitsMetadataDeserializer(tDocumentDeserializer), "hits"); + SearchResult.setupSearchResultDeserializer(op, tDocumentDeserializer); } diff --git a/java-client/src/main/java/org/opensearch/client/opensearch/core/search/SearchResult.java b/java-client/src/main/java/org/opensearch/client/opensearch/core/search/SearchResult.java new file mode 100644 index 0000000000..69933e43ee --- /dev/null +++ b/java-client/src/main/java/org/opensearch/client/opensearch/core/search/SearchResult.java @@ -0,0 +1,620 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +/* + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ +package org.opensearch.client.opensearch.core.search; + +import org.opensearch.client.opensearch._types.ClusterStatistics; +import org.opensearch.client.opensearch._types.ShardStatistics; +import org.opensearch.client.opensearch._types.aggregations.Aggregate; +import org.opensearch.client.opensearch.core.search.HitsMetadata; +import org.opensearch.client.opensearch.core.search.Profile; +import org.opensearch.client.opensearch.core.search.Suggestion; +import org.opensearch.client.json.ExternallyTaggedUnion; +import org.opensearch.client.json.JsonData; +import org.opensearch.client.json.JsonpDeserializable; +import org.opensearch.client.json.JsonpDeserializer; +import org.opensearch.client.json.JsonpMapper; +import org.opensearch.client.json.JsonpSerializable; +import org.opensearch.client.json.JsonpSerializer; +import org.opensearch.client.json.JsonpUtils; +import org.opensearch.client.json.NamedDeserializer; +import org.opensearch.client.json.ObjectBuilderDeserializer; +import org.opensearch.client.json.ObjectDeserializer; +import org.opensearch.client.util.ApiTypeHelper; +import org.opensearch.client.util.ObjectBuilder; +import org.opensearch.client.util.ObjectBuilderBase; +import jakarta.json.stream.JsonGenerator; + +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.function.Supplier; +import javax.annotation.Nullable; + +//typedef: _global.search.SearchResult + +public abstract class SearchResult implements JsonpSerializable { + + private final long took; + + private final boolean timedOut; + + private final ShardStatistics shards; + + private final HitsMetadata hits; + + private final Map aggregations; + + @Nullable + private final ClusterStatistics clusters; + + private final List documents; + + private final Map fields; + + @Nullable + private final Double maxScore; + + @Nullable + private final Long numReducePhases; + + @Nullable + private final Profile profile; + + @Nullable + private final String pitId; + + @Nullable + private final String scrollId; + + private final Map>> suggest; + + @Nullable + private final Boolean terminatedEarly; + + @Nullable + private final JsonpSerializer tDocumentSerializer; + + // --------------------------------------------------------------------------------------------- + + protected SearchResult(AbstractBuilder builder) { + + this.took = ApiTypeHelper.requireNonNull(builder.took, this, "took"); + this.timedOut = ApiTypeHelper.requireNonNull(builder.timedOut, this, "timedOut"); + this.shards = ApiTypeHelper.requireNonNull(builder.shards, this, "shards"); + this.hits = ApiTypeHelper.requireNonNull(builder.hits, this, "hits"); + this.aggregations = ApiTypeHelper.unmodifiable(builder.aggregations); + this.clusters = builder.clusters; + this.documents = ApiTypeHelper.unmodifiable(builder.documents); + this.fields = ApiTypeHelper.unmodifiable(builder.fields); + this.maxScore = builder.maxScore; + this.numReducePhases = builder.numReducePhases; + this.profile = builder.profile; + this.pitId = builder.pitId; + this.scrollId = builder.scrollId; + this.suggest = ApiTypeHelper.unmodifiable(builder.suggest); + this.terminatedEarly = builder.terminatedEarly; + this.tDocumentSerializer = builder.tDocumentSerializer; + + } + + /** + * Required - API name: {@code took} + */ + public final long took() { + return this.took; + } + + /** + * Required - API name: {@code timed_out} + */ + public final boolean timedOut() { + return this.timedOut; + } + + /** + * Required - API name: {@code _shards} + */ + public final ShardStatistics shards() { + return this.shards; + } + + /** + * Required - API name: {@code hits} + */ + public final HitsMetadata hits() { + return this.hits; + } + + /** + * API name: {@code aggregations} + */ + public final Map aggregations() { + return this.aggregations; + } + + /** + * API name: {@code _clusters} + */ + @Nullable + public final ClusterStatistics clusters() { + return this.clusters; + } + + /** + * API name: {@code documents} + */ + public final List documents() { + return this.documents; + } + + /** + * API name: {@code fields} + */ + public final Map fields() { + return this.fields; + } + + /** + * API name: {@code max_score} + */ + @Nullable + public final Double maxScore() { + return this.maxScore; + } + + /** + * API name: {@code num_reduce_phases} + */ + @Nullable + public final Long numReducePhases() { + return this.numReducePhases; + } + + /** + * API name: {@code profile} + */ + @Nullable + public final Profile profile() { + return this.profile; + } + + /** + * API name: {@code pit_id} + */ + @Nullable + public final String pitId() { + return this.pitId; + } + + /** + * API name: {@code _scroll_id} + */ + @Nullable + public final String scrollId() { + return this.scrollId; + } + + /** + * API name: {@code suggest} + */ + public final Map>> suggest() { + return this.suggest; + } + + /** + * API name: {@code terminated_early} + */ + @Nullable + public final Boolean terminatedEarly() { + return this.terminatedEarly; + } + + /** + * Serialize this object to JSON. + */ + public void serialize(JsonGenerator generator, JsonpMapper mapper) { + generator.writeStartObject(); + serializeInternal(generator, mapper); + generator.writeEnd(); + } + + protected void serializeInternal(JsonGenerator generator, JsonpMapper mapper) { + + generator.writeKey("took"); + generator.write(this.took); + + generator.writeKey("timed_out"); + generator.write(this.timedOut); + + generator.writeKey("_shards"); + this.shards.serialize(generator, mapper); + + generator.writeKey("hits"); + this.hits.serialize(generator, mapper); + + if (ApiTypeHelper.isDefined(this.aggregations)) { + generator.writeKey("aggregations"); + ExternallyTaggedUnion.serializeTypedKeys(this.aggregations, generator, mapper); + + } + if (this.clusters != null) { + generator.writeKey("_clusters"); + this.clusters.serialize(generator, mapper); + + } + if (ApiTypeHelper.isDefined(this.documents)) { + generator.writeKey("documents"); + generator.writeStartArray(); + for (TDocument item0 : this.documents) { + JsonpUtils.serialize(item0, generator, tDocumentSerializer, mapper); + + } + generator.writeEnd(); + + } + if (ApiTypeHelper.isDefined(this.fields)) { + generator.writeKey("fields"); + generator.writeStartObject(); + for (Map.Entry item0 : this.fields.entrySet()) { + generator.writeKey(item0.getKey()); + item0.getValue().serialize(generator, mapper); + + } + generator.writeEnd(); + + } + if (this.maxScore != null) { + generator.writeKey("max_score"); + generator.write(this.maxScore); + + } + if (this.numReducePhases != null) { + generator.writeKey("num_reduce_phases"); + generator.write(this.numReducePhases); + + } + if (this.profile != null) { + generator.writeKey("profile"); + this.profile.serialize(generator, mapper); + + } + if (this.pitId != null) { + generator.writeKey("pit_id"); + generator.write(this.pitId); + + } + if (this.scrollId != null) { + generator.writeKey("_scroll_id"); + generator.write(this.scrollId); + + } + if (ApiTypeHelper.isDefined(this.suggest)) { + generator.writeKey("suggest"); + generator.writeStartObject(); + for (Map.Entry>> item0 : this.suggest.entrySet()) { + generator.writeKey(item0.getKey()); + generator.writeStartArray(); + if (item0.getValue() != null) { + for (Suggestion item1 : item0.getValue()) { + item1.serialize(generator, mapper); + + } + } + generator.writeEnd(); + + } + generator.writeEnd(); + + } + if (this.terminatedEarly != null) { + generator.writeKey("terminated_early"); + generator.write(this.terminatedEarly); + + } + + } + + // --------------------------------------------------------------------------------------------- + + protected abstract static class AbstractBuilder> + extends + ObjectBuilderBase { + private Long took; + + private Boolean timedOut; + + private ShardStatistics shards; + + private HitsMetadata hits; + + @Nullable + private Map aggregations; + + @Nullable + private ClusterStatistics clusters; + + @Nullable + private List documents; + + @Nullable + private Map fields; + + @Nullable + private Double maxScore; + + @Nullable + private Long numReducePhases; + + @Nullable + private Profile profile; + + @Nullable + private String pitId; + + @Nullable + private String scrollId; + + @Nullable + private Map>> suggest; + + @Nullable + private Boolean terminatedEarly; + + @Nullable + private JsonpSerializer tDocumentSerializer; + + /** + * Required - API name: {@code took} + */ + public final BuilderT took(long value) { + this.took = value; + return self(); + } + + /** + * Required - API name: {@code timed_out} + */ + public final BuilderT timedOut(boolean value) { + this.timedOut = value; + return self(); + } + + /** + * Required - API name: {@code _shards} + */ + public final BuilderT shards(ShardStatistics value) { + this.shards = value; + return self(); + } + + /** + * Required - API name: {@code _shards} + */ + public final BuilderT shards(Function> fn) { + return this.shards(fn.apply(new ShardStatistics.Builder()).build()); + } + + /** + * Required - API name: {@code hits} + */ + public final BuilderT hits(HitsMetadata value) { + this.hits = value; + return self(); + } + + /** + * Required - API name: {@code hits} + */ + public final BuilderT hits( + Function, ObjectBuilder>> fn) { + return this.hits(fn.apply(new HitsMetadata.Builder()).build()); + } + + /** + * API name: {@code aggregations} + *

+ * Adds all entries of map to aggregations. + */ + public final BuilderT aggregations(Map map) { + this.aggregations = _mapPutAll(this.aggregations, map); + return self(); + } + + /** + * API name: {@code aggregations} + *

+ * Adds an entry to aggregations. + */ + public final BuilderT aggregations(String key, Aggregate value) { + this.aggregations = _mapPut(this.aggregations, key, value); + return self(); + } + + /** + * API name: {@code aggregations} + *

+ * Adds an entry to aggregations using a builder lambda. + */ + public final BuilderT aggregations(String key, Function> fn) { + return aggregations(key, fn.apply(new Aggregate.Builder()).build()); + } + + /** + * API name: {@code _clusters} + */ + public final BuilderT clusters(@Nullable ClusterStatistics value) { + this.clusters = value; + return self(); + } + + /** + * API name: {@code _clusters} + */ + public final BuilderT clusters(Function> fn) { + return this.clusters(fn.apply(new ClusterStatistics.Builder()).build()); + } + + /** + * API name: {@code documents} + *

+ * Adds all elements of list to documents. + */ + public final BuilderT documents(List list) { + this.documents = _listAddAll(this.documents, list); + return self(); + } + + /** + * API name: {@code documents} + *

+ * Adds one or more values to documents. + */ + public final BuilderT documents(TDocument value, TDocument... values) { + this.documents = _listAdd(this.documents, value, values); + return self(); + } + + /** + * API name: {@code fields} + *

+ * Adds all entries of map to fields. + */ + public final BuilderT fields(Map map) { + this.fields = _mapPutAll(this.fields, map); + return self(); + } + + /** + * API name: {@code fields} + *

+ * Adds an entry to fields. + */ + public final BuilderT fields(String key, JsonData value) { + this.fields = _mapPut(this.fields, key, value); + return self(); + } + + /** + * API name: {@code max_score} + */ + public final BuilderT maxScore(@Nullable Double value) { + this.maxScore = value; + return self(); + } + + /** + * API name: {@code num_reduce_phases} + */ + public final BuilderT numReducePhases(@Nullable Long value) { + this.numReducePhases = value; + return self(); + } + + /** + * API name: {@code profile} + */ + public final BuilderT profile(@Nullable Profile value) { + this.profile = value; + return self(); + } + + /** + * API name: {@code profile} + */ + public final BuilderT profile(Function> fn) { + return this.profile(fn.apply(new Profile.Builder()).build()); + } + + /** + * API name: {@code pit_id} + */ + public final BuilderT pitId(@Nullable String value) { + this.pitId = value; + return self(); + } + + /** + * API name: {@code _scroll_id} + */ + public final BuilderT scrollId(@Nullable String value) { + this.scrollId = value; + return self(); + } + + /** + * API name: {@code suggest} + *

+ * Adds all entries of map to suggest. + */ + public final BuilderT suggest(Map>> map) { + this.suggest = _mapPutAll(this.suggest, map); + return self(); + } + + /** + * API name: {@code suggest} + *

+ * Adds an entry to suggest. + */ + public final BuilderT suggest(String key, List> value) { + this.suggest = _mapPut(this.suggest, key, value); + return self(); + } + + /** + * API name: {@code terminated_early} + */ + public final BuilderT terminatedEarly(@Nullable Boolean value) { + this.terminatedEarly = value; + return self(); + } + + /** + * Serializer for TDocument. If not set, an attempt will be made to find a + * serializer from the JSON context. + */ + public final BuilderT tDocumentSerializer(@Nullable JsonpSerializer value) { + this.tDocumentSerializer = value; + return self(); + } + + protected abstract BuilderT self(); + + } + + // --------------------------------------------------------------------------------------------- + + protected static > void setupSearchResultDeserializer( + ObjectDeserializer op, JsonpDeserializer tDocumentDeserializer) { + + op.add(AbstractBuilder::took, JsonpDeserializer.longDeserializer(), "took"); + op.add(AbstractBuilder::timedOut, JsonpDeserializer.booleanDeserializer(), "timed_out"); + op.add(AbstractBuilder::shards, ShardStatistics._DESERIALIZER, "_shards"); + op.add(AbstractBuilder::hits, HitsMetadata.createHitsMetadataDeserializer(tDocumentDeserializer), "hits"); + op.add(AbstractBuilder::aggregations, Aggregate._TYPED_KEYS_DESERIALIZER, "aggregations"); + op.add(AbstractBuilder::clusters, ClusterStatistics._DESERIALIZER, "_clusters"); + op.add(AbstractBuilder::documents, JsonpDeserializer.arrayDeserializer(tDocumentDeserializer), "documents"); + op.add(AbstractBuilder::fields, JsonpDeserializer.stringMapDeserializer(JsonData._DESERIALIZER), "fields"); + op.add(AbstractBuilder::maxScore, JsonpDeserializer.doubleDeserializer(), "max_score"); + op.add(AbstractBuilder::numReducePhases, JsonpDeserializer.longDeserializer(), "num_reduce_phases"); + op.add(AbstractBuilder::profile, Profile._DESERIALIZER, "profile"); + op.add(AbstractBuilder::pitId, JsonpDeserializer.stringDeserializer(), "pit_id"); + op.add(AbstractBuilder::scrollId, JsonpDeserializer.stringDeserializer(), "_scroll_id"); + op.add(AbstractBuilder::suggest, JsonpDeserializer.stringMapDeserializer( + JsonpDeserializer.arrayDeserializer(Suggestion.createSuggestionDeserializer(tDocumentDeserializer))), + "suggest"); + op.add(AbstractBuilder::terminatedEarly, JsonpDeserializer.booleanDeserializer(), "terminated_early"); + + } + +} diff --git a/java-client/src/test/java/org/opensearch/client/opensearch/integTest/AbstractSearchTemplateRequestIT.java b/java-client/src/test/java/org/opensearch/client/opensearch/integTest/AbstractSearchTemplateRequestIT.java new file mode 100644 index 0000000000..4a4f82a62f --- /dev/null +++ b/java-client/src/test/java/org/opensearch/client/opensearch/integTest/AbstractSearchTemplateRequestIT.java @@ -0,0 +1,206 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.client.opensearch.integTest; + +import java.io.IOException; +import java.util.Map; + +import org.junit.Test; +import org.opensearch.client.json.JsonData; +import org.opensearch.client.opensearch._types.Refresh; +import org.opensearch.client.opensearch._types.mapping.Property; +import org.opensearch.client.opensearch.core.PutScriptRequest; +import org.opensearch.client.opensearch.core.SearchTemplateResponse; + +public abstract class AbstractSearchTemplateRequestIT + extends + OpenSearchJavaClientTestCase { + + private static final String TEST_SEARCH_TEMPLATE = "search-template-integration-test"; + + @Test + public void testTemplateSearchResults() throws Exception { + var index = "test-search-template-search"; + createDocuments(index); + + var searchResponse = sendTemplateRequest(index, "NoDocuments", false, + false); + assertEquals(0, searchResponse.hits().hits().size()); + assertEquals(0, searchResponse.suggest().size()); + assertEquals(0, searchResponse.aggregations().size()); + + searchResponse = sendTemplateRequest(index, "Document", false, false); + assertEquals(4, searchResponse.hits().hits().size()); + assertEquals(0, searchResponse.suggest().size()); + assertEquals(0, searchResponse.aggregations().size()); + + searchResponse = sendTemplateRequest(index, "1", false, false); + assertEquals(1, searchResponse.hits().hits().size()); + assertEquals(1, searchResponse.hits().hits().get(0).source().getValue().intValue()); + assertEquals(0, searchResponse.suggest().size()); + assertEquals(0, searchResponse.aggregations().size()); + } + + @Test + public void testTemplateSearchSuggest() throws Exception { + var index = "test-search-template-suggs"; + createDocuments(index); + + var searchResponse = sendTemplateRequest(index, "Document", true, false); + assertEquals(4, searchResponse.hits().hits().size()); + assertEquals(1, searchResponse.suggest().size()); + assertEquals(0, searchResponse.aggregations().size()); + + // intentional typo + searchResponse = sendTemplateRequest(index, "Docunent", true, false); + assertEquals(0, searchResponse.hits().hits().size()); + assertEquals(1, searchResponse.suggest().size()); + var options = searchResponse.suggest().get("term#test-suggest").get(0).options(); + assertEquals(1, options.size()); + assertEquals("document", options.get(0).term().text()); + assertEquals(0, searchResponse.aggregations().size()); + + } + + @Test + public void testTemplateSearchAggregations() throws Exception { + var index = "test-search-template-aggs"; + createDocuments(index); + + var searchResponse = sendTemplateRequest(index, "NoDocuments", false, true); + assertEquals(0, searchResponse.hits().hits().size()); + assertEquals(0, searchResponse.suggest().size()); + assertEquals(1, searchResponse.aggregations().size()); + + searchResponse = sendTemplateRequest(index, "Document", false, true); + assertEquals(4, searchResponse.hits().hits().size()); + assertEquals(0, searchResponse.suggest().size()); + assertEquals(1, searchResponse.aggregations().size()); + var buckets = searchResponse.aggregations().get("test-aggs").sterms().buckets().array(); + assertEquals(2, buckets.size()); + assertEquals(2, buckets.get(0).docCount()); + assertEquals(2, buckets.get(1).docCount()); + + } + + private SearchTemplateResponse sendTemplateRequest(String index, + String title, boolean suggs, boolean aggs) throws IOException { + return javaClient().searchTemplate( + request -> request + .index(index) + .id(TEST_SEARCH_TEMPLATE) + .params(Map.of( + "title", JsonData.of(title), + "suggs", JsonData.of(suggs), + "aggs", JsonData.of(aggs))), + SimpleDoc.class); + } + + private void createDocuments(String index) throws IOException { + createIndex(index); + javaClient().create(_1 -> _1.index(index).id("1") + .document(createDoc("Document 1", "The text of document 1", 1)) + .refresh(Refresh.True)); + javaClient().create(_1 -> _1.index(index).id("2") + .document(createDoc("Document 2", "The text of document 2", 1)) + .refresh(Refresh.True)); + javaClient().create(_1 -> _1.index(index).id("3") + .document(createDoc("Document 3", "The text of document 3", 2)) + .refresh(Refresh.True)); + javaClient().create(_1 -> _1.index(index).id("4") + .document(createDoc("Document 4", "The text of document 4", 2)) + .refresh(Refresh.True)); + } + + private void createIndex(String index) throws IOException { + Property titleProp = new Property.Builder().text(v -> v).build(); + Property textProp = new Property.Builder().text(v -> v).build(); + Property valueProp = new Property.Builder().keyword(v -> v).build(); + javaClient().indices().create(c -> c.index(index) + .mappings(m -> m + .properties("title", titleProp) + .properties("text", textProp) + .properties("value", valueProp))); + createSearchTemplate(); + } + + /* + * """{ + "query": { + "match": { + "title": "{{title}}" + } + } + {{#suggs}} + ,"suggest" : { + "test-suggest" : { + "text" : "{{title}}", + "term" : { + "field" : "title" + } + } + } + {{/suggs}} + {{#aggs}} + ,"aggs": { + "test-aggs": { + "terms": { + "field": "value" + } + } + } + {{/aggs}} + }""" + */ + private void createSearchTemplate() throws IOException { + var templateReq = new PutScriptRequest.Builder() + .id(TEST_SEARCH_TEMPLATE) + .script(s -> s.lang("mustache").source( + "{ \"query\": { \"match\": { \"title\": \"{{title}}\" } } " + + "{{#suggs}},\"suggest\" : { \"test-suggest\" : { \"text\" : " + + "\"{{title}}\", \"term\" : { \"field\" : \"title\" } } } {{/suggs}}" + + "{{#aggs}} ,\"aggs\": {\"test-aggs\": { \"terms\": " + + "{ \"field\": \"value\" } } }{{/aggs}} }" + ) + ); + javaClient().putScript(templateReq.build()); + } + + private SimpleDoc createDoc(String title, String text, Integer value) { + return new SimpleDoc(title, text, value); + } + + public static class SimpleDoc { + private String title; + private String text; + private Integer value; + + public SimpleDoc() { + } + + public SimpleDoc(String title, String text, Integer value) { + this.title = title; + this.text = text; + this.value = value; + } + + public String getTitle() { + return title; + } + + public String getText() { + return text; + } + + public Integer getValue() { + return value; + } + } + +} diff --git a/java-client/src/test/java/org/opensearch/client/opensearch/integTest/httpclient5/SearchTemplateRequestIT.java b/java-client/src/test/java/org/opensearch/client/opensearch/integTest/httpclient5/SearchTemplateRequestIT.java new file mode 100644 index 0000000000..d0a1ebe940 --- /dev/null +++ b/java-client/src/test/java/org/opensearch/client/opensearch/integTest/httpclient5/SearchTemplateRequestIT.java @@ -0,0 +1,14 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.client.opensearch.integTest.httpclient5; + +import org.opensearch.client.opensearch.integTest.AbstractSearchTemplateRequestIT; + +public class SearchTemplateRequestIT extends AbstractSearchTemplateRequestIT implements HttpClient5TransportSupport{ +} diff --git a/java-client/src/test/java/org/opensearch/client/opensearch/integTest/restclient/SearchTemplateRequestIT.java b/java-client/src/test/java/org/opensearch/client/opensearch/integTest/restclient/SearchTemplateRequestIT.java new file mode 100644 index 0000000000..680b936ac8 --- /dev/null +++ b/java-client/src/test/java/org/opensearch/client/opensearch/integTest/restclient/SearchTemplateRequestIT.java @@ -0,0 +1,25 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.client.opensearch.integTest.restclient; + +import org.apache.hc.core5.http.HttpHost; +import org.opensearch.client.json.jackson.JacksonJsonpMapper; +import org.opensearch.client.opensearch.integTest.AbstractSearchTemplateRequestIT; +import org.opensearch.client.transport.OpenSearchTransport; +import org.opensearch.client.transport.rest_client.RestClientTransport; +import org.opensearch.common.settings.Settings; + +import java.io.IOException; + +public class SearchTemplateRequestIT extends AbstractSearchTemplateRequestIT { + @Override + public OpenSearchTransport buildTransport(Settings settings, HttpHost[] hosts) throws IOException { + return new RestClientTransport(buildClient(settings, hosts), new JacksonJsonpMapper()); + } +}