Skip to content

Use Jackson as the default JSON implementations, update libraries #976

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Apr 2, 2025
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
7 changes: 0 additions & 7 deletions docs/reference/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ This page guides you through the installation process of the Java client, shows
```groovy
dependencies {
implementation 'co.elastic.clients:elasticsearch-java:9.0.0-beta1'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.0'
}
```

Expand All @@ -41,12 +40,6 @@ In the `pom.xml` of your project, add the following repository definition and de
<version>9.0.0-beta1</version>
</dependency>

<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.0</version>
</dependency>

</dependencies>
</project>
```
Expand Down
7 changes: 0 additions & 7 deletions docs/reference/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ Releases are hosted on [Maven Central](https://search.maven.org/search?q=g:co.el
```groovy
dependencies {
implementation 'co.elastic.clients:elasticsearch-java:9.0.0-beta1'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.0'
}
```

Expand All @@ -37,12 +36,6 @@ In the `pom.xml` of your project, add the following repository definition and de
<version>9.0.0-beta1</version>
</dependency>

<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.0</version>
</dependency>

</dependencies>
</project>
```
Expand Down
54 changes: 25 additions & 29 deletions java-client/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,9 @@ publishing {
}

withXml {
// Note: org.elasticsearch.client is now an optional dependency, so the below is no more useful.
// It's kept in case it ever comes back as a required dependency.

// Set the version of dependencies of the org.elasticsearch.client group to the one that we are building.
// Since the unified release process releases everything at once, this ensures all published artifacts depend
// on the exact same version. This assumes of course that the binary API and the behavior of these dependencies
Expand All @@ -169,21 +172,14 @@ publishing {
.compile("/project/dependencies/dependency[groupId/text() = 'org.elasticsearch.client']")
val versionSelector = xPathFactory.newXPath().compile("version")

var foundVersion = false;

val deps = depSelector.evaluate(asElement().ownerDocument, javax.xml.xpath.XPathConstants.NODESET)
as org.w3c.dom.NodeList

for (i in 0 until deps.length) {
val dep = deps.item(i)
val version = versionSelector.evaluate(dep, javax.xml.xpath.XPathConstants.NODE) as org.w3c.dom.Element
foundVersion = true;
version.textContent = project.version.toString()
}

if (!foundVersion) {
throw GradleException("Could not find a 'org.elasticsearch.client' to update dependency version in the POM.")
}
}
}
}
Expand All @@ -198,15 +194,16 @@ signing {
}

dependencies {
// Compile and test with the last 7.x version to make sure transition scenarios where
// the Java API client coexists with a 7.x HLRC work fine
// Compile and test with the last 8.x version to make sure transition scenarios where
// the Java API client coexists with a 8.x HLRC work fine
val elasticsearchVersion = "8.17.0"
val jacksonVersion = "2.17.0"
val jacksonVersion = "2.18.3"
val openTelemetryVersion = "1.29.0"

// Apache 2.0
// https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-low.html
api("org.elasticsearch.client", "elasticsearch-rest-client", elasticsearchVersion)
compileOnly("org.elasticsearch.client", "elasticsearch-rest-client", elasticsearchVersion)
testImplementation("org.elasticsearch.client", "elasticsearch-rest-client", elasticsearchVersion)

api("org.apache.httpcomponents.client5","httpclient5","5.4")

Expand All @@ -216,38 +213,34 @@ dependencies {

// EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
// https://github.com/eclipse-ee4j/jsonp
api("jakarta.json:jakarta.json-api:2.0.1")
api("jakarta.json:jakarta.json-api:2.1.3")

// Needed even if using Jackson to have an implementation of the Jsonp object model
// EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
// https://github.com/eclipse-ee4j/parsson
api("org.eclipse.parsson:parsson:1.0.5")
api("org.eclipse.parsson:parsson:1.1.7")

// OpenTelemetry API for native instrumentation of the client.
// Apache 2.0
// https://github.com/open-telemetry/opentelemetry-java
implementation("io.opentelemetry", "opentelemetry-api", openTelemetryVersion)
// Use it once it's stable (see Instrumentation.java). Limited to tests for now.
testImplementation("io.opentelemetry", "opentelemetry-semconv", "$openTelemetryVersion-alpha")
testImplementation("io.opentelemetry", "opentelemetry-sdk", openTelemetryVersion)

// EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
// https://github.com/eclipse-ee4j/jsonb-api
compileOnly("jakarta.json.bind", "jakarta.json.bind-api", "2.0.0")
testImplementation("jakarta.json.bind", "jakarta.json.bind-api", "2.0.0")
compileOnly("jakarta.json.bind", "jakarta.json.bind-api", "3.0.1")
testImplementation("jakarta.json.bind", "jakarta.json.bind-api", "3.0.1")

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

// EPL-2.0 OR BSD-3-Clause
// https://eclipse-ee4j.github.io/yasson/
testImplementation("org.eclipse", "yasson", "2.0.4") {
// Exclude Glassfish as we use Parsson (basically Glassfish renamed in the Jakarta namespace).
exclude(group = "org.glassfish", module = "jakarta.json")
}
testImplementation("org.eclipse", "yasson", "3.0.4")

// Apache-2.0
testImplementation("commons-io:commons-io:2.17.0")
Expand All @@ -268,8 +261,6 @@ dependencies {
// updating transitive dependency from testcontainers
testImplementation("org.apache.commons","commons-compress","1.26.1")

testImplementation("io.opentelemetry", "opentelemetry-sdk", openTelemetryVersion)

// Apache-2.0
// https://github.com/awaitility/awaitility
testImplementation("org.awaitility", "awaitility", "4.2.0")
Expand Down Expand Up @@ -324,10 +315,15 @@ class SpdxReporter(val dest: File) : ReportRenderer {
val depName = dep.group + ":" + dep.name

val info = LicenseDataCollector.multiModuleLicenseInfo(dep)
val depUrl = if (depName.startsWith("org.apache.httpcomponents")) {
"https://hc.apache.org/"
} else {
info.moduleUrls.first()
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 ->
Expand Down
38 changes: 36 additions & 2 deletions java-client/src/main/java/co/elastic/clients/json/JsonpUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

package co.elastic.clients.json;

import co.elastic.clients.json.jackson.JacksonJsonProvider;
import co.elastic.clients.util.AllowForbiddenApis;
import jakarta.json.JsonException;
import jakarta.json.JsonObject;
Expand All @@ -43,23 +44,56 @@
public class JsonpUtils {

private static JsonProvider systemJsonProvider = null;
private static JsonProvider defaultJsonProvider = null;

/**
* Get a <code>JsonProvider</code> instance. This method first calls the standard `JsonProvider.provider()` that is based on
* the current thread's context classloader, and in case of failure tries to find a provider in other classloaders. The
* value is cached for subsequent calls.
*/
public static JsonProvider provider() {
JsonProvider result = systemJsonProvider;
JsonProvider result = defaultJsonProvider;
if (result == null) {
result = findProvider();
defaultJsonProvider = result;
}
return result;
}

/**
* Sets the <code>JsonProvider</code> that will be returned by {@link JsonProvider}.
*/
public static void setProvider(JsonProvider provider) {
defaultJsonProvider = provider;
}

static JsonProvider findProvider() {
try {
// Default to Jackson
Class.forName("com.fasterxml.jackson.databind.ObjectMapper");
return new JacksonJsonProvider();
} catch (ClassNotFoundException e) {
return findSystemProvider();
}
}

/**
* Get the system's <code>JsonProvider</code> instance return by {@code ServiceLoader}. First calls the standard
* `JsonProvider.provider()` that is based on the current thread's context classloader, and in case of failure tries to
* find a provider in other classloaders. The value is cached for subsequent calls.
*/
public static JsonProvider systemProvider() {
JsonProvider result = systemJsonProvider;
if (result == null) {
result = findSystemProvider();
systemJsonProvider = result;
}
return result;
}

@AllowForbiddenApis("Implementation of the JsonProvider lookup")
static JsonProvider findProvider() {
static JsonProvider findSystemProvider() {

RuntimeException exception;
try {
return JsonProvider.provider();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ public boolean ignoreUnknownFields() {

@Override
public JsonProvider jsonProvider() {
return JsonpUtils.provider();
return JsonpUtils.systemProvider();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,15 +133,15 @@ public JsonParser createParser(InputStream in, Charset charset) {
*/
@Override
public JsonParser createParser(JsonObject obj) {
return JsonpUtils.provider().createParserFactory(null).createParser(obj);
return JsonpUtils.systemProvider().createParserFactory(null).createParser(obj);
}

/**
* Not implemented.
*/
@Override
public JsonParser createParser(JsonArray array) {
return JsonpUtils.provider().createParserFactory(null).createParser(array);
return JsonpUtils.systemProvider().createParserFactory(null).createParser(array);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,11 @@
* object (e.g. START_OBJECT, VALUE_NUMBER, etc).
*/
class JsonValueParser {
private final JsonProvider provider = JsonpUtils.provider();
private final JsonProvider systemProvider = JsonpUtils.systemProvider();

public JsonObject parseObject(JsonParser parser) throws IOException {

JsonObjectBuilder ob = provider.createObjectBuilder();
JsonObjectBuilder ob = systemProvider.createObjectBuilder();

JsonToken token;
while((token = parser.nextToken()) != JsonToken.END_OBJECT) {
Expand All @@ -59,7 +59,7 @@ public JsonObject parseObject(JsonParser parser) throws IOException {
}

public JsonArray parseArray(JsonParser parser) throws IOException {
JsonArrayBuilder ab = provider.createArrayBuilder();
JsonArrayBuilder ab = systemProvider.createArrayBuilder();

while(parser.nextToken() != JsonToken.END_ARRAY) {
ab.add(parseValue(parser));
Expand All @@ -86,23 +86,23 @@ public JsonValue parseValue(JsonParser parser) throws IOException {
return JsonValue.NULL;

case VALUE_STRING:
return provider.createValue(parser.getText());
return systemProvider.createValue(parser.getText());

case VALUE_NUMBER_FLOAT:
case VALUE_NUMBER_INT:
switch(parser.getNumberType()) {
case INT:
return provider.createValue(parser.getIntValue());
return systemProvider.createValue(parser.getIntValue());
case LONG:
return provider.createValue(parser.getLongValue());
return systemProvider.createValue(parser.getLongValue());
case FLOAT:
case DOUBLE:
// Use double also for floats, as JSON-P has no support for float
return new DoubleNumber(parser.getDoubleValue());
case BIG_DECIMAL:
return provider.createValue(parser.getDecimalValue());
return systemProvider.createValue(parser.getDecimalValue());
case BIG_INTEGER:
return provider.createValue(parser.getBigIntegerValue());
return systemProvider.createValue(parser.getBigIntegerValue());
}

default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ public JsonbJsonpMapper(JsonProvider jsonProvider, JsonbProvider jsonbProvider)
}

public JsonbJsonpMapper() {
this(JsonpUtils.provider(), JsonbProvider.provider());
// Use a native JSON-P/JSON-B implementations.
this(JsonpUtils.systemProvider(), JsonbProvider.provider());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@
package co.elastic.clients.json;

import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.json.jackson.JacksonJsonProvider;
import co.elastic.clients.testkit.ModelTestCase;
import co.elastic.clients.elasticsearch.security.IndexPrivilege;
import co.elastic.clients.elasticsearch.security.IndicesPrivileges;
import co.elastic.clients.elasticsearch.security.RoleTemplateScript;
import co.elastic.clients.elasticsearch.security.UserIndicesPrivileges;
import co.elastic.clients.util.AllowForbiddenApis;
import jakarta.json.JsonException;
import jakarta.json.spi.JsonProvider;
import jakarta.json.stream.JsonGenerator;
import jakarta.json.stream.JsonParser;
Expand Down Expand Up @@ -58,13 +58,7 @@ public Enumeration<URL> getResources(String name) {
ClassLoader savedLoader = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(emptyLoader);

assertThrows(JsonException.class, () -> {
assertNotNull(JsonProvider.provider());
});

assertNotNull(JsonpUtils.provider());

} finally {
Thread.currentThread().setContextClassLoader(savedLoader);
}
Expand All @@ -91,6 +85,15 @@ public void testObjectToString() {
assertEquals("Hit: {\"_index\":\"idx\",\"_id\":\"id1\",\"_source\":\"Some user data\"}", hit.toString());
}

@Test
public void testDefaultProvider() {
// Provider defaults to Jackson
assertTrue(JsonpUtils.provider() instanceof JacksonJsonProvider);

// System provider uses service lookup
assertFalse(JsonpUtils.systemProvider() instanceof JacksonJsonProvider);
}

private static class SomeUserData {
@Override
public String toString() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ protected enum JsonImpl { Jsonb, Jackson, Simple };
protected final JsonpMapper mapper;

private static JsonImpl chooseJsonImpl(EnumSet<JsonImpl> jsonImplCandidates, int rand) {
// Converting an EnumSet to an array always uses the same order.
// Converting an EnumSet an array always uses the same order.
return jsonImplCandidates.toArray(new JsonImpl[jsonImplCandidates.size()])[rand % jsonImplCandidates.size()];
}

Expand Down
Loading