diff --git a/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/action/EqlSearchResponse.java b/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/action/EqlSearchResponse.java index 4631c07d266..ba3f77933b2 100644 --- a/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/action/EqlSearchResponse.java +++ b/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/action/EqlSearchResponse.java @@ -24,7 +24,6 @@ import org.elasticsearch.search.SearchHits; import java.io.IOException; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Objects; @@ -36,44 +35,43 @@ import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpect * Response to perform an eql search * * Example events response: - * EqlSearchResponse.Events events = new EqlSearchResponse.Events(new SearchHit[]{ + * List<SearchHit> events = Arrays.asList( * new SearchHit(1, "111", null), - * new SearchHit(2, "222", null), - * }); - * - * EqlSearchResponse.Hits hits = new EqlSearchResponse.Hits(events, new TotalHits(100, TotalHits.Relation.EQUAL_TO)); + * new SearchHit(2, "222", null) + * ); + * EqlSearchResponse.Hits hits = new EqlSearchResponse.Hits(Arrays.asList( + * new EqlSearchResponse.Sequence(Collections.singletonList("4021"), events), + * new EqlSearchResponse.Sequence(Collections.singletonList("2343"), events) + * ), null, null, new TotalHits(0, TotalHits.Relation.EQUAL_TO)); * EqlSearchResponse response = new EqlSearchResponse(hits, 5, false); * * * Example sequence response: - * EqlSearchResponse.Events events1 = new EqlSearchResponse.Events(new SearchHit[]{ + * List<SearchHit> events1 = Arrays.asList( * new SearchHit(1, "111", null), - * new SearchHit(2, "222", null), - * }); - * EqlSearchResponse.Events events2 = new EqlSearchResponse.Events(new SearchHit[]{ - * new SearchHit(3, "333", null), - * new SearchHit(4, "444", null), - * }); - * EqlSearchResponse.Sequences sequences = new EqlSearchResponse.Sequences( - * new EqlSearchResponse.Sequence[]{ + * new SearchHit(2, "222", null) + * ); + * List<SearchHit> events2 = Arrays.asList( + * new SearchHit(1, "333", null), + * new SearchHit(2, "444", null) + * ); + * List<Sequence> sequences = Arrays.asList( * new EqlSearchResponse.Sequence(new String[]{"4021"}, events1), * new EqlSearchResponse.Sequence(new String[]{"2343"}, events2) - * }); + * ); * - * EqlSearchResponse.Hits hits = new EqlSearchResponse.Hits(sequences, new TotalHits(100, TotalHits.Relation.EQUAL_TO)); + * EqlSearchResponse.Hits hits = new EqlSearchResponse.Hits(null, sequences, null, new TotalHits(100, TotalHits.Relation.EQUAL_TO)); * EqlSearchResponse response = new EqlSearchResponse(hits, 5, false); * * * Example count response: * TotalHits totals = new TotalHits(100, TotalHits.Relation.EQUAL_TO); - * EqlSearchResponse.Counts counts = new EqlSearchResponse.Counts( - * new EqlSearchResponse.Count[]{ + * List<Count> counts = Arrays.asList( * new EqlSearchResponse.Count(40, new String[]{"foo", "bar"}, .42233f), - * new EqlSearchResponse.Count(15, new String[]{"foo", "bar"}, .170275f), - * } + * new EqlSearchResponse.Count(15, new String[]{"foo", "bar"}, .170275f) * ); * - * EqlSearchResponse.Hits hits = new EqlSearchResponse.Hits(counts, totals); + * EqlSearchResponse.Hits hits = new EqlSearchResponse.Hits(null, null, counts, totals); * EqlSearchResponse response = new EqlSearchResponse(hits, 5, false); */ public class EqlSearchResponse extends ActionResponse implements ToXContentObject { @@ -188,17 +186,18 @@ public class EqlSearchResponse extends ActionResponse implements ToXContentObjec public static class Sequence implements Writeable, ToXContentObject { private static final class Fields { static final String JOIN_KEYS = "join_keys"; + static final String EVENTS = "events"; } private static final ParseField JOIN_KEYS = new ParseField(Fields.JOIN_KEYS); - private static final ParseField EVENTS = new ParseField(Events.NAME); + private static final ParseField EVENTS = new ParseField(Fields.EVENTS); private static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>("eql/search_response_sequence", true, args -> { int i = 0; @SuppressWarnings("unchecked") List joinKeys = (List) args[i++]; - @SuppressWarnings("unchecked") Events events = new Events(((List) args[i]).toArray(new SearchHit[0])); + @SuppressWarnings("unchecked") List events = (List) args[i]; return new EqlSearchResponse.Sequence(joinKeys, events); }); @@ -208,16 +207,16 @@ public class EqlSearchResponse extends ActionResponse implements ToXContentObjec } private final List joinKeys; - private final Events events; + private final List events; - public Sequence(List joinKeys, Events events) { + public Sequence(List joinKeys, List events) { this.joinKeys = joinKeys == null ? Collections.emptyList() : joinKeys; - this.events = events == null ? Events.EMPTY : events; + this.events = events == null ? Collections.emptyList() : events; } public Sequence(StreamInput in) throws IOException { this.joinKeys = in.readStringList(); - this.events = new Events(in); + this.events = in.readList(SearchHit::new); } public static Sequence fromXContent(XContentParser parser) { @@ -227,27 +226,22 @@ public class EqlSearchResponse extends ActionResponse implements ToXContentObjec @Override public void writeTo(StreamOutput out) throws IOException { out.writeStringCollection(joinKeys); - out.writeVInt(events.entries().length); - if (events.entries().length > 0) { - for (SearchHit hit : events.entries()) { - hit.writeTo(out); - } - } + out.writeList(events); } @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { builder.startObject(); - if (joinKeys != null) { - builder.startArray(Fields.JOIN_KEYS); - for(String s : joinKeys) { - builder.value(s); + if (joinKeys.isEmpty() == false) { + builder.field(Fields.JOIN_KEYS, joinKeys); + } + if (events.isEmpty() == false) { + builder.startArray(EVENTS.getPreferredName()); + for (SearchHit event : events) { + event.toXContent(builder, params); } builder.endArray(); } - if (events != null) { - events.toXContent(builder, params); - } builder.endObject(); return builder; } @@ -356,184 +350,41 @@ public class EqlSearchResponse extends ActionResponse implements ToXContentObjec } } - // Base class for serialized collection of entries (events, sequences, counts) - private abstract static class Entries implements Writeable, ToXContentFragment { - private final String name; - private final T[] entries; - - Entries(String name, T[] entries) { - this.name = name; - if (entries == null) { - this.entries = createEntriesArray(0); - } else { - this.entries = entries; - } - } - - Entries(String name, StreamInput in) throws IOException { - this.name = name; - int size = in.readVInt(); - entries = createEntriesArray(size); - for (int i = 0; i < size; i++) { - entries[i] = createEntry(in); - } - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - out.writeVInt(entries.length); - if (entries.length > 0) { - for (T e : entries) { - e.writeTo(out); - } - } - } - - @Override - public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - builder.startArray(name); - if (entries != null) { - for (T e : entries) { - e.toXContent(builder, params); - } - } - builder.endArray(); - return null; - } - - public T[] entries() { - return entries; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - Entries that = (Entries) o; - return Arrays.deepEquals(entries, that.entries); - } - - @Override - public int hashCode() { - return Arrays.hashCode(entries); - } - - protected abstract T[] createEntriesArray(int size); - protected abstract T createEntry(StreamInput in) throws IOException; - } - - // Events - public static class Events extends EqlSearchResponse.Entries { - private static final Events EMPTY = new Events((SearchHit[]) null); - private static final String NAME = "events"; - - public Events(SearchHit[] entries) { - super(NAME, entries); - } - - public Events(StreamInput in) throws IOException { - super(NAME, in); - } - - @Override - protected final SearchHit[] createEntriesArray(int size) { - return new SearchHit[size]; - } - - @Override - protected final SearchHit createEntry(StreamInput in) throws IOException { - return new SearchHit(in); - } - } - - // Sequences - public static class Sequences extends EqlSearchResponse.Entries { - private static String NAME = "sequences"; - - public Sequences(Sequence[] entries) { - super(NAME, entries); - } - - public Sequences(StreamInput in) throws IOException { - super(NAME, in); - } - - @Override - protected final Sequence[] createEntriesArray(int size) { - return new Sequence[size]; - } - - @Override - protected final Sequence createEntry(StreamInput in) throws IOException { - return new Sequence(in); - } - } - - // Counts - public static class Counts extends EqlSearchResponse.Entries { - private static String NAME = "counts"; - public Counts(Count[] entries) { - super(NAME, entries); - } - - public Counts(StreamInput in) throws IOException { - super(NAME, in); - } - - @Override - protected final Count[] createEntriesArray(int size) { - return new Count[size]; - } - - @Override - protected final Count createEntry(StreamInput in) throws IOException { - return new Count(in); - } - } - // Hits public static class Hits implements Writeable, ToXContentFragment { - public static final Hits EMPTY = new Hits((Events)null, null); + public static final Hits EMPTY = new Hits(null, null, null, null); - private Events events = null; - private Sequences sequences = null; - private Counts counts = null; - private TotalHits totalHits; + private final List events; + private final List sequences; + private final List counts; + private final TotalHits totalHits; private static final class Fields { static final String HITS = "hits"; static final String TOTAL = "total"; + static final String EVENTS = "events"; + static final String SEQUENCES = "sequences"; + static final String COUNTS = "counts"; } - public Hits(Events events, @Nullable TotalHits totalHits) { + public Hits(@Nullable List events, @Nullable List sequences, @Nullable List counts, + @Nullable TotalHits totalHits) { this.events = events; - this.totalHits = totalHits; - } - - public Hits(Sequences sequences, @Nullable TotalHits totalHits) { this.sequences = sequences; - this.totalHits = totalHits; - } - - public Hits(Counts counts, @Nullable TotalHits totalHits) { this.counts = counts; this.totalHits = totalHits; } + public Hits(StreamInput in) throws IOException { if (in.readBoolean()) { totalHits = Lucene.readTotalHits(in); } else { totalHits = null; } - - events = in.readBoolean() ? new Events(in) : null; - sequences = in.readBoolean() ? new Sequences(in) : null; - counts = in.readBoolean() ? new Counts(in) : null; + events = in.readBoolean() ? in.readList(SearchHit::new) : null; + sequences = in.readBoolean() ? in.readList(Sequence::new) : null; + counts = in.readBoolean() ? in.readList(Count::new) : null; } @Override @@ -543,9 +394,24 @@ public class EqlSearchResponse extends ActionResponse implements ToXContentObjec if (hasTotalHits) { Lucene.writeTotalHits(out, totalHits); } - out.writeOptionalWriteable(events); - out.writeOptionalWriteable(sequences); - out.writeOptionalWriteable(counts); + if (events != null) { + out.writeBoolean(true); + out.writeList(events); + } else { + out.writeBoolean(false); + } + if (sequences != null) { + out.writeBoolean(true); + out.writeList(sequences); + } else { + out.writeBoolean(false); + } + if (counts != null) { + out.writeBoolean(true); + out.writeList(counts); + } else { + out.writeBoolean(false); + } } public static Hits fromXContent(XContentParser parser) throws IOException { @@ -568,17 +434,17 @@ public class EqlSearchResponse extends ActionResponse implements ToXContentObjec totalHits = new TotalHits(parser.longValue(), TotalHits.Relation.EQUAL_TO); } } else if (token == XContentParser.Token.START_ARRAY) { - if (Events.NAME.equals(currentFieldName)) { + if (Fields.EVENTS.equals(currentFieldName)) { searchHits = new ArrayList<>(); while (parser.nextToken() != XContentParser.Token.END_ARRAY) { searchHits.add(SearchHit.fromXContent(parser)); } - } else if (Sequences.NAME.equals(currentFieldName)) { + } else if (Fields.SEQUENCES.equals(currentFieldName)) { sequences = new ArrayList<>(); while (parser.nextToken() != XContentParser.Token.END_ARRAY) { sequences.add(Sequence.fromXContent(parser)); } - } else if (Counts.NAME.equals(currentFieldName)) { + } else if (Fields.COUNTS.equals(currentFieldName)) { counts = new ArrayList<>(); while (parser.nextToken() != XContentParser.Token.END_ARRAY) { counts.add(Count.fromXContent(parser)); @@ -594,17 +460,7 @@ public class EqlSearchResponse extends ActionResponse implements ToXContentObjec } } } - - if (searchHits != null) { - return new EqlSearchResponse.Hits(new EqlSearchResponse.Events(searchHits.toArray(new SearchHit[searchHits.size()])), - totalHits); - } else if (sequences != null) { - return new EqlSearchResponse.Hits(new EqlSearchResponse.Sequences(sequences.toArray(new Sequence[sequences.size()])), - totalHits); - } else if (counts != null) { - return new EqlSearchResponse.Hits(new EqlSearchResponse.Counts(counts.toArray(new Count[counts.size()])), totalHits); - } - return new EqlSearchResponse.Hits((Events)null, totalHits); + return new EqlSearchResponse.Hits(searchHits, sequences, counts, totalHits); } @Override @@ -617,13 +473,17 @@ public class EqlSearchResponse extends ActionResponse implements ToXContentObjec builder.endObject(); } if (events != null) { - events.toXContent(builder, params); + builder.startArray(Fields.EVENTS); + for (SearchHit event : events) { + event.toXContent(builder, params); + } + builder.endArray(); } if (sequences != null) { - sequences.toXContent(builder, params); + builder.field(Fields.SEQUENCES, sequences); } if (counts != null) { - counts.toXContent(builder, params); + builder.field(Fields.COUNTS, counts); } builder.endObject(); diff --git a/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/plugin/TransportEqlSearchAction.java b/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/plugin/TransportEqlSearchAction.java index 082dc90a823..b73183f500e 100644 --- a/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/plugin/TransportEqlSearchAction.java +++ b/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/plugin/TransportEqlSearchAction.java @@ -22,7 +22,9 @@ import org.elasticsearch.xpack.eql.action.EqlSearchAction; import org.elasticsearch.xpack.eql.action.EqlSearchRequest; import org.elasticsearch.xpack.eql.action.EqlSearchResponse; +import java.util.Arrays; import java.util.Collections; +import java.util.List; public class TransportEqlSearchAction extends HandledTransportAction { private final SecurityContext securityContext; @@ -51,14 +53,14 @@ public class TransportEqlSearchAction extends HandledTransportAction events = Arrays.asList( new SearchHit(1, "111", null), - new SearchHit(2, "222", null), - }); - EqlSearchResponse.Hits hits = new EqlSearchResponse.Hits(new EqlSearchResponse.Sequences(new EqlSearchResponse.Sequence[]{ + new SearchHit(2, "222", null) + ); + EqlSearchResponse.Hits hits = new EqlSearchResponse.Hits(null, Arrays.asList( new EqlSearchResponse.Sequence(Collections.singletonList("4021"), events), new EqlSearchResponse.Sequence(Collections.singletonList("2343"), events) - }), new TotalHits(0, TotalHits.Relation.EQUAL_TO)); + ), null, new TotalHits(0, TotalHits.Relation.EQUAL_TO)); return new EqlSearchResponse(hits, 0, false); } } diff --git a/x-pack/plugin/eql/src/test/java/org/elasticsearch/xpack/eql/action/EqlSearchResponseTests.java b/x-pack/plugin/eql/src/test/java/org/elasticsearch/xpack/eql/action/EqlSearchResponseTests.java index 98ee723b693..d0b207c1d25 100644 --- a/x-pack/plugin/eql/src/test/java/org/elasticsearch/xpack/eql/action/EqlSearchResponseTests.java +++ b/x-pack/plugin/eql/src/test/java/org/elasticsearch/xpack/eql/action/EqlSearchResponseTests.java @@ -11,23 +11,24 @@ import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.search.SearchHit; import org.elasticsearch.test.AbstractSerializingTestCase; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; public class EqlSearchResponseTests extends AbstractSerializingTestCase { - static EqlSearchResponse.Events randomEvents() { + static List randomEvents() { int size = randomIntBetween(1, 10); - SearchHit[] hits = null; + List hits = null; if (randomBoolean()) { - hits = new SearchHit[size]; + hits = new ArrayList<>(); for (int i = 0; i < size; i++) { - hits[i] = new SearchHit(i, randomAlphaOfLength(10), new HashMap<>()); + hits.add(new SearchHit(i, randomAlphaOfLength(10), new HashMap<>())); } } if (randomBoolean()) { - return new EqlSearchResponse.Events(hits); + return hits; } return null; } @@ -49,55 +50,47 @@ public class EqlSearchResponseTests extends AbstractSerializingTestCase seq = null; if (randomBoolean()) { - seq = new EqlSearchResponse.Sequence[size]; + seq = new ArrayList<>(); for (int i = 0; i < size; i++) { List joins = null; if (randomBoolean()) { joins = Arrays.asList(generateRandomStringArray(6, 11, false)); } - seq[i] = new EqlSearchResponse.Sequence(joins, randomEvents()); + seq.add(new EqlSearchResponse.Sequence(joins, randomEvents())); } } - EqlSearchResponse.Sequences sequences = null; - if (randomBoolean()) { - sequences = new EqlSearchResponse.Sequences(seq); - } EqlSearchResponse.Hits hits = null; if (randomBoolean()) { - hits = new EqlSearchResponse.Hits(sequences, totalHits); + hits = new EqlSearchResponse.Hits(null, seq, null, totalHits); } return new EqlSearchResponse(hits, randomIntBetween(0, 1001), randomBoolean()); } public static EqlSearchResponse createRandomCountResponse(TotalHits totalHits) { int size = randomIntBetween(1, 10); - EqlSearchResponse.Count[] cn = null; + List cn = null; if (randomBoolean()) { - cn = new EqlSearchResponse.Count[size]; + cn = new ArrayList<>(); for (int i = 0; i < size; i++) { List keys = null; if (randomBoolean()) { keys = Arrays.asList(generateRandomStringArray(6, 11, false)); } - cn[i] = new EqlSearchResponse.Count(randomIntBetween(0, 41), keys, randomFloat()); + cn.add(new EqlSearchResponse.Count(randomIntBetween(0, 41), keys, randomFloat())); } } - EqlSearchResponse.Counts counts = null; - if (randomBoolean()) { - counts = new EqlSearchResponse.Counts(cn); - } EqlSearchResponse.Hits hits = null; if (randomBoolean()) { - hits = new EqlSearchResponse.Hits(counts, totalHits); + hits = new EqlSearchResponse.Hits(null, null, cn, totalHits); } return new EqlSearchResponse(hits, randomIntBetween(0, 1001), randomBoolean()); }