Skip to content

Commit

Permalink
Fallback to current class if loading a Jsonp provider using ServiceLo…
Browse files Browse the repository at this point in the history
…ader fails (#173) (#174)

Co-authored-by: Sylvain Wallez <sylvain@elastic.co>
  • Loading branch information
github-actions[bot] and swallez authored Mar 1, 2022
1 parent 2983fad commit 47348c0
Show file tree
Hide file tree
Showing 11 changed files with 167 additions and 10 deletions.
3 changes: 3 additions & 0 deletions config/forbidden-apis.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@defaultMessage JsonProvider.provider() should not be used directly. Use JsonpUtils#provider() instead.
jakarta.json.spi.JsonProvider#provider()

15 changes: 14 additions & 1 deletion java-client/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ plugins {
checkstyle
`maven-publish`
id("com.github.jk1.dependency-license-report") version "1.17"
id("de.thetaphi.forbiddenapis") version "3.2"
}

java {
Expand All @@ -37,6 +38,15 @@ java {
withSourcesJar()
}

forbiddenApis {
signaturesFiles = files(File(rootProject.projectDir, "config/forbidden-apis.txt"))
suppressAnnotations = setOf("co.elastic.clients.util.AllowForbiddenApis")
}

tasks.forbiddenApisMain {
bundledSignatures = setOf("jdk-system-out")
}

tasks.getByName<ProcessResources>("processResources") {
// Only process main source-set resources (test files are large)
expand(
Expand Down Expand Up @@ -193,7 +203,10 @@ dependencies {

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

// EPL-1.0
// https://junit.org/junit4/
Expand Down
35 changes: 35 additions & 0 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,10 +19,13 @@

package co.elastic.clients.json;

import co.elastic.clients.util.AllowForbiddenApis;
import co.elastic.clients.util.ObjectBuilder;
import jakarta.json.JsonException;
import jakarta.json.JsonObject;
import jakarta.json.JsonString;
import jakarta.json.JsonValue;
import jakarta.json.spi.JsonProvider;
import jakarta.json.stream.JsonGenerator;
import jakarta.json.stream.JsonParser;
import jakarta.json.stream.JsonParser.Event;
Expand All @@ -32,10 +35,42 @@
import java.io.StringReader;
import java.util.AbstractMap;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.stream.Collectors;

public class JsonpUtils {

/**
* 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.
*/
@AllowForbiddenApis("Implementation of the JsonProvider lookup")
public static JsonProvider provider() {
RuntimeException exception;
try {
return JsonProvider.provider();
} catch(RuntimeException re) {
exception = re;
}

// Not found from the thread's context classloader. Try from our own classloader which should be a descendant of an app-server
// classloader if any, and if it still fails try from the SPI class which hopefully will be close to the implementation.

try {
return ServiceLoader.load(JsonProvider.class, JsonpUtils.class.getClassLoader()).iterator().next();
} catch(Exception e) {
// ignore
}

try {
return ServiceLoader.load(JsonProvider.class, JsonProvider.class.getClassLoader()).iterator().next();
} catch(Exception e) {
// ignore
}

throw new JsonException("Unable to get a JsonProvider. Check your classpath or thread context classloader.", exception);
}

/**
* Advances the parser to the next event and checks that this even is the expected one.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

package co.elastic.clients.json.jackson;

import co.elastic.clients.json.JsonpUtils;
import com.fasterxml.jackson.core.JsonFactory;
import jakarta.json.JsonArray;
import jakarta.json.JsonArrayBuilder;
Expand Down Expand Up @@ -133,15 +134,15 @@ public JsonParser createParser(InputStream in, Charset charset) {
*/
@Override
public JsonParser createParser(JsonObject obj) {
return JsonProvider.provider().createParserFactory(null).createParser(obj);
return JsonpUtils.provider().createParserFactory(null).createParser(obj);
}

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

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

package co.elastic.clients.json.jackson;

import co.elastic.clients.json.JsonpUtils;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import jakarta.json.JsonArray;
Expand All @@ -36,7 +37,7 @@
* object (e.g. START_OBJECT, VALUE_NUMBER, etc).
*/
class JsonValueParser {
private final JsonProvider provider = JsonProvider.provider();
private final JsonProvider provider = JsonpUtils.provider();

public JsonObject parseObject(JsonParser parser) throws IOException {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import co.elastic.clients.json.JsonpMapper;
import co.elastic.clients.json.JsonpMapperBase;
import co.elastic.clients.json.JsonpSerializable;
import co.elastic.clients.json.JsonpUtils;
import jakarta.json.bind.Jsonb;
import jakarta.json.bind.spi.JsonbProvider;
import jakarta.json.spi.JsonProvider;
Expand All @@ -50,7 +51,7 @@ public JsonbJsonpMapper(JsonProvider jsonProvider, JsonbProvider jsonbProvider)
}

public JsonbJsonpMapper() {
this(JsonProvider.provider(), JsonbProvider.provider());
this(JsonpUtils.provider(), JsonbProvider.provider());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* 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.
*/

package co.elastic.clients.util;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Annotation to allow usage of forbidden APIs inside a whole class, a method, or a field.
*/
@Retention(RetentionPolicy.CLASS)
@Target({ ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE })
public @interface AllowForbiddenApis {
/**
* The reason for allowing forbidden APIs
*/
String value();
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
package co.elastic.clients.elasticsearch.experiments;

import co.elastic.clients.elasticsearch.experiments.api.FooRequest;
import co.elastic.clients.json.JsonpUtils;
import co.elastic.clients.json.jsonb.JsonbJsonpMapper;
import jakarta.json.spi.JsonProvider;
import jakarta.json.stream.JsonGenerator;
Expand Down Expand Up @@ -50,7 +51,7 @@ public void testFoo() throws Exception {
)
.build();

JsonProvider provider = JsonProvider.provider();
JsonProvider provider = JsonpUtils.provider();
JsonGenerator generator = provider.createGenerator(baos);
foo.serialize(generator, new JsonbJsonpMapper());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
package co.elastic.clients.elasticsearch.experiments.containers;

import co.elastic.clients.elasticsearch.model.ModelTestCase;
import co.elastic.clients.json.JsonpUtils;
import co.elastic.clients.json.jsonb.JsonbJsonpMapper;
import jakarta.json.spi.JsonProvider;
import jakarta.json.stream.JsonGenerator;
Expand Down Expand Up @@ -55,7 +56,7 @@ public void testDeserialization() {
public void testSerialization() {

ByteArrayOutputStream baos = new ByteArrayOutputStream();
JsonProvider provider = JsonProvider.provider();
JsonProvider provider = JsonpUtils.provider();
JsonGenerator generator = provider.createGenerator(baos);

su.serialize(generator, new JsonbJsonpMapper());
Expand All @@ -71,7 +72,7 @@ public void testSerialization() {
public void testMissingVariantDeserialization() {
String json = "{}";

JsonProvider provider = JsonProvider.provider();
JsonProvider provider = JsonpUtils.provider();
JsonParser parser = provider.createParser(new StringReader(json));

JsonParsingException e = assertThrows(JsonParsingException.class, () -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import co.elastic.clients.elasticsearch.experiments.inheritance.child.ChildClass;
import co.elastic.clients.elasticsearch.experiments.inheritance.final_.FinalClass;
import co.elastic.clients.json.JsonpUtils;
import co.elastic.clients.json.jsonb.JsonbJsonpMapper;
import jakarta.json.spi.JsonProvider;
import jakarta.json.stream.JsonGenerator;
Expand All @@ -37,7 +38,7 @@ public class InheritanceTest extends Assert {
public void testSerialization() {

ByteArrayOutputStream baos = new ByteArrayOutputStream();
JsonProvider provider = JsonProvider.provider();
JsonProvider provider = JsonpUtils.provider();

FinalClass fc = new FinalClass.Builder()
// Start fields from the top of the hierarchy to test setter return values
Expand Down Expand Up @@ -73,7 +74,7 @@ public void testSerialization() {

@Test
public void testDeserialization() {
JsonProvider provider = JsonProvider.provider();
JsonProvider provider = JsonpUtils.provider();

JsonParser parser = provider.createParser(new StringReader(
"{\"baseField\":\"baseValue\",\"childField\":\"childValue\",\"finalField\":\"finalValue\"}"));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* 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.
*/

package co.elastic.clients.elasticsearch.json;

import co.elastic.clients.json.JsonpUtils;
import co.elastic.clients.util.AllowForbiddenApis;
import jakarta.json.JsonException;
import jakarta.json.spi.JsonProvider;
import org.junit.Assert;
import org.junit.Test;

import java.net.URL;
import java.util.Collections;
import java.util.Enumeration;

public class JsonpUtilsTest extends Assert {

@Test
@AllowForbiddenApis("Testing JsonpUtil.provider()")
public void testProviderLoading() {
// See https://github.com/elastic/elasticsearch-java/issues/163

// Create an empty non-delegating classloader and set it as the context classloader. It simulates a
// plugin system that doesn't set the context classloader to the plugins classloader.
ClassLoader emptyLoader = new ClassLoader() {
@Override
public Enumeration<URL> getResources(String name) {
return Collections.emptyEnumeration();
}
};

ClassLoader savedLoader = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(emptyLoader);

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

assertNotNull(JsonpUtils.provider());

} finally {
Thread.currentThread().setContextClassLoader(savedLoader);
}
}
}

0 comments on commit 47348c0

Please sign in to comment.