Use a more compact layers.idx format

Update the `layers.idx` format so that it is more compact and can be
parsed by third-parties as YAML.

Closes gh-20860
This commit is contained in:
Phillip Webb
2020-04-06 15:40:54 -07:00
parent 36fd2ed249
commit 65672a1150
18 changed files with 475 additions and 168 deletions

View File

@@ -31,6 +31,7 @@ import org.springframework.util.Assert;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;
/**
* {@link Layers} implementation backed by a {@code BOOT-INF/layers.idx} file.
@@ -43,12 +44,19 @@ class IndexedLayers implements Layers {
private MultiValueMap<String, String> layers = new LinkedMultiValueMap<>();
IndexedLayers(String indexFile) {
String[] lines = indexFile.split("\n");
Arrays.stream(lines).map(String::trim).filter((line) -> !line.isEmpty()).forEach((line) -> {
String[] content = line.split(" ");
Assert.state(content.length == 2, "Layer index file is malformed");
this.layers.add(content[0], content[1]);
});
String[] lines = Arrays.stream(indexFile.split("\n")).filter(StringUtils::hasText).toArray(String[]::new);
String layer = null;
for (String line : lines) {
if (line.startsWith("- ")) {
layer = line.substring(3, line.length() - 2);
}
else if (line.startsWith(" - ")) {
this.layers.add(layer, line.substring(5, line.length() - 1));
}
else {
throw new IllegalStateException("Layer index file is malformed");
}
}
Assert.state(!this.layers.isEmpty(), "Empty layer index file loaded");
}
@@ -59,10 +67,15 @@ class IndexedLayers implements Layers {
@Override
public String getLayer(ZipEntry entry) {
String name = entry.getName();
for (Map.Entry<String, List<String>> indexEntry : this.layers.entrySet()) {
if (indexEntry.getValue().contains(name)) {
return indexEntry.getKey();
return getLayer(entry.getName());
}
private String getLayer(String name) {
for (Map.Entry<String, List<String>> entry : this.layers.entrySet()) {
for (String candidate : entry.getValue()) {
if (candidate.equals(name) || (candidate.endsWith("/") && name.startsWith(candidate))) {
return entry.getKey();
}
}
}
throw new IllegalStateException("No layer defined in index for file '" + name + "'");

View File

@@ -77,10 +77,13 @@ class HelpCommandTests {
JarEntry indexEntry = new JarEntry("BOOT-INF/layers.idx");
jarOutputStream.putNextEntry(indexEntry);
Writer writer = new OutputStreamWriter(jarOutputStream, StandardCharsets.UTF_8);
writer.write("0001 BOOT-INF/lib/a.jar\n");
writer.write("0001 BOOT-INF/lib/b.jar\n");
writer.write("0002 BOOT-INF/lib/c.jar\n");
writer.write("0003 BOOT-INF/lib/d.jar\n");
writer.write("- \"0001\":\n");
writer.write(" - \"BOOT-INF/lib/a.jar\"\n");
writer.write(" - \"BOOT-INF/lib/b.jar\"\n");
writer.write("- \"0002\":\n");
writer.write(" - \"0002 BOOT-INF/lib/c.jar\"\n");
writer.write("- \"0003\":\n");
writer.write(" - \"BOOT-INF/lib/d.jar\"\n");
writer.flush();
}
return file;

View File

@@ -69,6 +69,13 @@ class IndexedLayersTests {
.withMessage("No layer defined in index for file " + "'file.jar'");
}
@Test
void getLayerWhenMatchesFolderReturnsLayer() throws Exception {
IndexedLayers layers = new IndexedLayers(getIndex());
assertThat(layers.getLayer(mockEntry("META-INF/MANIFEST.MF"))).isEqualTo("application");
assertThat(layers.getLayer(mockEntry("META-INF/a/sub/folder/and/a/file"))).isEqualTo("application");
}
private String getIndex() throws Exception {
ClassPathResource resource = new ClassPathResource("test-layers.idx", getClass());
InputStreamReader reader = new InputStreamReader(resource.getInputStream());

View File

@@ -85,10 +85,13 @@ class LayerToolsJarModeTests {
JarEntry indexEntry = new JarEntry("BOOT-INF/layers.idx");
jarOutputStream.putNextEntry(indexEntry);
Writer writer = new OutputStreamWriter(jarOutputStream, StandardCharsets.UTF_8);
writer.write("0001 BOOT-INF/lib/a.jar\n");
writer.write("0001 BOOT-INF/lib/b.jar\n");
writer.write("0002 BOOT-INF/lib/c.jar\n");
writer.write("0003 BOOT-INF/lib/d.jar\n");
writer.write("- \"0001\":\n");
writer.write(" - \"BOOT-INF/lib/a.jar\"\n");
writer.write(" - \"BOOT-INF/lib/b.jar\"\n");
writer.write("- \"0002\":\n");
writer.write(" - \"0002 BOOT-INF/lib/c.jar\"\n");
writer.write("- \"0003\":\n");
writer.write(" - \"BOOT-INF/lib/d.jar\"\n");
writer.flush();
}
return file;

View File

@@ -98,10 +98,13 @@ class ListCommandTests {
JarEntry indexEntry = new JarEntry("BOOT-INF/layers.idx");
out.putNextEntry(indexEntry);
Writer writer = new OutputStreamWriter(out, StandardCharsets.UTF_8);
writer.write("0001 BOOT-INF/lib/a.jar\n");
writer.write("0001 BOOT-INF/lib/b.jar\n");
writer.write("0002 BOOT-INF/lib/c.jar\n");
writer.write("0003 BOOT-INF/lib/d.jar\n");
writer.write("- \"0001\":\n");
writer.write(" - \"BOOT-INF/lib/a.jar\"\n");
writer.write(" - \"BOOT-INF/lib/b.jar\"\n");
writer.write("- \"0002\":\n");
writer.write(" - \"0002 BOOT-INF/lib/c.jar\"\n");
writer.write("- \"0003\":\n");
writer.write(" - \"BOOT-INF/lib/d.jar\"\n");
writer.flush();
}

View File

@@ -1,3 +1,6 @@
test BOOT-INF/lib/a.jar
test BOOT-INF/lib/b.jar
application BOOT-INF/classes/Demo.class
- "test":
- "BOOT-INF/lib/a.jar"
- "BOOT-INF/lib/b.jar"
- "application":
- "BOOT-INF/classes/Demo.class"
- "META-INF/"