Skip to content

Commit df5a9bc

Browse files
committed
feat: migrate from HLRC to elasticsearch-java 8.x client
Replace the deprecated High Level REST Client with the new elasticsearch-java 8.x client. Upgrade testcontainers to 1.21.4 to support the updated client in integration tests. Remove the now-dead DATA_STREAM_COMPATIBLE_ES_VERSION check and its test — the connector minimum of ES 8.0.0 makes the old 7.9.0 data-stream floor unreachable.
1 parent 7900911 commit df5a9bc

28 files changed

Lines changed: 1396 additions & 1080 deletions

checkstyle/suppressions.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
<!-- switch statements on types exceed maximum complexity -->
1010
<suppress
11-
checks="(CyclomaticComplexity)"
11+
checks="(CyclomaticComplexity|NPathComplexity)"
1212
files="(Mapping|DataConverter).java"
1313
/>
1414

pom.xml

Lines changed: 23 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -37,25 +37,23 @@
3737
</scm>
3838

3939
<properties>
40-
<es.version>7.17.24</es.version>
40+
<elasticsearch.java.version>8.19.15</elasticsearch.java.version>
4141
<lz4.version>1.10.1</lz4.version>
4242
<hamcrest.version>1.3</hamcrest.version>
4343
<gson.version>2.9.0</gson.version>
44-
<test.containers.version>1.16.3</test.containers.version>
44+
<test.containers.version>1.21.4</test.containers.version>
4545
<kafka.connect.maven.plugin.version>0.11.1</kafka.connect.maven.plugin.version>
4646
<maven.release.plugin.version>2.5.3</maven.release.plugin.version>
4747
<hadoop.version>3.3.1</hadoop.version>
4848
<apacheds-jdbm1.version>2.0.0-M2</apacheds-jdbm1.version>
49-
<!-- TODO: Remove the version pin after releasing https://github.com/confluentinc/common/pull/494 -->
50-
<jackson.databind.version>2.15.2</jackson.databind.version>
51-
<jackson.version>2.15.2</jackson.version>
49+
<log4j.version>2.25.4</log4j.version>
50+
<jackson.version>2.18.6</jackson.version>
5251
<!-- temporary fix by pinning the version until we upgrade to a version of common that contains this or newer version.
5352
See https://github.com/confluentinc/common/pull/332 for details (common-parent 7.0.0-0) -->
5453
<dependency.check.version>6.1.6</dependency.check.version>
5554
<confluent.maven.repo>http://packages.confluent.io/maven/</confluent.maven.repo>
5655
<commons.codec.version>1.15</commons.codec.version>
5756
<confluent.version>[7.9.0,7.9.1)</confluent.version>
58-
<jackson.version>2.16.0</jackson.version>
5957
<dependency.check.skip>true</dependency.check.skip>
6058
</properties>
6159

@@ -86,28 +84,12 @@
8684
</dependency>
8785
<dependency>
8886
<groupId>org.elasticsearch.client</groupId>
89-
<artifactId>elasticsearch-rest-high-level-client</artifactId>
90-
<version>${es.version}</version>
91-
</dependency>
92-
<dependency>
93-
<groupId>org.elasticsearch</groupId>
94-
<artifactId>elasticsearch</artifactId>
95-
<version>${es.version}</version>
96-
<exclusions>
97-
<exclusion>
98-
<groupId>org.yaml</groupId>
99-
<artifactId>snakeyaml</artifactId>
100-
</exclusion>
101-
<exclusion>
102-
<groupId>org.lz4</groupId>
103-
<artifactId>lz4-java</artifactId>
104-
</exclusion>
105-
</exclusions>
87+
<artifactId>elasticsearch-rest-client</artifactId>
88+
<version>${elasticsearch.java.version}</version>
10689
</dependency>
10790
<dependency>
10891
<groupId>org.apache.logging.log4j</groupId>
10992
<artifactId>log4j-api</artifactId>
110-
<version>2.17.1</version>
11193
</dependency>
11294
<!-- pin jackson-dataformat-cbor for CVE - the version comes from confluentinc/common -->
11395
<dependency>
@@ -118,7 +100,7 @@
118100
<dependency>
119101
<groupId>co.elastic.clients</groupId>
120102
<artifactId>elasticsearch-java</artifactId>
121-
<version>8.2.2</version>
103+
<version>${elasticsearch.java.version}</version>
122104
</dependency>
123105
<dependency>
124106
<groupId>org.eclipse.parsson</groupId>
@@ -242,6 +224,13 @@
242224
<artifactId>hadoop-minikdc</artifactId>
243225
<version>${hadoop.version}</version>
244226
<scope>test</scope>
227+
<exclusions>
228+
<!-- log4j 1.x is end-of-life and the 1.2.17 artifact is no longer reliably resolvable from upstream mirrors. reload4j (declared explicitly in test scope) provides drop-in org.apache.log4j.* classes for slf4j-log4j12. -->
229+
<exclusion>
230+
<groupId>log4j</groupId>
231+
<artifactId>log4j</artifactId>
232+
</exclusion>
233+
</exclusions>
245234
</dependency>
246235
<dependency>
247236
<groupId>org.assertj</groupId>
@@ -322,11 +311,6 @@
322311
<artifactId>jackson-annotations</artifactId>
323312
<version>${jackson.version}</version>
324313
</dependency>
325-
<dependency>
326-
<groupId>com.fasterxml.jackson.core</groupId>
327-
<artifactId>jackson-databind</artifactId>
328-
<version>${jackson.databind.version}</version>
329-
</dependency>
330314
<dependency>
331315
<groupId>com.fasterxml.jackson</groupId>
332316
<artifactId>jackson-bom</artifactId>
@@ -339,6 +323,14 @@
339323
<artifactId>jackson-dataformat-cbor</artifactId>
340324
<version>${jackson.version}</version>
341325
</dependency>
326+
<!-- Align all log4j artifacts (incl. transitive log4j-core via elasticsearch) to ${log4j.version}. -->
327+
<dependency>
328+
<groupId>org.apache.logging.log4j</groupId>
329+
<artifactId>log4j-bom</artifactId>
330+
<version>${log4j.version}</version>
331+
<scope>import</scope>
332+
<type>pom</type>
333+
</dependency>
342334
</dependencies>
343335
</dependencyManagement>
344336

@@ -501,7 +493,7 @@
501493
</tags>
502494

503495
<requirements>
504-
<requirement>Elasticsearch 7.x</requirement>
496+
<requirement>Elasticsearch 8.x / 9.x</requirement>
505497
</requirements>
506498

507499
<deliveryGuarantee>

service.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: kafka-connect-elasticsearch
22
lang: java
3-
lang_version: 8
3+
lang_version: "8"
44
git:
55
enable: true
66
codeowners:
@@ -13,6 +13,8 @@ semaphore:
1313
extra_build_args: "-Dcloud -Pjenkins"
1414
run_pint_merge: true
1515
generate_connect_changelogs: true
16+
github_write_targets:
17+
- confluentinc/docs-kafka-connect-elasticsearch
1618
code_artifact:
1719
enable: true
1820
package_paths:

src/main/java/io/confluent/connect/elasticsearch/DataConverter.java

Lines changed: 74 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,10 @@
3737
import org.apache.kafka.connect.storage.Converter;
3838
import org.apache.kafka.connect.storage.HeaderConverter;
3939
import org.apache.kafka.connect.storage.SimpleHeaderConverter;
40-
import org.elasticsearch.action.DocWriteRequest;
41-
import org.elasticsearch.action.DocWriteRequest.OpType;
42-
import org.elasticsearch.action.delete.DeleteRequest;
43-
import org.elasticsearch.action.index.IndexRequest;
44-
import org.elasticsearch.action.update.UpdateRequest;
45-
import org.elasticsearch.index.VersionType;
46-
import org.elasticsearch.xcontent.XContentType;
40+
import co.elastic.clients.elasticsearch._types.VersionType;
41+
import co.elastic.clients.elasticsearch.core.bulk.BulkOperation;
42+
import co.elastic.clients.json.JsonData;
43+
4744
import org.slf4j.Logger;
4845
import org.slf4j.LoggerFactory;
4946

@@ -121,7 +118,7 @@ private String convertKey(Schema keySchema, Object key) {
121118
}
122119
}
123120

124-
public DocWriteRequest<?> convertRecord(SinkRecord record, String resourceName) {
121+
public BulkOperation convertRecord(SinkRecord record, String resourceName) {
125122
if (record.value() == null) {
126123
switch (config.behaviorOnNullValues()) {
127124
case IGNORE:
@@ -166,32 +163,91 @@ public DocWriteRequest<?> convertRecord(SinkRecord record, String resourceName)
166163

167164
// delete
168165
if (record.value() == null) {
169-
return maybeAddExternalVersioning(new DeleteRequest(resourceName).id(id), record);
166+
if (!config.isDataStream() && !config.shouldIgnoreKey(record.topic())) {
167+
final long externalVersion = resolveExternalVersion(record);
168+
return BulkOperation.of(b -> b.delete(d -> d
169+
.index(resourceName).id(id)
170+
.versionType(VersionType.External).version(externalVersion)));
171+
}
172+
return BulkOperation.of(b -> b.delete(d -> d.index(resourceName).id(id)));
170173
}
171174

172175
String payload = getPayload(record);
173176
payload = maybeAddTimestamp(payload, record.timestamp());
177+
final String finalPayload = payload;
174178

175179
// index
176180
switch (config.writeMethod()) {
177181
case UPSERT:
178-
return new UpdateRequest(resourceName, id)
179-
.doc(payload, XContentType.JSON)
180-
.upsert(payload, XContentType.JSON)
181-
.retryOnConflict(Math.min(config.maxInFlightRequests(), 5));
182+
return BulkOperation.of(b -> b.update(u -> u
183+
.index(resourceName)
184+
.id(id)
185+
.retryOnConflict(Math.min(config.maxInFlightRequests(), 5))
186+
.action(a -> a
187+
.doc(JsonData.fromJson(finalPayload))
188+
.upsert(JsonData.fromJson(finalPayload))
189+
)
190+
));
182191
case INSERT:
183-
OpType opType = config.isDataStream() ? OpType.CREATE : OpType.INDEX;
184-
IndexRequest req =
185-
new IndexRequest(resourceName).source(payload, XContentType.JSON).opType(opType);
192+
if (config.isDataStream()) {
193+
if (config.useAutogeneratedIds()) {
194+
return BulkOperation.of(b -> b.create(c -> c
195+
.index(resourceName)
196+
.document(JsonData.fromJson(finalPayload))
197+
));
198+
}
199+
return BulkOperation.of(b -> b.create(c -> c
200+
.index(resourceName).id(id)
201+
.document(JsonData.fromJson(finalPayload))
202+
));
203+
}
186204
if (config.useAutogeneratedIds()) {
187-
return req;
205+
return BulkOperation.of(b -> b.index(i -> i
206+
.index(resourceName)
207+
.document(JsonData.fromJson(finalPayload))
208+
));
188209
}
189-
return maybeAddExternalVersioning(req.id(id), record);
210+
if (!config.shouldIgnoreKey(record.topic())) {
211+
final long externalVersion = resolveExternalVersion(record);
212+
return BulkOperation.of(b -> b.index(i -> i
213+
.index(resourceName).id(id)
214+
.document(JsonData.fromJson(finalPayload))
215+
.versionType(VersionType.External).version(externalVersion)));
216+
}
217+
return BulkOperation.of(b -> b.index(i -> i
218+
.index(resourceName).id(id)
219+
.document(JsonData.fromJson(finalPayload))
220+
));
190221
default:
191222
return null; // shouldn't happen
192223
}
193224
}
194225

226+
/**
227+
* In many cases, we explicitly set the record version using the topic's offset.
228+
* This version will, in turn, be checked by Elasticsearch and will throw a versioning
229+
* error if the request represents an equivalent or older version of the record.
230+
*
231+
* @param record the record to be processed
232+
* @return the version to use for the Elasticsearch request
233+
*/
234+
private long resolveExternalVersion(SinkRecord record) {
235+
if (config.hasExternalVersionHeader()) {
236+
final Header versionHeader = record.headers().lastWithName(config.externalVersionHeader());
237+
final byte[] versionValue = HEADER_CONVERTER.fromConnectHeader(
238+
record.topic(), versionHeader.key(), versionHeader.schema(), versionHeader.value()
239+
);
240+
try {
241+
//fromConnectHeader byte output is UTF_8
242+
return Long.parseLong(new String(versionValue, StandardCharsets.UTF_8));
243+
} catch (NumberFormatException e) {
244+
throw new ConnectException("Error converting to long: "
245+
+ new String(versionValue, StandardCharsets.UTF_8), e);
246+
}
247+
}
248+
return record.kafkaOffset();
249+
}
250+
195251
private String getPayload(SinkRecord record) {
196252
if (record.value() == null) {
197253
return null;
@@ -241,44 +297,6 @@ private String maybeAddTimestamp(String payload, Long timestamp) {
241297
return payload;
242298
}
243299

244-
/**
245-
* In many cases, we explicitly set the record version using the topic's offset.
246-
* This version will, in turn, be checked by Elasticsearch and will throw a versioning
247-
* error if the request represents an equivalent or older version of the record.
248-
*
249-
* @param request the request currently being constructed for `record`
250-
* @param record the record to be processed
251-
* @return the (possibly modified) request which was passed in
252-
*/
253-
private DocWriteRequest<?> maybeAddExternalVersioning(
254-
DocWriteRequest<?> request,
255-
SinkRecord record
256-
) {
257-
if (!config.isDataStream() && !config.shouldIgnoreKey(record.topic())) {
258-
request.versionType(VersionType.EXTERNAL);
259-
if (config.hasExternalVersionHeader()) {
260-
final Header versionHeader = record.headers().lastWithName(config.externalVersionHeader());
261-
final byte[] versionValue = HEADER_CONVERTER.fromConnectHeader(
262-
record.topic(),
263-
versionHeader.key(),
264-
versionHeader.schema(),
265-
versionHeader.value()
266-
);
267-
try {
268-
//fromConnectHeader byte output is UTF_8
269-
request.version(Long.parseLong(new String(versionValue, StandardCharsets.UTF_8)));
270-
} catch (NumberFormatException e) {
271-
throw new ConnectException("Error converting to long: "
272-
+ new String(versionValue, StandardCharsets.UTF_8), e);
273-
}
274-
} else {
275-
request.version(record.kafkaOffset());
276-
}
277-
}
278-
279-
return request;
280-
}
281-
282300
// We need to pre process the Kafka Connect schema before converting to JSON as Elasticsearch
283301
// expects a different JSON format from the current JSON converter provides. Rather than
284302
// completely rewrite a converter for Elasticsearch, we will refactor the JSON converter to

0 commit comments

Comments
 (0)