Skip to content

Commit e7571fd

Browse files
authored
Fix dependency collection for new Spring Boot nested jars (#7931)
1 parent 8dcc168 commit e7571fd

File tree

4 files changed

+106
-75
lines changed

4 files changed

+106
-75
lines changed

telemetry/src/main/java/datadog/telemetry/dependency/DependencyResolver.java

Lines changed: 51 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,21 +24,19 @@ public static List<Dependency> resolve(URI uri) {
2424

2525
static List<Dependency> internalResolve(final URI uri) throws IOException {
2626
final String scheme = uri.getScheme();
27-
JarReader.Extracted metadata;
28-
String path;
29-
if ("file".equals(scheme)) {
30-
File f;
31-
if (uri.isOpaque()) {
32-
f = new File(uri.getSchemeSpecificPart());
33-
} else {
34-
f = new File(uri);
35-
}
36-
path = f.getAbsolutePath();
37-
metadata = JarReader.readJarFile(path);
38-
} else if ("jar".equals(scheme) && uri.getSchemeSpecificPart().startsWith("file:")) {
39-
path = uri.getSchemeSpecificPart().substring("file:".length());
40-
metadata = JarReader.readNestedJarFile(path);
41-
} else {
27+
JarReader.Extracted metadata = null;
28+
switch (scheme) {
29+
case "file":
30+
final File f = uri.isOpaque() ? new File(uri.getSchemeSpecificPart()) : new File(uri);
31+
final String path = f.getAbsolutePath();
32+
metadata = JarReader.readJarFile(path);
33+
break;
34+
case "jar":
35+
metadata = resolveNestedJar(uri);
36+
break;
37+
default:
38+
}
39+
if (metadata == null) {
4240
log.debug("unsupported dependency type: {}", uri);
4341
return Collections.emptyList();
4442
}
@@ -56,4 +54,42 @@ static List<Dependency> internalResolve(final URI uri) throws IOException {
5654
Dependency.guessFallbackNoPom(metadata.manifest, metadata.jarName, is));
5755
}
5856
}
57+
58+
private static JarReader.Extracted resolveNestedJar(final URI uri) throws IOException {
59+
String path = uri.getSchemeSpecificPart();
60+
61+
// Strip optional trailing '!' or '!/'.
62+
if (path.endsWith("!")) {
63+
path = path.substring(0, path.length() - 1);
64+
} else if (path.endsWith("!/")) {
65+
path = path.substring(0, path.length() - 2);
66+
}
67+
68+
if (path.startsWith("file:")) {
69+
// Old style nested dependencies, as seen in Spring Boot 2 and others.
70+
// These look like jar:file:/path/to.jar!/path/to/nested.jar!/
71+
path = path.substring("file:".length());
72+
final int sepIdx = path.indexOf("!/");
73+
if (sepIdx == -1) {
74+
throw new IllegalArgumentException("Invalid nested jar path: " + path);
75+
}
76+
final String outerPath = path.substring(0, sepIdx);
77+
final String innerPath = path.substring(sepIdx + 2);
78+
return JarReader.readNestedJarFile(outerPath, innerPath);
79+
} else if (path.startsWith("nested:")) {
80+
// New style nested dependencies, for Spring 3.2+.
81+
// These look like jar:nested:/path/to.jar/!path/to/nested.jar!/ (yes, /!, not !/).
82+
// See https://docs.spring.io/spring-boot/specification/executable-jar/jarfile-class.html
83+
path = path.substring("nested:".length());
84+
final int sepIdx = path.indexOf("/!");
85+
if (sepIdx == -1) {
86+
throw new IllegalArgumentException("Invalid nested jar path: " + path);
87+
}
88+
final String outerPath = path.substring(0, sepIdx);
89+
final String innerPath = path.substring(sepIdx + 2);
90+
return JarReader.readNestedJarFile(outerPath, innerPath);
91+
} else {
92+
return null;
93+
}
94+
}
5995
}

telemetry/src/main/java/datadog/telemetry/dependency/JarReader.java

Lines changed: 6 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
package datadog.telemetry.dependency;
22

33
import java.io.File;
4-
import java.io.FileInputStream;
54
import java.io.IOException;
65
import java.io.InputStream;
6+
import java.nio.file.Files;
77
import java.nio.file.NoSuchFileException;
8+
import java.nio.file.Paths;
89
import java.util.Enumeration;
910
import java.util.HashMap;
1011
import java.util.Map;
@@ -67,21 +68,16 @@ public static Extracted readJarFile(String jarPath) throws IOException {
6768
pomProperties,
6869
attributes,
6970
false,
70-
() -> new FileInputStream(jarPath));
71+
() -> Files.newInputStream(Paths.get(jarPath)));
7172
}
7273
}
7374

74-
public static Extracted readNestedJarFile(final String jarPath) throws IOException {
75-
final int sepIdx = jarPath.indexOf("!/");
76-
if (sepIdx == -1) {
77-
throw new IllegalArgumentException("Invalid nested jar path: " + jarPath);
78-
}
79-
final String outerJarPath = jarPath.substring(0, sepIdx);
80-
final String innerJarPath = getInnerJarPath(jarPath);
75+
public static Extracted readNestedJarFile(final String outerJarPath, final String innerJarPath)
76+
throws IOException {
8177
try (final JarFile outerJar = new JarFile(outerJarPath, false /* no verify */)) {
8278
final ZipEntry entry = outerJar.getEntry(innerJarPath);
8379
if (entry == null) {
84-
throw new NoSuchFileException("Nested jar not found: " + jarPath);
80+
throw new NoSuchFileException("Nested jar not found: " + innerJarPath);
8581
}
8682
if (entry.isDirectory()) {
8783
return new Extracted(
@@ -111,19 +107,6 @@ public static Extracted readNestedJarFile(final String jarPath) throws IOExcepti
111107
}
112108
}
113109

114-
private static String getInnerJarPath(final String jarPath) {
115-
final int sepIdx = jarPath.indexOf("!/");
116-
if (sepIdx == -1) {
117-
throw new IllegalArgumentException("Invalid nested jar path: " + jarPath);
118-
}
119-
String innerJarPath = jarPath.substring(sepIdx + 2);
120-
final int innerSepIdx = innerJarPath.indexOf('!');
121-
if (innerSepIdx != -1) {
122-
innerJarPath = innerJarPath.substring(0, innerSepIdx);
123-
}
124-
return innerJarPath;
125-
}
126-
127110
static class NestedJarInputStream extends InputStream implements AutoCloseable {
128111
private final JarFile outerJar;
129112
private final InputStream innerInputStream;

telemetry/src/test/groovy/datadog/telemetry/dependency/DependencyResolverSpecification.groovy

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,48 @@ class DependencyResolverSpecification extends DepSpecification {
159159
dep.source == 'opentracing-util-0.33.0.jar'
160160
}
161161

162+
void 'spring boot dependency new style'() throws IOException {
163+
when:
164+
String zipPath = Classloader.classLoader.getResource('datadog/telemetry/dependencies/spring-boot-app.jar').path
165+
URI uri = new URI("jar:nested:$zipPath/!BOOT-INF/lib/opentracing-util-0.33.0.jar!/")
166+
167+
Dependency dep = DependencyResolver.resolve(uri).get(0)
168+
169+
then:
170+
dep != null
171+
dep.name == 'io.opentracing:opentracing-util'
172+
dep.version == '0.33.0'
173+
dep.hash == null
174+
dep.source == 'opentracing-util-0.33.0.jar'
175+
}
176+
177+
void 'spring boot dependency new style empty path'() throws IOException {
178+
when:
179+
URI uri = new URI("jar:nested:")
180+
List<Dependency> deps = DependencyResolver.resolve(uri)
181+
182+
then:
183+
deps.isEmpty()
184+
}
185+
186+
void 'spring boot dependency old style empty path'() throws IOException {
187+
when:
188+
URI uri = new URI("jar:file:")
189+
List<Dependency> deps = DependencyResolver.resolve(uri)
190+
191+
then:
192+
deps.isEmpty()
193+
}
194+
195+
void 'jar unknown'() throws IOException {
196+
when:
197+
URI uri = new URI("jar:unknown")
198+
List<Dependency> deps = DependencyResolver.resolve(uri)
199+
200+
then:
201+
deps.isEmpty()
202+
}
203+
162204
void 'spring boot dependency without maven metadata'() throws IOException {
163205
given:
164206
def innerJarData = new ByteArrayOutputStream()

telemetry/src/test/groovy/datadog/telemetry/dependency/JarReaderSpecification.groovy

Lines changed: 7 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -52,29 +52,9 @@ class JarReaderSpecification extends DepSpecification {
5252
void 'read nested jar'() {
5353
given:
5454
String outerPath = getJar("spring-boot-app.jar").getAbsolutePath()
55-
String jarPath = "$outerPath!/BOOT-INF/lib/opentracing-util-0.33.0.jar"
5655

5756
when:
58-
def result = JarReader.readNestedJarFile(jarPath)
59-
60-
then:
61-
result.jarName == "opentracing-util-0.33.0.jar"
62-
result.pomProperties.size() == 1
63-
def properties = result.pomProperties['META-INF/maven/io.opentracing/opentracing-util/pom.properties']
64-
properties.groupId == "io.opentracing"
65-
properties.artifactId == "opentracing-util"
66-
properties.version == "0.33.0"
67-
result.manifest != null
68-
result.manifest.getValue("Automatic-Module-Name") == "io.opentracing.util"
69-
}
70-
71-
void 'read nested jar with ending separator'() {
72-
given:
73-
String outerPath = getJar("spring-boot-app.jar").getAbsolutePath()
74-
String jarPath = "$outerPath!/BOOT-INF/lib/opentracing-util-0.33.0.jar!/"
75-
76-
when:
77-
def result = JarReader.readNestedJarFile(jarPath)
57+
def result = JarReader.readNestedJarFile(outerPath, "BOOT-INF/lib/opentracing-util-0.33.0.jar")
7858

7959
then:
8060
result.jarName == "opentracing-util-0.33.0.jar"
@@ -99,11 +79,8 @@ class JarReaderSpecification extends DepSpecification {
9979
}
10080

10181
void 'non-existent outer jar for nested jar'() {
102-
given:
103-
String jarPath = "non-existent.jar!/BOOT-INF/lib/opentracing-util-0.33.0.jar!/"
104-
10582
when:
106-
JarReader.readNestedJarFile(jarPath)
83+
JarReader.readNestedJarFile("non-existent.jar", "BOOT-INF/lib/opentracing-util-0.33.0.jar")
10784

10885
then:
10986
thrown(IOException)
@@ -112,34 +89,27 @@ class JarReaderSpecification extends DepSpecification {
11289
void 'non-existent inner jar for nested jar'() {
11390
given:
11491
String outerPath = getJar("spring-boot-app.jar").getAbsolutePath()
115-
String jarPath = "$outerPath!/BOOT-INF/lib/non-existent.jar"
11692

11793
when:
118-
JarReader.readNestedJarFile(jarPath)
94+
JarReader.readNestedJarFile(outerPath, "BOOT-INF/lib/non-existent.jar")
11995

12096
then:
12197
thrown(IOException)
12298
}
12399

124100
void 'doubly nested jar path'() {
125-
given:
126-
String jarPath = "non-existent.jar!/BOOT-INF/lib/opentracing-util-0.33.0.jar!/third"
127-
128101
when:
129-
JarReader.readNestedJarFile(jarPath)
102+
JarReader.readNestedJarFile("non-existent.jar", "BOOT-INF/lib/opentracing-util-0.33.0.jar/third")
130103

131104
then:
132105
thrown(IOException)
133106
}
134107

135-
void 'lack of nested jar path'() {
136-
given:
137-
String jarPath = "non-existent.jar"
138-
108+
void 'empty nested jar path'() {
139109
when:
140-
JarReader.readNestedJarFile(jarPath)
110+
JarReader.readNestedJarFile("non-existent.jar", "")
141111

142112
then:
143-
thrown(IllegalArgumentException)
113+
thrown(IOException)
144114
}
145115
}

0 commit comments

Comments
 (0)