Skip to content

Commit 73b76be

Browse files
authored
Add support for typed-key arrays, refactor and add tests (#125)
1 parent 7444423 commit 73b76be

File tree

7 files changed

+393
-74
lines changed

7 files changed

+393
-74
lines changed

java-client/src/main/java/co/elastic/clients/json/ExternallyTaggedUnion.java

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,10 @@
2424
import jakarta.json.stream.JsonParser;
2525
import jakarta.json.stream.JsonParsingException;
2626

27+
import java.util.ArrayList;
2728
import java.util.EnumSet;
2829
import java.util.HashMap;
30+
import java.util.List;
2931
import java.util.Map;
3032
import java.util.function.BiFunction;
3133

@@ -57,13 +59,13 @@ public Deserializer(Map<String, JsonpDeserializer<? extends Member>> deserialize
5759
/**
5860
* Deserialize a union value, given its type.
5961
*/
60-
public Union deserialize(String type, JsonParser parser, JsonpMapper mapper) {
62+
public Union deserialize(String type, JsonParser parser, JsonpMapper mapper, Event event) {
6163
JsonpDeserializer<? extends Member> deserializer = deserializers.get(type);
6264
if (deserializer == null) {
6365
throw new JsonParsingException("Unknown variant type '" + type + "'", parser.getLocation());
6466
}
6567

66-
return unionCtor.apply(type, deserializer.deserialize(parser, mapper));
68+
return unionCtor.apply(type, deserializer.deserialize(parser, mapper, event));
6769
}
6870

6971
/**
@@ -104,10 +106,44 @@ public void deserializeEntry(String key, JsonParser parser, JsonpMapper mapper,
104106
String type = key.substring(0, hashPos);
105107
String name = key.substring(hashPos + 1);
106108

107-
targetMap.put(name, deserializer.deserialize(type, parser, mapper));
109+
targetMap.put(name, deserializer.deserialize(type, parser, mapper, parser.next()));
108110
}
109111
}
110112

113+
static <T extends TaggedUnion<?, ?>> JsonpDeserializer<Map<String, List<T>>> arrayMapDeserializer(
114+
TypedKeysDeserializer<T> deserializer
115+
) {
116+
return JsonpDeserializer.of(
117+
EnumSet.of(Event.START_OBJECT),
118+
(parser, mapper, event) -> {
119+
Map<String, List<T>> result = new HashMap<>();
120+
while ((event = parser.next()) != Event.END_OBJECT) {
121+
JsonpUtils.expectEvent(parser, event, Event.KEY_NAME);
122+
// Split key and type
123+
String key = parser.getString();
124+
int hashPos = key.indexOf('#');
125+
if (hashPos == -1) {
126+
throw new JsonParsingException(
127+
"Property name '" + key + "' is not in the 'type#name' format. Make sure the request has 'typed_keys' set.",
128+
parser.getLocation()
129+
);
130+
}
131+
132+
String type = key.substring(0, hashPos);
133+
String name = key.substring(hashPos + 1);
134+
135+
List<T> list = new ArrayList<>();
136+
JsonpUtils.expectNextEvent(parser, Event.START_ARRAY);
137+
while ((event = parser.next()) != Event.END_ARRAY) {
138+
list.add(deserializer.deserializer.deserialize(type, parser, mapper, event));
139+
}
140+
result.put(name, list);
141+
}
142+
return result;
143+
}
144+
);
145+
}
146+
111147
/**
112148
* Serialize an externally tagged union using the typed keys encoding.
113149
*/
@@ -119,6 +155,26 @@ public void deserializeEntry(String key, JsonParser parser, JsonpMapper mapper,
119155
generator.writeEnd();
120156
}
121157

158+
static <T extends JsonpSerializable & TaggedUnion<? extends JsonEnum, ?>> void serializeTypedKeysArray(
159+
Map<String, List<T>> map, JsonGenerator generator, JsonpMapper mapper
160+
) {
161+
generator.writeStartObject();
162+
for (Map.Entry<String, List<T>> entry: map.entrySet()) {
163+
List<T> list = entry.getValue();
164+
if (list.isEmpty()) {
165+
continue; // We can't know the kind, skip this entry
166+
}
167+
168+
generator.writeKey(list.get(0)._kind().jsonValue() + "#" + entry.getKey());
169+
generator.writeStartArray();
170+
for (T value: list) {
171+
value.serialize(generator, mapper);
172+
}
173+
generator.writeEnd();
174+
}
175+
generator.writeEnd();
176+
}
177+
122178
/**
123179
* Serialize an externally tagged union using the typed keys encoding, without the enclosing start/end object.
124180
*/
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package co.elastic.clients.elasticsearch;
21+
22+
import co.elastic.clients.json.JsonpMapper;
23+
import co.elastic.clients.json.jsonb.JsonbJsonpMapper;
24+
import co.elastic.clients.transport.ElasticsearchTransport;
25+
import co.elastic.clients.transport.rest_client.RestClientTransport;
26+
import org.apache.http.HttpHost;
27+
import org.apache.http.auth.AuthScope;
28+
import org.apache.http.auth.UsernamePasswordCredentials;
29+
import org.apache.http.impl.client.BasicCredentialsProvider;
30+
import org.elasticsearch.client.RestClient;
31+
import org.testcontainers.elasticsearch.ElasticsearchContainer;
32+
33+
import java.time.Duration;
34+
35+
public class ElasticsearchTestServer implements AutoCloseable {
36+
37+
private volatile ElasticsearchContainer container;
38+
private int port;
39+
private final JsonpMapper mapper = new JsonbJsonpMapper();
40+
private RestClient restClient;
41+
private ElasticsearchTransport transport;
42+
private ElasticsearchClient client;
43+
44+
private static ElasticsearchTestServer global;
45+
46+
public static synchronized ElasticsearchTestServer global() {
47+
if (global == null) {
48+
System.out.println("Starting global ES test server.");
49+
global = new ElasticsearchTestServer();
50+
global.setup();
51+
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
52+
System.out.println("Stopping global ES test server.");
53+
global.close();
54+
}));
55+
}
56+
return global;
57+
}
58+
59+
private synchronized void setup() {
60+
container = new ElasticsearchContainer("docker.elastic.co/elasticsearch/elasticsearch:7.16.2")
61+
.withEnv("ES_JAVA_OPTS", "-Xms256m -Xmx256m")
62+
.withEnv("path.repo", "/tmp") // for snapshots
63+
.withStartupTimeout(Duration.ofSeconds(30))
64+
.withPassword("changeme");
65+
container.start();
66+
port = container.getMappedPort(9200);
67+
68+
BasicCredentialsProvider credsProv = new BasicCredentialsProvider();
69+
credsProv.setCredentials(
70+
AuthScope.ANY, new UsernamePasswordCredentials("elastic", "changeme")
71+
);
72+
restClient = RestClient.builder(new HttpHost("localhost", port))
73+
.setHttpClientConfigCallback(hc -> hc.setDefaultCredentialsProvider(credsProv))
74+
.build();
75+
transport = new RestClientTransport(restClient, mapper);
76+
client = new ElasticsearchClient(transport);
77+
}
78+
79+
@Override
80+
public void close() {
81+
if (this == global) {
82+
// Closed with a shutdown hook
83+
return;
84+
}
85+
86+
if (container != null) {
87+
container.stop();
88+
}
89+
container = null;
90+
}
91+
92+
public int port() {
93+
return port;
94+
}
95+
96+
public RestClient restClient() {
97+
return restClient;
98+
}
99+
100+
public ElasticsearchTransport transport() {
101+
return transport;
102+
}
103+
104+
public JsonpMapper mapper() {
105+
return mapper;
106+
}
107+
108+
public ElasticsearchClient client() {
109+
return client;
110+
}
111+
}

java-client/src/test/java/co/elastic/clients/elasticsearch/end_to_end/RequestTest.java

Lines changed: 17 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import co.elastic.clients.elasticsearch.ElasticsearchAsyncClient;
2323
import co.elastic.clients.elasticsearch.ElasticsearchClient;
24+
import co.elastic.clients.elasticsearch.ElasticsearchTestServer;
2425
import co.elastic.clients.elasticsearch._types.ElasticsearchException;
2526
import co.elastic.clients.elasticsearch._types.Refresh;
2627
import co.elastic.clients.elasticsearch._types.aggregations.HistogramAggregate;
@@ -42,26 +43,12 @@
4243
import co.elastic.clients.elasticsearch.indices.GetMappingResponse;
4344
import co.elastic.clients.elasticsearch.indices.IndexState;
4445
import co.elastic.clients.elasticsearch.model.ModelTestCase;
45-
import co.elastic.clients.elasticsearch.snapshot.CreateRepositoryResponse;
46-
import co.elastic.clients.elasticsearch.snapshot.CreateSnapshotResponse;
47-
import co.elastic.clients.json.JsonpMapper;
48-
import co.elastic.clients.json.jsonb.JsonbJsonpMapper;
49-
import co.elastic.clients.transport.ElasticsearchTransport;
5046
import co.elastic.clients.transport.endpoints.BooleanResponse;
51-
import co.elastic.clients.transport.rest_client.RestClientTransport;
52-
import org.apache.http.HttpHost;
53-
import org.apache.http.auth.AuthScope;
54-
import org.apache.http.auth.UsernamePasswordCredentials;
55-
import org.apache.http.impl.client.BasicCredentialsProvider;
56-
import org.elasticsearch.client.RestClient;
57-
import org.junit.AfterClass;
5847
import org.junit.Assert;
5948
import org.junit.BeforeClass;
6049
import org.junit.Test;
61-
import org.testcontainers.elasticsearch.ElasticsearchContainer;
6250

6351
import java.io.IOException;
64-
import java.time.Duration;
6552
import java.util.Collections;
6653
import java.util.Map;
6754
import java.util.concurrent.CompletableFuture;
@@ -70,38 +57,11 @@
7057

7158
public class RequestTest extends Assert {
7259

73-
private static ElasticsearchContainer container;
74-
private static final JsonpMapper mapper = new JsonbJsonpMapper();
75-
private static RestClient restClient;
76-
private static ElasticsearchTransport transport;
77-
private static ElasticsearchClient client;
60+
static ElasticsearchClient client;
7861

7962
@BeforeClass
8063
public static void setup() {
81-
container = new ElasticsearchContainer("docker.elastic.co/elasticsearch/elasticsearch:7.16.2")
82-
.withEnv("ES_JAVA_OPTS", "-Xms256m -Xmx256m")
83-
.withEnv("path.repo", "/tmp") // for snapshots
84-
.withStartupTimeout(Duration.ofSeconds(30))
85-
.withPassword("changeme");
86-
container.start();
87-
int port = container.getMappedPort(9200);
88-
89-
BasicCredentialsProvider credsProv = new BasicCredentialsProvider();
90-
credsProv.setCredentials(
91-
AuthScope.ANY, new UsernamePasswordCredentials("elastic", "changeme")
92-
);
93-
restClient = RestClient.builder(new HttpHost("localhost", port))
94-
.setHttpClientConfigCallback(hc -> hc.setDefaultCredentialsProvider(credsProv))
95-
.build();
96-
transport = new RestClientTransport(restClient, mapper);
97-
client = new ElasticsearchClient(transport);
98-
}
99-
100-
@AfterClass
101-
public static void tearDown() {
102-
if (container != null) {
103-
container.stop();
104-
}
64+
client = ElasticsearchTestServer.global().client();
10565
}
10666

10767
@Test
@@ -112,7 +72,7 @@ public void testCount() throws Exception {
11272

11373
@Test
11474
public void testIndexCreation() throws Exception {
115-
ElasticsearchAsyncClient asyncClient = new ElasticsearchAsyncClient(transport);
75+
ElasticsearchAsyncClient asyncClient = new ElasticsearchAsyncClient(client._transport());
11676

11777
// Ping the server
11878
assertTrue(client.ping().value());
@@ -222,7 +182,7 @@ public void testDataIngestion() throws Exception {
222182
public void testCatRequest() throws IOException {
223183
// Cat requests should have the "format=json" added by the transport
224184
NodesResponse nodes = client.cat().nodes(_0 -> _0);
225-
System.out.println(ModelTestCase.toJson(nodes, mapper));
185+
System.out.println(ModelTestCase.toJson(nodes, client._transport().jsonpMapper()));
226186

227187
assertEquals(1, nodes.valueBody().size());
228188
assertEquals("*", nodes.valueBody().get(0).master());
@@ -247,15 +207,25 @@ public void testBulkRequest() throws IOException {
247207
.id("def")
248208
.document(appData)
249209
))
210+
.operations(_1 -> _1
211+
.update(_2 -> _2
212+
.index("foo")
213+
.id("gh")
214+
.action(_3 -> _3
215+
.docAsUpsert(true)
216+
.doc(appData))
217+
)
218+
)
250219
);
251220

252221
assertFalse(bulk.errors());
253-
assertEquals(2, bulk.items().size());
222+
assertEquals(3, bulk.items().size());
254223
assertEquals(OperationType.Create, bulk.items().get(0).operationType());
255224
assertEquals("foo", bulk.items().get(0).index());
256225
assertEquals(1L, bulk.items().get(0).version().longValue());
257226
assertEquals("foo", bulk.items().get(1).index());
258227
assertEquals(1L, bulk.items().get(1).version().longValue());
228+
assertEquals(42, client.get(b -> b.index("foo").id("gh"), AppData.class).source().intValue);
259229
}
260230

261231
@Test
@@ -291,7 +261,7 @@ public void testRefresh() throws IOException {
291261

292262

293263
ExecutionException ee = assertThrows(ExecutionException.class, () -> {
294-
ElasticsearchAsyncClient aClient = new ElasticsearchAsyncClient(transport);
264+
ElasticsearchAsyncClient aClient = new ElasticsearchAsyncClient(client._transport());
295265
GetResponse<String> response = aClient.get(
296266
_0 -> _0.index("doesnotexist").id("reallynot"), String.class
297267
).get();
@@ -398,30 +368,6 @@ public void testDefaultIndexSettings() throws IOException {
398368
assertNull(settings.get(index).defaults());
399369
}
400370

401-
@Test
402-
public void testSnapshotCreation() throws IOException {
403-
// https://github.com/elastic/elasticsearch-java/issues/74
404-
// https://github.com/elastic/elasticsearch/issues/82358
405-
406-
CreateRepositoryResponse repo = client.snapshot().createRepository(b1 -> b1
407-
.name("test")
408-
.type("fs")
409-
.settings(b2 -> b2
410-
.location("/tmp/test-repo")
411-
)
412-
);
413-
414-
assertTrue(repo.acknowledged());
415-
416-
CreateSnapshotResponse snapshot = client.snapshot().create(b -> b
417-
.repository("test")
418-
.snapshot("1")
419-
.waitForCompletion(true)
420-
);
421-
422-
assertNotNull(snapshot.snapshot());
423-
}
424-
425371
@Test
426372
public void testValueBodyResponse() throws Exception {
427373
DiskUsageResponse resp = client.indices().diskUsage(b -> b

0 commit comments

Comments
 (0)