Skip to content

Commit 2c2b962

Browse files
committed
Merge branch '2.1.x'
Closes gh-17232
2 parents f4d9e1c + d82ccf1 commit 2c2b962

File tree

3 files changed

+214
-131
lines changed

3 files changed

+214
-131
lines changed

spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootZipCopyAction.java

Lines changed: 90 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -22,24 +22,19 @@
2222
import java.io.OutputStream;
2323
import java.util.Calendar;
2424
import java.util.GregorianCalendar;
25-
import java.util.HashSet;
26-
import java.util.Set;
25+
import java.util.Map;
2726
import java.util.function.Function;
2827
import java.util.zip.CRC32;
29-
import java.util.zip.ZipInputStream;
3028

3129
import org.apache.commons.compress.archivers.zip.UnixStat;
3230
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
3331
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
3432
import org.gradle.api.GradleException;
3533
import org.gradle.api.file.FileCopyDetails;
3634
import org.gradle.api.file.FileTreeElement;
37-
import org.gradle.api.internal.file.CopyActionProcessingStreamAction;
3835
import org.gradle.api.internal.file.copy.CopyAction;
3936
import org.gradle.api.internal.file.copy.CopyActionProcessingStream;
40-
import org.gradle.api.internal.file.copy.FileCopyDetailsInternal;
4137
import org.gradle.api.specs.Spec;
42-
import org.gradle.api.specs.Specs;
4338
import org.gradle.api.tasks.WorkResult;
4439

4540
import org.springframework.boot.loader.tools.DefaultLaunchScript;
@@ -50,6 +45,7 @@
5045
* Stores jar files without compression as required by Spring Boot's loader.
5146
*
5247
* @author Andy Wilkinson
48+
* @author Phillip Webb
5349
*/
5450
class BootZipCopyAction implements CopyAction {
5551

@@ -88,191 +84,155 @@ class BootZipCopyAction implements CopyAction {
8884

8985
@Override
9086
public WorkResult execute(CopyActionProcessingStream stream) {
91-
ZipArchiveOutputStream zipStream;
92-
Spec<FileTreeElement> loaderEntries;
9387
try {
94-
FileOutputStream fileStream = new FileOutputStream(this.output);
95-
writeLaunchScriptIfNecessary(fileStream);
96-
zipStream = new ZipArchiveOutputStream(fileStream);
97-
if (this.encoding != null) {
98-
zipStream.setEncoding(this.encoding);
99-
}
100-
loaderEntries = writeLoaderClassesIfNecessary(zipStream);
88+
writeArchive(stream);
89+
return () -> true;
10190
}
10291
catch (IOException ex) {
10392
throw new GradleException("Failed to create " + this.output, ex);
10493
}
94+
}
95+
96+
private void writeArchive(CopyActionProcessingStream stream) throws IOException {
97+
OutputStream outputStream = new FileOutputStream(this.output);
10598
try {
106-
stream.process(new ZipStreamAction(zipStream, this.output, this.preserveFileTimestamps, this.requiresUnpack,
107-
createExclusionSpec(loaderEntries), this.compressionResolver));
108-
}
109-
finally {
99+
writeLaunchScriptIfNecessary(outputStream);
100+
ZipArchiveOutputStream zipOutputStream = new ZipArchiveOutputStream(outputStream);
110101
try {
111-
zipStream.close();
102+
if (this.encoding != null) {
103+
zipOutputStream.setEncoding(this.encoding);
104+
}
105+
Processor processor = new Processor(zipOutputStream);
106+
stream.process(processor::process);
107+
processor.finish();
112108
}
113-
catch (IOException ex) {
114-
// Continue
109+
finally {
110+
closeQuietly(zipOutputStream);
115111
}
116112
}
117-
return () -> true;
118-
}
119-
120-
private Spec<FileTreeElement> createExclusionSpec(Spec<FileTreeElement> loaderEntries) {
121-
return Specs.union(loaderEntries, this.exclusions);
122-
}
123-
124-
private Spec<FileTreeElement> writeLoaderClassesIfNecessary(ZipArchiveOutputStream out) {
125-
if (!this.includeDefaultLoader) {
126-
return Specs.satisfyNone();
113+
finally {
114+
closeQuietly(outputStream);
127115
}
128-
return writeLoaderClasses(out);
129116
}
130117

131-
private Spec<FileTreeElement> writeLoaderClasses(ZipArchiveOutputStream out) {
132-
try (ZipInputStream in = new ZipInputStream(
133-
getClass().getResourceAsStream("/META-INF/loader/spring-boot-loader.jar"))) {
134-
Set<String> entries = new HashSet<>();
135-
java.util.zip.ZipEntry entry;
136-
while ((entry = in.getNextEntry()) != null) {
137-
if (entry.isDirectory() && !entry.getName().startsWith("META-INF/")) {
138-
writeDirectory(new ZipArchiveEntry(entry), out);
139-
entries.add(entry.getName());
140-
}
141-
else if (entry.getName().endsWith(".class")) {
142-
writeClass(new ZipArchiveEntry(entry), in, out);
143-
}
144-
}
145-
return (element) -> {
146-
String path = element.getRelativePath().getPathString();
147-
if (element.isDirectory() && !path.endsWith(("/"))) {
148-
path += "/";
149-
}
150-
return entries.contains(path);
151-
};
152-
}
153-
catch (IOException ex) {
154-
throw new GradleException("Failed to write loader classes", ex);
118+
private void writeLaunchScriptIfNecessary(OutputStream outputStream) {
119+
if (this.launchScript == null) {
120+
return;
155121
}
156-
}
157-
158-
private void writeDirectory(ZipArchiveEntry entry, ZipArchiveOutputStream out) throws IOException {
159-
prepareEntry(entry, UnixStat.DIR_FLAG | UnixStat.DEFAULT_DIR_PERM);
160-
out.putArchiveEntry(entry);
161-
out.closeArchiveEntry();
162-
}
163-
164-
private void writeClass(ZipArchiveEntry entry, ZipInputStream in, ZipArchiveOutputStream out) throws IOException {
165-
prepareEntry(entry, UnixStat.FILE_FLAG | UnixStat.DEFAULT_FILE_PERM);
166-
out.putArchiveEntry(entry);
167-
byte[] buffer = new byte[4096];
168-
int read;
169-
while ((read = in.read(buffer)) > 0) {
170-
out.write(buffer, 0, read);
122+
try {
123+
File file = this.launchScript.getScript();
124+
Map<String, String> properties = this.launchScript.getProperties();
125+
outputStream.write(new DefaultLaunchScript(file, properties).toByteArray());
126+
outputStream.flush();
127+
this.output.setExecutable(true);
171128
}
172-
out.closeArchiveEntry();
173-
}
174-
175-
private void prepareEntry(ZipArchiveEntry entry, int unixMode) {
176-
if (!this.preserveFileTimestamps) {
177-
entry.setTime(CONSTANT_TIME_FOR_ZIP_ENTRIES);
129+
catch (IOException ex) {
130+
throw new GradleException("Failed to write launch script to " + this.output, ex);
178131
}
179-
entry.setUnixMode(unixMode);
180132
}
181133

182-
private void writeLaunchScriptIfNecessary(FileOutputStream fileStream) {
134+
private void closeQuietly(OutputStream outputStream) {
183135
try {
184-
if (this.launchScript != null) {
185-
fileStream
186-
.write(new DefaultLaunchScript(this.launchScript.getScript(), this.launchScript.getProperties())
187-
.toByteArray());
188-
this.output.setExecutable(true);
189-
}
136+
outputStream.close();
190137
}
191138
catch (IOException ex) {
192-
throw new GradleException("Failed to write launch script to " + this.output, ex);
193139
}
194140
}
195141

196-
private static final class ZipStreamAction implements CopyActionProcessingStreamAction {
197-
198-
private final ZipArchiveOutputStream zipStream;
199-
200-
private final File output;
201-
202-
private final boolean preserveFileTimestamps;
203-
204-
private final Spec<FileTreeElement> requiresUnpack;
142+
/**
143+
* Internal process used to copy {@link FileCopyDetails file details} to the zip file.
144+
*/
145+
private class Processor {
205146

206-
private final Spec<FileTreeElement> exclusions;
147+
private ZipArchiveOutputStream outputStream;
207148

208-
private final Function<FileCopyDetails, ZipCompression> compressionType;
149+
private Spec<FileTreeElement> writtenLoaderEntries;
209150

210-
private ZipStreamAction(ZipArchiveOutputStream zipStream, File output, boolean preserveFileTimestamps,
211-
Spec<FileTreeElement> requiresUnpack, Spec<FileTreeElement> exclusions,
212-
Function<FileCopyDetails, ZipCompression> compressionType) {
213-
this.zipStream = zipStream;
214-
this.output = output;
215-
this.preserveFileTimestamps = preserveFileTimestamps;
216-
this.requiresUnpack = requiresUnpack;
217-
this.exclusions = exclusions;
218-
this.compressionType = compressionType;
151+
Processor(ZipArchiveOutputStream outputStream) {
152+
this.outputStream = outputStream;
219153
}
220154

221-
@Override
222-
public void processFile(FileCopyDetailsInternal details) {
223-
if (this.exclusions.isSatisfiedBy(details)) {
155+
public void process(FileCopyDetails details) {
156+
if (BootZipCopyAction.this.exclusions.isSatisfiedBy(details)
157+
|| (this.writtenLoaderEntries != null && this.writtenLoaderEntries.isSatisfiedBy(details))) {
224158
return;
225159
}
226160
try {
161+
writeLoaderEntriesIfNecessary(details);
227162
if (details.isDirectory()) {
228-
createDirectory(details);
163+
processDirectory(details);
229164
}
230165
else {
231-
createFile(details);
166+
processFile(details);
232167
}
233168
}
234169
catch (IOException ex) {
235-
throw new GradleException("Failed to add " + details + " to " + this.output, ex);
170+
throw new GradleException("Failed to add " + details + " to " + BootZipCopyAction.this.output, ex);
171+
}
172+
}
173+
174+
public void finish() throws IOException {
175+
writeLoaderEntriesIfNecessary(null);
176+
}
177+
178+
private void writeLoaderEntriesIfNecessary(FileCopyDetails details) throws IOException {
179+
if (!BootZipCopyAction.this.includeDefaultLoader || this.writtenLoaderEntries != null) {
180+
return;
181+
}
182+
if (isInMetaInf(details)) {
183+
// Don't write loader entries until after META-INF folder (see gh-16698)
184+
return;
185+
}
186+
LoaderZipEntries loaderEntries = new LoaderZipEntries(
187+
BootZipCopyAction.this.preserveFileTimestamps ? null : CONSTANT_TIME_FOR_ZIP_ENTRIES);
188+
this.writtenLoaderEntries = loaderEntries.writeTo(this.outputStream);
189+
}
190+
191+
private boolean isInMetaInf(FileCopyDetails details) {
192+
if (details == null) {
193+
return false;
236194
}
195+
String[] segments = details.getRelativePath().getSegments();
196+
return segments.length > 0 && "META-INF".equals(segments[0]);
237197
}
238198

239-
private void createDirectory(FileCopyDetailsInternal details) throws IOException {
199+
private void processDirectory(FileCopyDetails details) throws IOException {
240200
ZipArchiveEntry archiveEntry = new ZipArchiveEntry(details.getRelativePath().getPathString() + '/');
241201
archiveEntry.setUnixMode(UnixStat.DIR_FLAG | details.getMode());
242202
archiveEntry.setTime(getTime(details));
243-
this.zipStream.putArchiveEntry(archiveEntry);
244-
this.zipStream.closeArchiveEntry();
203+
this.outputStream.putArchiveEntry(archiveEntry);
204+
this.outputStream.closeArchiveEntry();
245205
}
246206

247-
private void createFile(FileCopyDetailsInternal details) throws IOException {
207+
private void processFile(FileCopyDetails details) throws IOException {
248208
String relativePath = details.getRelativePath().getPathString();
249209
ZipArchiveEntry archiveEntry = new ZipArchiveEntry(relativePath);
250210
archiveEntry.setUnixMode(UnixStat.FILE_FLAG | details.getMode());
251211
archiveEntry.setTime(getTime(details));
252-
ZipCompression compression = this.compressionType.apply(details);
212+
ZipCompression compression = BootZipCopyAction.this.compressionResolver.apply(details);
253213
if (compression == ZipCompression.STORED) {
254214
prepareStoredEntry(details, archiveEntry);
255215
}
256-
this.zipStream.putArchiveEntry(archiveEntry);
257-
details.copyTo(this.zipStream);
258-
this.zipStream.closeArchiveEntry();
216+
this.outputStream.putArchiveEntry(archiveEntry);
217+
details.copyTo(this.outputStream);
218+
this.outputStream.closeArchiveEntry();
259219
}
260220

261-
private void prepareStoredEntry(FileCopyDetailsInternal details, ZipArchiveEntry archiveEntry)
262-
throws IOException {
221+
private void prepareStoredEntry(FileCopyDetails details, ZipArchiveEntry archiveEntry) throws IOException {
263222
archiveEntry.setMethod(java.util.zip.ZipEntry.STORED);
264223
archiveEntry.setSize(details.getSize());
265224
archiveEntry.setCompressedSize(details.getSize());
266225
Crc32OutputStream crcStream = new Crc32OutputStream();
267226
details.copyTo(crcStream);
268227
archiveEntry.setCrc(crcStream.getCrc());
269-
if (this.requiresUnpack.isSatisfiedBy(details)) {
228+
if (BootZipCopyAction.this.requiresUnpack.isSatisfiedBy(details)) {
270229
archiveEntry.setComment("UNPACK:" + FileUtils.sha1Hash(details.getFile()));
271230
}
272231
}
273232

274233
private long getTime(FileCopyDetails details) {
275-
return this.preserveFileTimestamps ? details.getLastModified() : CONSTANT_TIME_FOR_ZIP_ENTRIES;
234+
return BootZipCopyAction.this.preserveFileTimestamps ? details.getLastModified()
235+
: CONSTANT_TIME_FOR_ZIP_ENTRIES;
276236
}
277237

278238
}
@@ -282,25 +242,25 @@ private long getTime(FileCopyDetails details) {
282242
*/
283243
private static final class Crc32OutputStream extends OutputStream {
284244

285-
private final CRC32 crc32 = new CRC32();
245+
private final CRC32 crc = new CRC32();
286246

287247
@Override
288248
public void write(int b) throws IOException {
289-
this.crc32.update(b);
249+
this.crc.update(b);
290250
}
291251

292252
@Override
293253
public void write(byte[] b) throws IOException {
294-
this.crc32.update(b);
254+
this.crc.update(b);
295255
}
296256

297257
@Override
298258
public void write(byte[] b, int off, int len) throws IOException {
299-
this.crc32.update(b, off, len);
259+
this.crc.update(b, off, len);
300260
}
301261

302262
private long getCrc() {
303-
return this.crc32.getValue();
263+
return this.crc.getValue();
304264
}
305265

306266
}

0 commit comments

Comments
 (0)