Skip to content

Commit cc9c0f9

Browse files
authored
Close JarFile (#1970)
1 parent b971357 commit cc9c0f9

File tree

2 files changed

+108
-7
lines changed

2 files changed

+108
-7
lines changed

javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/ExporterClassLoader.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import java.io.IOException;
1111
import java.io.InputStream;
12+
import java.net.URISyntaxException;
1213
import java.net.URL;
1314
import java.net.URLClassLoader;
1415
import java.util.Enumeration;
@@ -127,10 +128,9 @@ private static String getPackageName(String className) {
127128
}
128129

129130
private static Manifest getManifest(URL url) {
130-
try {
131-
JarFile jarFile = new JarFile(url.getFile());
131+
try (JarFile jarFile = new JarFile(url.toURI().getPath())) {
132132
return jarFile.getManifest();
133-
} catch (IOException e) {
133+
} catch (IOException | URISyntaxException e) {
134134
log.warn(e.getMessage(), e);
135135
}
136136
return null;

javaagent-tooling/src/test/groovy/io/opentelemetry/javaagent/tooling/ExporterClassLoaderTest.groovy

+105-4
Original file line numberDiff line numberDiff line change
@@ -5,40 +5,91 @@
55

66
package io.opentelemetry.javaagent.tooling
77

8+
import groovy.transform.CompileStatic
89
import io.opentelemetry.javaagent.spi.exporter.MetricExporterFactory
910
import io.opentelemetry.javaagent.spi.exporter.SpanExporterFactory
1011
import io.opentelemetry.sdk.metrics.export.MetricExporter
1112
import io.opentelemetry.sdk.trace.export.SpanExporter
1213
import java.nio.charset.StandardCharsets
14+
import java.util.jar.Attributes
1315
import java.util.jar.JarEntry
16+
import java.util.jar.JarFile
1417
import java.util.jar.JarOutputStream
18+
import java.util.jar.Manifest
1519
import spock.lang.Specification
1620

1721
class ExporterClassLoaderTest extends Specification {
1822

1923
// Verifies https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/542
2024
def "does not look in parent classloader for metric exporters"() {
2125
setup:
22-
def parentClassloader = new URLClassLoader([createJarWithClasses(MetricExporterFactoryParent)] as URL[])
26+
def parentClassloader = new ParentClassLoader([createJarWithClasses(MetricExporterFactoryParent)] as URL[])
2327
def childClassloader = new ExporterClassLoader(createJarWithClasses(MetricExporterFactoryChild), parentClassloader)
2428

2529
when:
2630
ServiceLoader<MetricExporterFactory> serviceLoader = ServiceLoader.load(MetricExporterFactory, childClassloader)
2731

2832
then:
2933
serviceLoader.size() == 1
34+
35+
and:
36+
childClassloader.manifest != null
37+
38+
when:
39+
MetricExporterFactory instance = serviceLoader.iterator().next()
40+
Class clazz = instance.getClass()
41+
42+
then:
43+
clazz.getClassLoader() == childClassloader
3044
}
3145

3246
def "does not look in parent classloader for span exporters"() {
3347
setup:
34-
def parentClassloader = new URLClassLoader([createJarWithClasses(SpanExporterFactoryParent)] as URL[])
48+
def parentClassloader = new ParentClassLoader([createJarWithClasses(SpanExporterFactoryParent)] as URL[])
3549
def childClassloader = new ExporterClassLoader(createJarWithClasses(SpanExporterFactoryChild), parentClassloader)
3650

3751
when:
3852
ServiceLoader<SpanExporterFactory> serviceLoader = ServiceLoader.load(SpanExporterFactory, childClassloader)
3953

4054
then:
4155
serviceLoader.size() == 1
56+
57+
and:
58+
childClassloader.manifest != null
59+
60+
when:
61+
SpanExporterFactory instance = serviceLoader.iterator().next()
62+
Class clazz = instance.getClass()
63+
64+
then:
65+
clazz.getClassLoader() == childClassloader
66+
}
67+
68+
// Verifies that loading of exporter jar succeeds when there is a space in path to exporter jar
69+
def "load jar with space in path"() {
70+
setup:
71+
def parentClassloader = new ParentClassLoader()
72+
// " .jar" is used to make path to jar contain a space
73+
def childClassloader = new ExporterClassLoader(createJarWithClasses(" .jar", MetricExporterFactoryChild), parentClassloader)
74+
75+
when:
76+
ServiceLoader<MetricExporterFactory> serviceLoader = ServiceLoader.load(MetricExporterFactory, childClassloader)
77+
78+
then:
79+
serviceLoader.size() == 1
80+
81+
and:
82+
childClassloader.manifest != null
83+
84+
when:
85+
MetricExporterFactory instance = serviceLoader.iterator().next()
86+
Class clazz = instance.getClass()
87+
88+
then:
89+
clazz.getClassLoader() == childClassloader
90+
91+
and:
92+
clazz.getPackage().getImplementationVersion() == "test-implementation-version"
4293
}
4394

4495
static class MetricExporterFactoryParent implements MetricExporterFactory {
@@ -93,15 +144,35 @@ class ExporterClassLoaderTest extends Specification {
93144
}
94145
}
95146

96-
static URL createJarWithClasses(final Class<?>... classes)
147+
static URL createJarWithClasses(final Class<?>... classes) {
148+
createJarWithClasses(".jar", classes)
149+
}
150+
151+
static URL createJarWithClasses(final String suffix, final Class<?>... classes)
97152
throws IOException {
98-
File tmpJar = File.createTempFile(UUID.randomUUID().toString() + "-", ".jar")
153+
File tmpJar = File.createTempFile(UUID.randomUUID().toString() + "-", suffix)
99154
tmpJar.deleteOnExit()
100155

101156
JarOutputStream target = new JarOutputStream(new FileOutputStream(tmpJar))
102157
for (Class<?> clazz : classes) {
103158
addToJar(clazz, clazz.getInterfaces()[0], target)
104159
}
160+
161+
Manifest manifest = new Manifest()
162+
Attributes attributes = manifest.getMainAttributes()
163+
attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0")
164+
attributes.put(Attributes.Name.SPECIFICATION_TITLE, "test-specification-title")
165+
attributes.put(Attributes.Name.SPECIFICATION_VERSION, "test-specification-version")
166+
attributes.put(Attributes.Name.SPECIFICATION_VENDOR, "test-specification-vendor")
167+
attributes.put(Attributes.Name.IMPLEMENTATION_TITLE, "test-implementation-title")
168+
attributes.put(Attributes.Name.IMPLEMENTATION_VERSION, "test-implementation-version")
169+
attributes.put(Attributes.Name.IMPLEMENTATION_VENDOR, "test-implementation-vendor")
170+
171+
JarEntry manifestEntry = new JarEntry(JarFile.MANIFEST_NAME)
172+
target.putNextEntry(manifestEntry)
173+
manifest.write(target)
174+
target.closeEntry()
175+
105176
target.close()
106177

107178
return tmpJar.toURI().toURL()
@@ -149,4 +220,34 @@ class ExporterClassLoaderTest extends Specification {
149220
private static String getResourceName(final String className) {
150221
return className.replace('.', '/') + ".class"
151222
}
223+
224+
@CompileStatic
225+
private static class ParentClassLoader extends URLClassLoader {
226+
227+
ParentClassLoader() {
228+
super()
229+
}
230+
231+
ParentClassLoader(URL[] urls) {
232+
super(urls)
233+
}
234+
235+
@Override
236+
Package getPackage(String name) {
237+
// ExporterClassLoader uses getPackage to check whether package has already been
238+
// defined. As getPackage also searches packages from parent class loader we return
239+
// null here to ensure that package is defined in ExporterClassLoader.
240+
null
241+
}
242+
243+
@Override
244+
Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
245+
// test classes are available in system class loader filter them so that
246+
// they would be loaded by ExporterClassLoader
247+
if (name.startsWith(ExporterClassLoaderTest.getName())) {
248+
throw new ClassNotFoundException(name)
249+
}
250+
return super.loadClass(name, resolve)
251+
}
252+
}
152253
}

0 commit comments

Comments
 (0)