Improve startup performance for nested JARs

Refactor spring-boot-loader to work directly with low level zip data
structures, removing the need to read every byte when the application
loads.

This change was initially driven by the desire to improve tab-completion
time when working with the Spring CLI tool. Local tests show CLI
startup time improving from ~0.7 to ~0.22 seconds.

Startup times for regular Spring Boot applications are also improved,
for example, the tomcat sample application now starts 0.5 seconds
faster.
This commit is contained in:
Phillip Webb
2013-11-08 00:29:23 -08:00
parent 6a6159f106
commit d2678e08de
31 changed files with 1673 additions and 962 deletions

View File

@@ -0,0 +1,143 @@
/*
* Copyright 2012-2013 the original author or 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 org.springframework.boot.loader;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertThat;
/**
* Tests for {@link AsciiBytes}.
*
* @author Phillip Webb
*/
public class AsciiBytesTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void createFromBytes() throws Exception {
AsciiBytes bytes = new AsciiBytes(new byte[] { 65, 66 });
assertThat(bytes.toString(), equalTo("AB"));
}
@Test
public void createFromBytesWithOffset() throws Exception {
AsciiBytes bytes = new AsciiBytes(new byte[] { 65, 66, 67, 68 }, 1, 2);
assertThat(bytes.toString(), equalTo("BC"));
}
@Test
public void createFromString() throws Exception {
AsciiBytes bytes = new AsciiBytes("AB");
assertThat(bytes.toString(), equalTo("AB"));
}
@Test
public void length() throws Exception {
AsciiBytes b1 = new AsciiBytes(new byte[] { 65, 66 });
AsciiBytes b2 = new AsciiBytes(new byte[] { 65, 66, 67, 68 }, 1, 2);
assertThat(b1.length(), equalTo(2));
assertThat(b2.length(), equalTo(2));
}
@Test
public void startWith() throws Exception {
AsciiBytes abc = new AsciiBytes(new byte[] { 65, 66, 67 });
AsciiBytes ab = new AsciiBytes(new byte[] { 65, 66 });
AsciiBytes bc = new AsciiBytes(new byte[] { 65, 66, 67 }, 1, 2);
AsciiBytes abcd = new AsciiBytes(new byte[] { 65, 66, 67, 68 });
assertThat(abc.startsWith(abc), equalTo(true));
assertThat(abc.startsWith(ab), equalTo(true));
assertThat(abc.startsWith(bc), equalTo(false));
assertThat(abc.startsWith(abcd), equalTo(false));
}
@Test
public void endsWith() throws Exception {
AsciiBytes abc = new AsciiBytes(new byte[] { 65, 66, 67 });
AsciiBytes bc = new AsciiBytes(new byte[] { 65, 66, 67 }, 1, 2);
AsciiBytes ab = new AsciiBytes(new byte[] { 65, 66 });
AsciiBytes aabc = new AsciiBytes(new byte[] { 65, 65, 66, 67 });
assertThat(abc.endsWith(abc), equalTo(true));
assertThat(abc.endsWith(bc), equalTo(true));
assertThat(abc.endsWith(ab), equalTo(false));
assertThat(abc.endsWith(aabc), equalTo(false));
}
@Test
public void substringFromBeingIndex() throws Exception {
AsciiBytes abcd = new AsciiBytes(new byte[] { 65, 66, 67, 68 });
assertThat(abcd.substring(0).toString(), equalTo("ABCD"));
assertThat(abcd.substring(1).toString(), equalTo("BCD"));
assertThat(abcd.substring(2).toString(), equalTo("CD"));
assertThat(abcd.substring(3).toString(), equalTo("D"));
assertThat(abcd.substring(4).toString(), equalTo(""));
this.thrown.expect(IndexOutOfBoundsException.class);
abcd.substring(5);
}
@Test
public void substring() throws Exception {
AsciiBytes abcd = new AsciiBytes(new byte[] { 65, 66, 67, 68 });
assertThat(abcd.substring(0, 4).toString(), equalTo("ABCD"));
assertThat(abcd.substring(1, 3).toString(), equalTo("BC"));
assertThat(abcd.substring(3, 4).toString(), equalTo("D"));
assertThat(abcd.substring(3, 3).toString(), equalTo(""));
this.thrown.expect(IndexOutOfBoundsException.class);
abcd.substring(3, 5);
}
@Test
public void appendString() throws Exception {
AsciiBytes bc = new AsciiBytes(new byte[] { 65, 66, 67, 68 }, 1, 2);
AsciiBytes appended = bc.append("D");
assertThat(bc.toString(), equalTo("BC"));
assertThat(appended.toString(), equalTo("BCD"));
}
@Test
public void appendBytes() throws Exception {
AsciiBytes bc = new AsciiBytes(new byte[] { 65, 66, 67, 68 }, 1, 2);
AsciiBytes appended = bc.append(new byte[] { 68 });
assertThat(bc.toString(), equalTo("BC"));
assertThat(appended.toString(), equalTo("BCD"));
}
@Test
public void hashCodeAndEquals() throws Exception {
AsciiBytes abcd = new AsciiBytes(new byte[] { 65, 66, 67, 68 });
AsciiBytes bc = new AsciiBytes(new byte[] { 66, 67 });
AsciiBytes bc_substring = new AsciiBytes(new byte[] { 65, 66, 67, 68 })
.substring(1, 3);
AsciiBytes bc_string = new AsciiBytes("BC");
assertThat(bc.hashCode(), equalTo(bc.hashCode()));
assertThat(bc.hashCode(), equalTo(bc_substring.hashCode()));
assertThat(bc.hashCode(), equalTo(bc_string.hashCode()));
assertThat(bc, equalTo(bc));
assertThat(bc, equalTo(bc_substring));
assertThat(bc, equalTo(bc_string));
assertThat(bc.hashCode(), not(equalTo(abcd.hashCode())));
assertThat(bc, not(equalTo(abcd)));
}
}

View File

@@ -33,10 +33,9 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.springframework.boot.loader.AsciiBytes;
import org.springframework.boot.loader.TestJarCreator;
import org.springframework.boot.loader.archive.Archive;
import org.springframework.boot.loader.archive.Archive.Entry;
import org.springframework.boot.loader.archive.ExplodedArchive;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.nullValue;
@@ -131,8 +130,8 @@ public class ExplodedArchiveTests {
Archive filteredArchive = this.archive
.getFilteredArchive(new Archive.EntryRenameFilter() {
@Override
public String apply(String entryName, Entry entry) {
if (entryName.equals("1.dat")) {
public AsciiBytes apply(AsciiBytes entryName, Entry entry) {
if (entryName.toString().equals("1.dat")) {
return entryName;
}
return null;
@@ -149,7 +148,7 @@ public class ExplodedArchiveTests {
private Map<String, Archive.Entry> getEntriesMap(Archive archive) {
Map<String, Archive.Entry> entries = new HashMap<String, Archive.Entry>();
for (Archive.Entry entry : archive.getEntries()) {
entries.put(entry.getName(), entry);
entries.put(entry.getName().toString(), entry);
}
return entries;
}

View File

@@ -25,9 +25,8 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.springframework.boot.loader.AsciiBytes;
import org.springframework.boot.loader.TestJarCreator;
import org.springframework.boot.loader.archive.Archive;
import org.springframework.boot.loader.archive.JarFileArchive;
import org.springframework.boot.loader.archive.Archive.Entry;
import static org.hamcrest.Matchers.equalTo;
@@ -86,8 +85,8 @@ public class JarFileArchiveTests {
Archive filteredArchive = this.archive
.getFilteredArchive(new Archive.EntryRenameFilter() {
@Override
public String apply(String entryName, Entry entry) {
if (entryName.equals("1.dat")) {
public AsciiBytes apply(AsciiBytes entryName, Entry entry) {
if (entryName.toString().equals("1.dat")) {
return entryName;
}
return null;
@@ -100,7 +99,7 @@ public class JarFileArchiveTests {
private Map<String, Archive.Entry> getEntriesMap(Archive archive) {
Map<String, Archive.Entry> entries = new HashMap<String, Archive.Entry>();
for (Archive.Entry entry : archive.getEntries()) {
entries.put(entry.getName(), entry);
entries.put(entry.getName().toString(), entry);
}
return entries;
}

View File

@@ -17,6 +17,7 @@
package org.springframework.boot.loader.data;
import org.junit.Test;
import org.springframework.boot.loader.data.RandomAccessData.ResourceAccess;
import org.springframework.util.FileCopyUtils;
import static org.hamcrest.Matchers.equalTo;
@@ -33,7 +34,8 @@ public class ByteArrayRandomAccessDataTest {
public void testGetInputStream() throws Exception {
byte[] bytes = new byte[] { 0, 1, 2, 3, 4, 5 };
RandomAccessData data = new ByteArrayRandomAccessData(bytes);
assertThat(FileCopyUtils.copyToByteArray(data.getInputStream()), equalTo(bytes));
assertThat(FileCopyUtils.copyToByteArray(data
.getInputStream(ResourceAccess.PER_READ)), equalTo(bytes));
assertThat(data.getSize(), equalTo((long) bytes.length));
}
@@ -42,8 +44,8 @@ public class ByteArrayRandomAccessDataTest {
byte[] bytes = new byte[] { 0, 1, 2, 3, 4, 5 };
RandomAccessData data = new ByteArrayRandomAccessData(bytes);
data = data.getSubsection(1, 4).getSubsection(1, 2);
assertThat(FileCopyUtils.copyToByteArray(data.getInputStream()),
equalTo(new byte[] { 2, 3 }));
assertThat(FileCopyUtils.copyToByteArray(data
.getInputStream(ResourceAccess.PER_READ)), equalTo(new byte[] { 2, 3 }));
assertThat(data.getSize(), equalTo(2L));
}
}

View File

@@ -16,9 +16,6 @@
package org.springframework.boot.loader.data;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
@@ -40,8 +37,10 @@ import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder;
import org.springframework.boot.loader.ByteArrayStartsWith;
import org.springframework.boot.loader.data.RandomAccessData;
import org.springframework.boot.loader.data.RandomAccessDataFile;
import org.springframework.boot.loader.data.RandomAccessData.ResourceAccess;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
/**
* Tests for {@link RandomAccessDataFile}.
@@ -72,73 +71,73 @@ public class RandomAccessDataFileTests {
@Before
public void setup() throws Exception {
this.tempFile = temporaryFolder.newFile();
FileOutputStream outputStream = new FileOutputStream(tempFile);
this.tempFile = this.temporaryFolder.newFile();
FileOutputStream outputStream = new FileOutputStream(this.tempFile);
outputStream.write(BYTES);
outputStream.close();
this.file = new RandomAccessDataFile(tempFile);
this.inputStream = file.getInputStream();
this.file = new RandomAccessDataFile(this.tempFile);
this.inputStream = this.file.getInputStream(ResourceAccess.PER_READ);
}
@After
public void cleanup() throws Exception {
inputStream.close();
file.close();
this.inputStream.close();
this.file.close();
}
@Test
public void fileNotNull() throws Exception {
thrown.expect(IllegalArgumentException.class);
thrown.equals("File must not be null");
this.thrown.expect(IllegalArgumentException.class);
this.thrown.equals("File must not be null");
new RandomAccessDataFile(null);
}
@Test
public void fileExists() throws Exception {
thrown.expect(IllegalArgumentException.class);
thrown.equals("File must exist");
this.thrown.expect(IllegalArgumentException.class);
this.thrown.equals("File must exist");
new RandomAccessDataFile(new File("/does/not/exist"));
}
@Test
public void fileNotNullWithConcurrentReads() throws Exception {
thrown.expect(IllegalArgumentException.class);
thrown.equals("File must not be null");
this.thrown.expect(IllegalArgumentException.class);
this.thrown.equals("File must not be null");
new RandomAccessDataFile(null, 1);
}
@Test
public void fileExistsWithConcurrentReads() throws Exception {
thrown.expect(IllegalArgumentException.class);
thrown.equals("File must exist");
this.thrown.expect(IllegalArgumentException.class);
this.thrown.equals("File must exist");
new RandomAccessDataFile(new File("/does/not/exist"), 1);
}
@Test
public void inputStreamRead() throws Exception {
for (int i = 0; i <= 255; i++) {
assertThat(inputStream.read(), equalTo(i));
assertThat(this.inputStream.read(), equalTo(i));
}
}
@Test
public void inputStreamReadNullBytes() throws Exception {
thrown.expect(NullPointerException.class);
thrown.expectMessage("Bytes must not be null");
inputStream.read(null);
this.thrown.expect(NullPointerException.class);
this.thrown.expectMessage("Bytes must not be null");
this.inputStream.read(null);
}
@Test
public void intputStreamReadNullBytesWithOffset() throws Exception {
thrown.expect(NullPointerException.class);
thrown.expectMessage("Bytes must not be null");
inputStream.read(null, 0, 1);
this.thrown.expect(NullPointerException.class);
this.thrown.expectMessage("Bytes must not be null");
this.inputStream.read(null, 0, 1);
}
@Test
public void inputStreamReadBytes() throws Exception {
byte[] b = new byte[256];
int amountRead = inputStream.read(b);
int amountRead = this.inputStream.read(b);
assertThat(b, equalTo(BYTES));
assertThat(amountRead, equalTo(256));
}
@@ -146,8 +145,8 @@ public class RandomAccessDataFileTests {
@Test
public void inputSteamReadOffsetBytes() throws Exception {
byte[] b = new byte[7];
inputStream.skip(1);
int amountRead = inputStream.read(b, 2, 3);
this.inputStream.skip(1);
int amountRead = this.inputStream.read(b, 2, 3);
assertThat(b, equalTo(new byte[] { 0, 0, 1, 2, 3, 0, 0 }));
assertThat(amountRead, equalTo(3));
}
@@ -155,91 +154,91 @@ public class RandomAccessDataFileTests {
@Test
public void inputStreamReadMoreBytesThanAvailable() throws Exception {
byte[] b = new byte[257];
int amountRead = inputStream.read(b);
int amountRead = this.inputStream.read(b);
assertThat(b, startsWith(BYTES));
assertThat(amountRead, equalTo(256));
}
@Test
public void inputStreamReadPastEnd() throws Exception {
inputStream.skip(255);
assertThat(inputStream.read(), equalTo(0xFF));
assertThat(inputStream.read(), equalTo(-1));
assertThat(inputStream.read(), equalTo(-1));
this.inputStream.skip(255);
assertThat(this.inputStream.read(), equalTo(0xFF));
assertThat(this.inputStream.read(), equalTo(-1));
assertThat(this.inputStream.read(), equalTo(-1));
}
@Test
public void inputStreamReadZeroLength() throws Exception {
byte[] b = new byte[] { 0x0F };
int amountRead = inputStream.read(b, 0, 0);
int amountRead = this.inputStream.read(b, 0, 0);
assertThat(b, equalTo(new byte[] { 0x0F }));
assertThat(amountRead, equalTo(0));
assertThat(inputStream.read(), equalTo(0));
assertThat(this.inputStream.read(), equalTo(0));
}
@Test
public void inputStreamSkip() throws Exception {
long amountSkipped = inputStream.skip(4);
assertThat(inputStream.read(), equalTo(4));
long amountSkipped = this.inputStream.skip(4);
assertThat(this.inputStream.read(), equalTo(4));
assertThat(amountSkipped, equalTo(4L));
}
@Test
public void inputStreamSkipMoreThanAvailable() throws Exception {
long amountSkipped = inputStream.skip(257);
assertThat(inputStream.read(), equalTo(-1));
long amountSkipped = this.inputStream.skip(257);
assertThat(this.inputStream.read(), equalTo(-1));
assertThat(amountSkipped, equalTo(256L));
}
@Test
public void inputStreamSkipPastEnd() throws Exception {
inputStream.skip(256);
long amountSkipped = inputStream.skip(1);
this.inputStream.skip(256);
long amountSkipped = this.inputStream.skip(1);
assertThat(amountSkipped, equalTo(0L));
}
@Test
public void subsectionNegativeOffset() throws Exception {
thrown.expect(IndexOutOfBoundsException.class);
file.getSubsection(-1, 1);
this.thrown.expect(IndexOutOfBoundsException.class);
this.file.getSubsection(-1, 1);
}
@Test
public void subsectionNegativeLength() throws Exception {
thrown.expect(IndexOutOfBoundsException.class);
file.getSubsection(0, -1);
this.thrown.expect(IndexOutOfBoundsException.class);
this.file.getSubsection(0, -1);
}
@Test
public void subsectionZeroLength() throws Exception {
RandomAccessData subsection = file.getSubsection(0, 0);
assertThat(subsection.getInputStream().read(), equalTo(-1));
RandomAccessData subsection = this.file.getSubsection(0, 0);
assertThat(subsection.getInputStream(ResourceAccess.PER_READ).read(), equalTo(-1));
}
@Test
public void subsectionTooBig() throws Exception {
file.getSubsection(0, 256);
thrown.expect(IndexOutOfBoundsException.class);
file.getSubsection(0, 257);
this.file.getSubsection(0, 256);
this.thrown.expect(IndexOutOfBoundsException.class);
this.file.getSubsection(0, 257);
}
@Test
public void subsectionTooBigWithOffset() throws Exception {
file.getSubsection(1, 255);
thrown.expect(IndexOutOfBoundsException.class);
file.getSubsection(1, 256);
this.file.getSubsection(1, 255);
this.thrown.expect(IndexOutOfBoundsException.class);
this.file.getSubsection(1, 256);
}
@Test
public void subsection() throws Exception {
RandomAccessData subsection = file.getSubsection(1, 1);
assertThat(subsection.getInputStream().read(), equalTo(1));
RandomAccessData subsection = this.file.getSubsection(1, 1);
assertThat(subsection.getInputStream(ResourceAccess.PER_READ).read(), equalTo(1));
}
@Test
public void inputStreamReadPastSubsection() throws Exception {
RandomAccessData subsection = file.getSubsection(1, 2);
InputStream inputStream = subsection.getInputStream();
RandomAccessData subsection = this.file.getSubsection(1, 2);
InputStream inputStream = subsection.getInputStream(ResourceAccess.PER_READ);
assertThat(inputStream.read(), equalTo(1));
assertThat(inputStream.read(), equalTo(2));
assertThat(inputStream.read(), equalTo(-1));
@@ -247,8 +246,8 @@ public class RandomAccessDataFileTests {
@Test
public void inputStreamReadBytesPastSubsection() throws Exception {
RandomAccessData subsection = file.getSubsection(1, 2);
InputStream inputStream = subsection.getInputStream();
RandomAccessData subsection = this.file.getSubsection(1, 2);
InputStream inputStream = subsection.getInputStream(ResourceAccess.PER_READ);
byte[] b = new byte[3];
int amountRead = inputStream.read(b);
assertThat(b, equalTo(new byte[] { 1, 2, 0 }));
@@ -257,20 +256,20 @@ public class RandomAccessDataFileTests {
@Test
public void inputStreamSkipPastSubsection() throws Exception {
RandomAccessData subsection = file.getSubsection(1, 2);
InputStream inputStream = subsection.getInputStream();
RandomAccessData subsection = this.file.getSubsection(1, 2);
InputStream inputStream = subsection.getInputStream(ResourceAccess.PER_READ);
assertThat(inputStream.skip(3), equalTo(2L));
assertThat(inputStream.read(), equalTo(-1));
}
@Test
public void inputStreamSkipNegative() throws Exception {
assertThat(inputStream.skip(-1), equalTo(0L));
assertThat(this.inputStream.skip(-1), equalTo(0L));
}
@Test
public void getFile() throws Exception {
assertThat(file.getFile(), equalTo(tempFile));
assertThat(this.file.getFile(), equalTo(this.tempFile));
}
@Test
@@ -282,8 +281,9 @@ public class RandomAccessDataFileTests {
@Override
public Boolean call() throws Exception {
InputStream subsectionInputStream = file.getSubsection(0, 256)
.getInputStream();
InputStream subsectionInputStream = RandomAccessDataFileTests.this.file
.getSubsection(0, 256)
.getInputStream(ResourceAccess.PER_READ);
byte[] b = new byte[256];
subsectionInputStream.read(b);
return Arrays.equals(b, BYTES);
@@ -297,11 +297,11 @@ public class RandomAccessDataFileTests {
@Test
public void close() throws Exception {
file.getInputStream().read();
file.close();
this.file.getInputStream(ResourceAccess.PER_READ).read();
this.file.close();
Field filePoolField = RandomAccessDataFile.class.getDeclaredField("filePool");
filePoolField.setAccessible(true);
Object filePool = filePoolField.get(file);
Object filePool = filePoolField.get(this.file);
Field filesField = filePool.getClass().getDeclaredField("files");
filesField.setAccessible(true);
Queue<?> queue = (Queue<?>) filesField.get(filePool);

View File

@@ -1,95 +0,0 @@
/*
* Copyright 2012-2013 the original author or 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 org.springframework.boot.loader.jar;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertThat;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.springframework.boot.loader.data.RandomAccessDataFile;
import org.springframework.boot.loader.jar.RandomAccessDataJarEntry;
import org.springframework.boot.loader.jar.RandomAccessDataJarInputStream;
/**
* Tests for {@link RandomAccessDataJarInputStream}.
*
* @author Phillip Webb
*/
public class RandomAccessDataJarInputStreamTests {
@Rule
public TemporaryFolder temporaryFolder = new TemporaryFolder();
private File file;
@Before
public void setup() throws Exception {
this.file = temporaryFolder.newFile();
ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream(file));
try {
writeDataEntry(zipOutputStream, "a", new byte[10]);
writeDataEntry(zipOutputStream, "b", new byte[20]);
}
finally {
zipOutputStream.close();
}
}
private void writeDataEntry(ZipOutputStream zipOutputStream, String name, byte[] data)
throws IOException {
ZipEntry entry = new ZipEntry(name);
entry.setMethod(ZipEntry.STORED);
entry.setSize(data.length);
entry.setCompressedSize(data.length);
CRC32 crc32 = new CRC32();
crc32.update(data);
entry.setCrc(crc32.getValue());
zipOutputStream.putNextEntry(entry);
zipOutputStream.write(data);
zipOutputStream.closeEntry();
}
@Test
public void entryData() throws Exception {
RandomAccessDataJarInputStream z = new RandomAccessDataJarInputStream(
new RandomAccessDataFile(file));
try {
RandomAccessDataJarEntry entry1 = z.getNextEntry();
RandomAccessDataJarEntry entry2 = z.getNextEntry();
assertThat(entry1.getName(), equalTo("a"));
assertThat(entry1.getData().getSize(), equalTo(10L));
assertThat(entry2.getName(), equalTo("b"));
assertThat(entry2.getData().getSize(), equalTo(20L));
assertThat(z.getNextEntry(), nullValue());
}
finally {
z.close();
}
}
}

View File

@@ -20,11 +20,9 @@ import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;
@@ -33,6 +31,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder;
import org.springframework.boot.loader.AsciiBytes;
import org.springframework.boot.loader.TestJarCreator;
import org.springframework.boot.loader.data.RandomAccessDataFile;
@@ -42,12 +41,13 @@ import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.Matchers.sameInstance;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link RandomAccessJarFile}.
* Tests for {@link JarFile}.
*
* @author Phillip Webb
*/
@@ -61,27 +61,18 @@ public class RandomAccessJarFileTests {
private File rootJarFile;
private RandomAccessJarFile jarFile;
private JarFile jarFile;
@Before
public void setup() throws Exception {
this.rootJarFile = this.temporaryFolder.newFile();
TestJarCreator.createTestJar(this.rootJarFile);
this.jarFile = new RandomAccessJarFile(this.rootJarFile);
this.jarFile = new JarFile(this.rootJarFile);
}
@Test
public void createFromFile() throws Exception {
RandomAccessJarFile jarFile = new RandomAccessJarFile(this.rootJarFile);
assertThat(jarFile.getName(), notNullValue(String.class));
jarFile.close();
}
@Test
public void createFromRandomAccessDataFile() throws Exception {
RandomAccessDataFile randomAccessDataFile = new RandomAccessDataFile(
this.rootJarFile, 1);
RandomAccessJarFile jarFile = new RandomAccessJarFile(randomAccessDataFile);
JarFile jarFile = new JarFile(this.rootJarFile);
assertThat(jarFile.getName(), notNullValue(String.class));
jarFile.close();
}
@@ -101,7 +92,7 @@ public class RandomAccessJarFileTests {
@Test
public void getEntries() throws Exception {
Enumeration<JarEntry> entries = this.jarFile.entries();
Enumeration<java.util.jar.JarEntry> entries = this.jarFile.entries();
assertThat(entries.nextElement().getName(), equalTo("META-INF/"));
assertThat(entries.nextElement().getName(), equalTo("META-INF/MANIFEST.MF"));
assertThat(entries.nextElement().getName(), equalTo("1.dat"));
@@ -114,7 +105,7 @@ public class RandomAccessJarFileTests {
@Test
public void getJarEntry() throws Exception {
JarEntry entry = this.jarFile.getJarEntry("1.dat");
java.util.jar.JarEntry entry = this.jarFile.getJarEntry("1.dat");
assertThat(entry, notNullValue(ZipEntry.class));
assertThat(entry.getName(), equalTo("1.dat"));
}
@@ -143,7 +134,7 @@ public class RandomAccessJarFileTests {
public void close() throws Exception {
RandomAccessDataFile randomAccessDataFile = spy(new RandomAccessDataFile(
this.rootJarFile, 1));
RandomAccessJarFile jarFile = new RandomAccessJarFile(randomAccessDataFile);
JarFile jarFile = new JarFile(randomAccessDataFile);
jarFile.close();
verify(randomAccessDataFile).close();
}
@@ -154,7 +145,7 @@ public class RandomAccessJarFileTests {
assertThat(url.toString(), equalTo("jar:file:" + this.rootJarFile.getPath()
+ "!/"));
JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
assertThat(jarURLConnection.getJarFile(), sameInstance((JarFile) this.jarFile));
assertThat(jarURLConnection.getJarFile(), sameInstance(this.jarFile));
assertThat(jarURLConnection.getJarEntry(), nullValue());
assertThat(jarURLConnection.getContentLength(), greaterThan(1));
assertThat(jarURLConnection.getContent(), sameInstance((Object) this.jarFile));
@@ -167,7 +158,7 @@ public class RandomAccessJarFileTests {
assertThat(url.toString(), equalTo("jar:file:" + this.rootJarFile.getPath()
+ "!/1.dat"));
JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
assertThat(jarURLConnection.getJarFile(), sameInstance((JarFile) this.jarFile));
assertThat(jarURLConnection.getJarFile(), sameInstance(this.jarFile));
assertThat(jarURLConnection.getJarEntry(),
sameInstance(this.jarFile.getJarEntry("1.dat")));
assertThat(jarURLConnection.getContentLength(), equalTo(1));
@@ -203,10 +194,10 @@ public class RandomAccessJarFileTests {
@Test
public void getNestedJarFile() throws Exception {
RandomAccessJarFile nestedJarFile = this.jarFile.getNestedJarFile(this.jarFile
JarFile nestedJarFile = this.jarFile.getNestedJarFile(this.jarFile
.getEntry("nested.jar"));
Enumeration<JarEntry> entries = nestedJarFile.entries();
Enumeration<java.util.jar.JarEntry> entries = nestedJarFile.entries();
assertThat(entries.nextElement().getName(), equalTo("META-INF/"));
assertThat(entries.nextElement().getName(), equalTo("META-INF/MANIFEST.MF"));
assertThat(entries.nextElement().getName(), equalTo("3.dat"));
@@ -222,15 +213,15 @@ public class RandomAccessJarFileTests {
assertThat(url.toString(), equalTo("jar:file:" + this.rootJarFile.getPath()
+ "!/nested.jar!/"));
assertThat(((JarURLConnection) url.openConnection()).getJarFile(),
sameInstance((JarFile) nestedJarFile));
sameInstance(nestedJarFile));
}
@Test
public void getNestedJarDirectory() throws Exception {
RandomAccessJarFile nestedJarFile = this.jarFile.getNestedJarFile(this.jarFile
.getEntry("d/"));
JarFile nestedJarFile = this.jarFile
.getNestedJarFile(this.jarFile.getEntry("d/"));
Enumeration<JarEntry> entries = nestedJarFile.entries();
Enumeration<java.util.jar.JarEntry> entries = nestedJarFile.entries();
assertThat(entries.nextElement().getName(), equalTo("9.dat"));
assertThat(entries.hasMoreElements(), equalTo(false));
@@ -243,7 +234,7 @@ public class RandomAccessJarFileTests {
assertThat(url.toString(), equalTo("jar:file:" + this.rootJarFile.getPath()
+ "!/d!/"));
assertThat(((JarURLConnection) url.openConnection()).getJarFile(),
sameInstance((JarFile) nestedJarFile));
sameInstance(nestedJarFile));
}
@Test
@@ -263,17 +254,16 @@ public class RandomAccessJarFileTests {
@Test
public void getFilteredJarFile() throws Exception {
RandomAccessJarFile filteredJarFile = this.jarFile
.getFilteredJarFile(new JarEntryFilter() {
@Override
public String apply(String entryName, JarEntry entry) {
if (entryName.equals("1.dat")) {
return "x.dat";
}
return null;
}
});
Enumeration<JarEntry> entries = filteredJarFile.entries();
JarFile filteredJarFile = this.jarFile.getFilteredJarFile(new JarEntryFilter() {
@Override
public AsciiBytes apply(AsciiBytes entryName, JarEntryData entry) {
if (entryName.toString().equals("1.dat")) {
return new AsciiBytes("x.dat");
}
return null;
}
});
Enumeration<java.util.jar.JarEntry> entries = filteredJarFile.entries();
assertThat(entries.nextElement().getName(), equalTo("x.dat"));
assertThat(entries.hasMoreElements(), equalTo(false));
@@ -289,4 +279,31 @@ public class RandomAccessJarFileTests {
assertThat(this.jarFile.getNestedJarFile(this.jarFile.getEntry("nested.jar"))
.toString(), equalTo(this.rootJarFile.getPath() + "!/nested.jar"));
}
@Test
public void verifySignedJar() throws Exception {
String classpath = System.getProperty("java.class.path");
String[] entries = classpath.split(System.getProperty("path.separator"));
String signedJarFile = null;
for (String entry : entries) {
if (entry.contains("bcprov")) {
signedJarFile = entry;
}
}
assertNotNull(signedJarFile);
java.util.jar.JarFile jarFile = new JarFile(new File(signedJarFile));
jarFile.getManifest();
Enumeration<JarEntry> jarEntries = jarFile.entries();
while (jarEntries.hasMoreElements()) {
JarEntry jarEntry = jarEntries.nextElement();
InputStream inputStream = jarFile.getInputStream(jarEntry);
inputStream.skip(Long.MAX_VALUE);
inputStream.close();
if (!jarEntry.getName().startsWith("META-INF") && !jarEntry.isDirectory()
&& !jarEntry.getName().endsWith("TigerDigest.class")) {
assertNotNull("Missing cert " + jarEntry.getName(),
jarEntry.getCertificates());
}
}
}
}