Skip to content

Commit f50a410

Browse files
authored
Define packages in exporter class loader (#409)
1 parent f6e16c8 commit f50a410

File tree

4 files changed

+84
-14
lines changed

4 files changed

+84
-14
lines changed

agent-tooling/src/main/java/io/opentelemetry/auto/tooling/ExporterClassLoader.java

Lines changed: 80 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,19 @@
2222
import java.net.URL;
2323
import java.net.URLClassLoader;
2424
import java.util.Enumeration;
25+
import java.util.jar.JarFile;
26+
import java.util.jar.Manifest;
27+
import lombok.extern.slf4j.Slf4j;
2528
import net.bytebuddy.jar.asm.ClassReader;
2629
import net.bytebuddy.jar.asm.ClassWriter;
2730
import net.bytebuddy.jar.asm.commons.ClassRemapper;
2831

32+
@Slf4j
2933
public class ExporterClassLoader extends URLClassLoader {
3034
// We need to prefix the names to prevent the gradle shadowJar relocation rules from touching
3135
// them. It's possible to do this by excluding this class from shading, but it may cause issue
3236
// with transitive dependencies down the line.
33-
private final ShadingRemapper remapper =
37+
private static final ShadingRemapper remapper =
3438
new ShadingRemapper(
3539
rule(
3640
"#io.opentelemetry.OpenTelemetry",
@@ -52,8 +56,11 @@ public class ExporterClassLoader extends URLClassLoader {
5256
rule("#java.util.logging.Logger", "#io.opentelemetry.auto.bootstrap.PatchLogger"),
5357
rule("#org.slf4j", "#io.opentelemetry.auto.slf4j"));
5458

55-
public ExporterClassLoader(final URL[] urls, final ClassLoader parent) {
56-
super(urls, parent);
59+
private final Manifest manifest;
60+
61+
public ExporterClassLoader(final URL url, final ClassLoader parent) {
62+
super(new URL[] {url}, parent);
63+
this.manifest = getManifest(url);
5764
}
5865

5966
@Override
@@ -69,16 +76,79 @@ public Enumeration<URL> getResources(final String name) throws IOException {
6976

7077
@Override
7178
protected Class<?> findClass(final String name) throws ClassNotFoundException {
72-
7379
// Use resource loading to get the class as a stream of bytes, then use ASM to transform it.
74-
try (final InputStream in = getResourceAsStream(name.replace('.', '/') + ".class")) {
75-
final ClassWriter cw = new ClassWriter(0);
76-
final ClassReader cr = new ClassReader(in);
77-
cr.accept(new ClassRemapper(cw, remapper), ClassReader.EXPAND_FRAMES);
78-
final byte[] bytes = cw.toByteArray();
80+
InputStream in = getResourceAsStream(name.replace('.', '/') + ".class");
81+
if (in == null) {
82+
throw new ClassNotFoundException(name);
83+
}
84+
try {
85+
final byte[] bytes = remapClassBytes(in);
86+
definePackageIfNeeded(name);
7987
return defineClass(name, bytes, 0, bytes.length);
8088
} catch (final IOException e) {
81-
throw new ClassNotFoundException(name);
89+
throw new ClassNotFoundException(name, e);
90+
} finally {
91+
try {
92+
in.close();
93+
} catch (IOException e) {
94+
log.debug(e.getMessage(), e);
95+
}
96+
}
97+
}
98+
99+
private void definePackageIfNeeded(String className) {
100+
String packageName = getPackageName(className);
101+
if (packageName == null) {
102+
// default package
103+
return;
104+
}
105+
if (isPackageDefined(packageName)) {
106+
// package has already been defined
107+
return;
108+
}
109+
try {
110+
definePackage(packageName);
111+
} catch (IllegalArgumentException e) {
112+
// this exception is thrown when the package has already been defined, which is possible due
113+
// to race condition with the check above
114+
if (!isPackageDefined(packageName)) {
115+
// this shouldn't happen however
116+
log.error(e.getMessage(), e);
117+
}
118+
}
119+
}
120+
121+
private boolean isPackageDefined(String packageName) {
122+
return getPackage(packageName) != null;
123+
}
124+
125+
private void definePackage(String packageName) {
126+
if (manifest == null) {
127+
definePackage(packageName, null, null, null, null, null, null, null);
128+
} else {
129+
definePackage(packageName, manifest, null);
130+
}
131+
}
132+
133+
private static byte[] remapClassBytes(InputStream in) throws IOException {
134+
final ClassWriter cw = new ClassWriter(0);
135+
final ClassReader cr = new ClassReader(in);
136+
cr.accept(new ClassRemapper(cw, remapper), ClassReader.EXPAND_FRAMES);
137+
return cw.toByteArray();
138+
}
139+
140+
private static String getPackageName(String className) {
141+
int index = className.lastIndexOf('.');
142+
return index == -1 ? null : className.substring(0, index);
143+
}
144+
145+
private static Manifest getManifest(URL url) {
146+
try {
147+
JarFile jarFile = new JarFile(url.getFile());
148+
return jarFile.getManifest();
149+
} catch (IOException e) {
150+
log.warn(e.getMessage(), e);
82151
}
152+
return null;
83153
}
84154
}

agent-tooling/src/main/java/io/opentelemetry/auto/tooling/TracerInstaller.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ private static synchronized void installExportersFromJar(final String exporterJa
6464
}
6565
final DefaultExporterConfig config = new DefaultExporterConfig("exporter");
6666
final ExporterClassLoader exporterLoader =
67-
new ExporterClassLoader(new URL[] {url}, TracerInstaller.class.getClassLoader());
67+
new ExporterClassLoader(url, TracerInstaller.class.getClassLoader());
6868

6969
final SpanExporterFactory spanExporterFactory =
7070
getExporterFactory(SpanExporterFactory.class, exporterLoader);

agent-tooling/src/test/groovy/io/opentelemetry/auto/test/ClassLoaderMatcherTest.groovy

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ class ClassLoaderMatcherTest extends AgentSpecification {
3232

3333
def "skips exporter classloader"() {
3434
setup:
35-
final URLClassLoader exporterLoader = new ExporterClassLoader(new URL[0], null)
35+
URL url = new URL("file://")
36+
final URLClassLoader exporterLoader = new ExporterClassLoader(url, null)
3637
expect:
3738
ClassLoaderMatcher.skipClassLoader().matches(exporterLoader)
3839
}

auto-exporters/src/test/groovy/io/opentelemetry/auto/exporteradapters/ExporterAdaptersTest.groovy

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,7 @@ class ExporterAdaptersTest extends Specification {
5353
def file = new File(exporter)
5454
println "Attempting to load ${file.toString()} for ${classname}"
5555
assert file.exists(): "${file.toString()} does not exist"
56-
URL[] urls = [file.toURI().toURL()]
57-
def classLoader = new ExporterClassLoader(urls, this.getClass().getClassLoader())
56+
def classLoader = new ExporterClassLoader(file.toURI().toURL(), this.getClass().getClassLoader())
5857
def serviceLoader = ServiceLoader.load(SpanExporterFactory, classLoader)
5958

6059
when:

0 commit comments

Comments
 (0)