When loading exporter factories via SPI from an external jar, look only inside that jar, and not any parent classloader. (#569)

This commit is contained in:
Nikita Salnikov-Tarnovski 2020-06-23 22:35:12 +03:00 committed by GitHub
parent 918d3913e3
commit d08a1a9c5c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 147 additions and 1 deletions

View File

@ -68,7 +68,9 @@ public class ExporterClassLoader extends URLClassLoader {
// A small hack to prevent other exporters from being loaded by this classloader if they
// should happen to appear on the classpath.
if (name.equals(
"META-INF/services/io.opentelemetry.sdk.contrib.auto.config.SpanExporterFactory")) {
"META-INF/services/io.opentelemetry.sdk.contrib.auto.config.SpanExporterFactory")
|| name.equals(
"META-INF/services/io.opentelemetry.sdk.contrib.auto.config.MetricExporterFactory")) {
return findResources(name);
}
return super.getResources(name);

View File

@ -0,0 +1,144 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed 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 io.opentelemetry.auto.tooling
import io.opentelemetry.sdk.contrib.auto.config.Config
import io.opentelemetry.sdk.contrib.auto.config.MetricExporterFactory
import io.opentelemetry.sdk.contrib.auto.config.SpanExporterFactory
import io.opentelemetry.sdk.metrics.export.MetricExporter
import io.opentelemetry.sdk.trace.export.SpanExporter
import java.nio.charset.StandardCharsets
import java.util.jar.JarEntry
import java.util.jar.JarOutputStream
import spock.lang.Specification
class ExporterClassLoaderTest extends Specification {
// Verifies https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/542
def "does not look in parent classloader for metric exporters"() {
setup:
def parentClassloader = new URLClassLoader([createJarWithClasses(MetricExporterFactoryParent)] as URL[])
def childClassloader = new ExporterClassLoader(createJarWithClasses(MetricExporterFactoryChild), parentClassloader)
when:
ServiceLoader<MetricExporterFactory> serviceLoader = ServiceLoader.load(MetricExporterFactory, childClassloader)
then:
serviceLoader.size() == 1
}
def "does not look in parent classloader for span exporters"() {
setup:
def parentClassloader = new URLClassLoader([createJarWithClasses(SpanExporterFactoryParent)] as URL[])
def childClassloader = new ExporterClassLoader(createJarWithClasses(SpanExporterFactoryChild), parentClassloader)
when:
ServiceLoader<SpanExporterFactory> serviceLoader = ServiceLoader.load(SpanExporterFactory, childClassloader)
then:
serviceLoader.size() == 1
}
static class MetricExporterFactoryParent implements MetricExporterFactory {
@Override
MetricExporter fromConfig(Config config) {
return null
}
}
static class MetricExporterFactoryChild implements MetricExporterFactory {
@Override
MetricExporter fromConfig(Config config) {
return null
}
}
static class SpanExporterFactoryParent implements SpanExporterFactory {
@Override
SpanExporter fromConfig(Config config) {
return null
}
}
static class SpanExporterFactoryChild implements SpanExporterFactory {
@Override
SpanExporter fromConfig(Config config) {
return null
}
}
static URL createJarWithClasses(final Class<?>... classes)
throws IOException {
final File tmpJar = File.createTempFile(UUID.randomUUID().toString() + "-", ".jar")
tmpJar.deleteOnExit()
final JarOutputStream target = new JarOutputStream(new FileOutputStream(tmpJar))
for (final Class<?> clazz : classes) {
addToJar(clazz, clazz.getInterfaces()[0], target)
}
target.close()
return tmpJar.toURI().toURL()
}
//This is mostly copy-pasted from IntegrationTestUtils, but we need to save service files as well
private static void addToJar(final Class<?> clazz, final Class<?> serviceInterface, final JarOutputStream jarOutputStream)
throws IOException {
String resourceName = getResourceName(clazz.getName())
ClassLoader loader = clazz.getClassLoader()
if (null == loader) {
// bootstrap resources can be fetched through the system loader
loader = ClassLoader.getSystemClassLoader()
}
InputStream inputStream = null
try {
final JarEntry entry = new JarEntry(resourceName)
jarOutputStream.putNextEntry(entry)
inputStream = loader.getResourceAsStream(resourceName)
final byte[] buffer = new byte[1024]
while (true) {
final int count = inputStream.read(buffer)
if (count == -1) {
break
}
jarOutputStream.write(buffer, 0, count)
}
jarOutputStream.closeEntry()
final JarEntry serviceEntry = new JarEntry("META-INF/services/" + serviceInterface.getName())
jarOutputStream.putNextEntry(serviceEntry)
jarOutputStream.write(clazz.getName().getBytes(StandardCharsets.UTF_8))
jarOutputStream.closeEntry()
} finally {
if (inputStream != null) {
inputStream.close()
}
}
}
/** com.foo.Bar -> com/foo/Bar.class */
private static String getResourceName(final String className) {
return className.replace('.', '/') + ".class"
}
}