Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .ci/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,13 @@ RUN ./gradlew

COPY --chown=$BUILDER_UID:$BUILDER_GID LICENSE.txt NOTICE.txt ./



# Prefetch and cache dependencies
COPY --chown=$BUILDER_UID:$BUILDER_GID build.gradle.kts settings.gradle.kts ./
COPY --chown=$BUILDER_UID:$BUILDER_GID buildSrc ./buildSrc/
COPY --chown=$BUILDER_UID:$BUILDER_GID config ./config/
COPY --chown=$BUILDER_UID:$BUILDER_GID rest5-client ./rest5-client/
COPY --chown=$BUILDER_UID:$BUILDER_GID java-client/build.gradle.kts ./java-client/
RUN ./gradlew resolveDependencies

Expand Down
2 changes: 2 additions & 0 deletions java-client/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,8 @@ dependencies {
val jacksonVersion = "2.18.3"
val openTelemetryVersion = "1.32.0"

api(project(":rest5-client"))

// Apache 2.0
// https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-low.html
compileOnly("org.elasticsearch.client", "elasticsearch-rest-client", elasticsearchVersion)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,30 @@
* under the License.
*/

package co.elastic.clients.transport.rest5_client.low_level;

import co.elastic.clients.transport.rest5_client.SafeResponseConsumer;
package co.elastic.clients.transport.rest5_client;

import co.elastic.clients.transport.rest5_client.low_level.BufferedByteConsumer;
import co.elastic.clients.transport.rest5_client.low_level.HttpAsyncResponseConsumerFactory;
import co.elastic.clients.transport.rest5_client.low_level.Request;
import co.elastic.clients.transport.rest5_client.low_level.RequestOptions;
import co.elastic.clients.transport.rest5_client.low_level.Response;
import co.elastic.clients.transport.rest5_client.low_level.Rest5Client;
import com.sun.net.httpserver.HttpServer;
import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.HttpException;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.HttpResponse;
import org.apache.hc.core5.http.io.entity.ByteArrayEntity;
import org.apache.hc.core5.http.message.BasicClassicHttpResponse;
import org.apache.hc.core5.http.protocol.HttpContext;
import org.apache.hc.core5.http.nio.AsyncResponseConsumer;
import org.apache.hc.core5.http.nio.support.AbstractAsyncResponseConsumer;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
Expand All @@ -42,20 +51,36 @@ public class SafeResponseConsumerTest {
static HttpHost ESHost;

// A consumer factory that throws an Error, to simulate the effect of an OOME
static HttpAsyncResponseConsumerFactory FailingConsumerFactory =
() -> new BasicAsyncResponseConsumer(new BufferedByteConsumer(100 * 1024 * 1024)) {
@Override
public void informationResponse(HttpResponse response, HttpContext context) {
super.informationResponse(response, context);
}
static HttpAsyncResponseConsumerFactory FailingConsumerFactory = new FailingAsyncResponseConsumerFactory();

@Override
protected BasicClassicHttpResponse buildResult(HttpResponse response, ByteArrayEntity entity,
ContentType contentType) {
super.buildResult(response, entity, contentType);
throw new Error("Error in buildResult");
}
};
static class FailingAsyncResponseConsumerFactory implements HttpAsyncResponseConsumerFactory {
@Override
public AsyncResponseConsumer<ClassicHttpResponse> createHttpAsyncResponseConsumer() {
return new FailingAsyncResponseConsumer();
}
}

static class FailingAsyncResponseConsumer extends AbstractAsyncResponseConsumer<ClassicHttpResponse,
ByteArrayEntity> {

FailingAsyncResponseConsumer() {
super(new BufferedByteConsumer(100));
}

@Override
public void informationResponse(HttpResponse response, org.apache.hc.core5.http.protocol.HttpContext context)
throws HttpException, IOException {
throw new Error("Error in informationResponse");
}

@Override
protected BasicClassicHttpResponse buildResult(
HttpResponse response, ByteArrayEntity entity,
ContentType contentType
) {
throw new Error("Error in buildResult");
}
}

@BeforeAll
public static void setup() throws Exception {
Expand Down
7 changes: 7 additions & 0 deletions rest5-client/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# rest5-client

The Elasticsearch Java client historically used a "Low Level Rest Client" (LLRC) based on Apache http client version 4 to handle http communications and distribution of requests among the nodes of a cluster.

This directory is a port of this client to Apache http client version 5 that is mostly a drop-in replacement, except for the initialization phase.

It is provided as an independent library as a convenience to users who have an existing code based on LLRC and would like to migrate to the more modern Apache http 5 library.
246 changes: 246 additions & 0 deletions rest5-client/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import com.github.jk1.license.ProjectData
import com.github.jk1.license.render.LicenseDataCollector
import com.github.jk1.license.render.ReportRenderer
import java.io.FileWriter

plugins {
java
`java-library`
`maven-publish`
publishing
checkstyle
signing
id("com.github.jk1.dependency-license-report") version "2.2"
}

checkstyle {
toolVersion = "10.16.0"
}

java {
targetCompatibility = JavaVersion.VERSION_17
sourceCompatibility = JavaVersion.VERSION_17

withJavadocJar()
withSourcesJar()
}

tasks.compileJava {
options.release.set(17)
}

tasks.withType<Test> {
useJUnitPlatform()
}

tasks.withType<Jar> {
doFirst {
if (rootProject.extra.has("gitHashFull")) {
val jar = this as Jar
jar.manifest.attributes["X-Git-Revision"] = rootProject.extra["gitHashFull"]
jar.manifest.attributes["X-Git-Commit-Time"] = rootProject.extra["gitCommitTime"]
} else {
throw GradleException("No git information available")
}
}

manifest {
attributes["Implementation-Title"] = "Elasticsearch Java client"
attributes["Implementation-Vendor"] = "Elastic"
attributes["Implementation-URL"] = "https://github.com/elastic/elasticsearch-java/"
attributes["Build-Date"] = rootProject.extra["buildTime"]
}

metaInf {
from("../LICENSE.txt")
from("../NOTICE.txt")
}
}

tasks.withType<Javadoc> {
val opt = options as StandardJavadocDocletOptions
// Gradle calls javadoc with a list of file and not a path. This prevents doc-files from being copied.
opt.addStringOption("sourcepath", project.projectDir.path + "/src/main/java")
opt.docFilesSubDirs(true)
opt.addBooleanOption("Xdoclint:-missing", true)
}

publishing {
repositories {
maven {
// See https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-gradle-registry
name = "ESJavaGithubPackages"
url = uri("https://maven.pkg.github.com/elastic/elasticsearch-java")
credentials(PasswordCredentials::class)
}

maven {
name = "Build"
url = uri("${rootProject.layout.buildDirectory.get().asFile}/repository")
}
}

publications {
create<MavenPublication>("maven") {
from(components["java"])
pom {
name.set("Elasticsearch Rest5 Client")
artifactId = "elasticsearch-rest5-client"
description.set("Low level client based on http5")
url.set("https://github.com/elastic/elasticsearch-java/")
licenses {
license {
name.set("The Apache Software License, Version 2.0")
url.set("https://www.apache.org/licenses/LICENSE-2.0.txt")
}
}
developers {
developer {
name.set("Elastic")
url.set("https://www.elastic.co")
inceptionYear.set("2020")
}
}
scm {
connection.set("scm:git:https://github.com/elastic/elasticsearch-java.git")
developerConnection.set("scm:git:ssh://[email protected]:elastic/elasticsearch-java.git")
url.set("https://github.com/elastic/elasticsearch-java/")
}
}
}
}
}


signing {
// Only sign if a key has been configured in gradle.properties
isRequired = providers.gradleProperty("signing.keyId").isPresent
sign(publishing.publications["maven"])
}

dependencies {
val jacksonVersion = "2.18.3"

// Apache 2.0
// https://hc.apache.org/httpcomponents-client-ga/
api("org.apache.httpcomponents.client5","httpclient5","5.4.4")

// Apache 2.0
// http://commons.apache.org/logging/
api("commons-logging:commons-logging:1.3.5")

testImplementation("org.apache.commons:commons-lang3:3.14.0")
testImplementation("junit:junit:4.13.2")

// Apache 2.0
// https://github.com/FasterXML/jackson
implementation("com.fasterxml.jackson.core", "jackson-core", jacksonVersion)
implementation("com.fasterxml.jackson.core", "jackson-databind", jacksonVersion)

// // Apache-2.0
// testImplementation("commons-io:commons-io:2.17.0")

// EPL-2.0
// https://junit.org/junit5/
testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.2")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2")

// Apache-2.0
// https://github.com/awaitility/awaitility
testImplementation("org.awaitility", "awaitility", "4.2.0")

// MIT
// https://github.com/mockito/mockito
testImplementation("org.mockito","mockito-core","5.12.0")

// Apache-2.0
// https://github.com/elastic/mocksocket
testImplementation("org.elasticsearch","mocksocket","1.2")

}


licenseReport {
renderers = arrayOf(SpdxReporter(File(rootProject.layout.buildDirectory.get().asFile, "release/dependencies.csv")))
excludeGroups = arrayOf("org.elasticsearch.client")
}

class SpdxReporter(val dest: File) : ReportRenderer {
// License names to their SPDX identifier
val spdxIds = mapOf(
"The Apache License, Version 2.0" to "Apache-2.0",
"Apache License, Version 2.0" to "Apache-2.0",
"The Apache Software License, Version 2.0" to "Apache-2.0",
"Apache-2.0" to "Apache-2.0",
"MIT License" to "MIT",
"BSD Zero Clause License" to "0BSD",
"Eclipse Public License 2.0" to "EPL-2.0",
"Eclipse Public License v. 2.0" to "EPL-2.0",
"Eclipse Public License - v 2.0" to "EPL-2.0",
"GNU General Public License, version 2 with the GNU Classpath Exception" to "GPL-2.0 WITH Classpath-exception-2.0",
"COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0" to "CDDL-1.0"
)

private fun quote(str: String): String {
return if (str.contains(',') || str.contains("\"")) {
"\"" + str.replace("\"", "\"\"") + "\""
} else {
str
}
}

override fun render(data: ProjectData?) {
dest.parentFile.mkdirs()
FileWriter(dest).use { out ->
out.append("name,url,version,revision,license\n")
data?.allDependencies?.forEach { dep ->

val depVersion = dep.version
val depName = dep.group + ":" + dep.name

val info = LicenseDataCollector.multiModuleLicenseInfo(dep)
val depUrl = when(dep.group) {
"org.apache.httpcomponents.client5" -> "https://hc.apache.org/"
"org.apache.httpcomponents.core5" -> "https://hc.apache.org/"
"com.fasterxml.jackson" -> "https://github.com/FasterXML/jackson"
else -> if (info.moduleUrls.isEmpty()) {
throw RuntimeException("No URL found for module '$depName'")
} else {
info.moduleUrls.first()
}
}

val licenseIds = info.licenses.mapNotNull { license ->
license.name?.let {
checkNotNull(spdxIds[it]) { "No SPDX identifier for $license" }
}
}.toSet()

// Combine multiple licenses.
// See https://spdx.github.io/spdx-spec/appendix-IV-SPDX-license-expressions/#composite-license-expressions
val licenseId = licenseIds.joinToString(" OR ")

out.append("${quote(depName)},${quote(depUrl)},${quote(depVersion)},,${quote(licenseId)}\n")
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@

import static co.elastic.clients.transport.rest5_client.low_level.Constants.DEFAULT_BUFFER_INITIAL_CAPACITY;

class BufferedByteConsumer extends AbstractBinAsyncEntityConsumer<ByteArrayEntity> {
public class BufferedByteConsumer extends AbstractBinAsyncEntityConsumer<ByteArrayEntity> {

private volatile ByteArrayBuffer buffer;
private final int limit;
private ContentType contentType;

BufferedByteConsumer(int bufferLimit) {
public BufferedByteConsumer(int bufferLimit) {
super();
if (bufferLimit <= 0) {
throw new IllegalArgumentException("Buffer limit must be greater than 0");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
package co.elastic.clients.transport.rest5_client.low_level;

import org.apache.hc.core5.http.HttpHost;
import org.elasticsearch.client.RestClient;

import java.util.HashSet;
import java.util.List;
Expand All @@ -31,7 +30,7 @@
import static org.junit.Assert.assertThat;

/**
* {@link RestClient.FailureListener} impl that allows to track when it gets called for which host.
* {@link Rest5Client.FailureListener} impl that allows to track when it gets called for which host.
*/
class HostsTrackingFailureListener extends Rest5Client.FailureListener {
private volatile Set<HttpHost> httpHosts = new HashSet<>();
Expand Down
Loading