22
22
import java .io .OutputStream ;
23
23
import java .util .Calendar ;
24
24
import java .util .GregorianCalendar ;
25
- import java .util .HashSet ;
26
- import java .util .Set ;
25
+ import java .util .Map ;
27
26
import java .util .function .Function ;
28
27
import java .util .zip .CRC32 ;
29
- import java .util .zip .ZipInputStream ;
30
28
31
29
import org .apache .commons .compress .archivers .zip .UnixStat ;
32
30
import org .apache .commons .compress .archivers .zip .ZipArchiveEntry ;
33
31
import org .apache .commons .compress .archivers .zip .ZipArchiveOutputStream ;
34
32
import org .gradle .api .GradleException ;
35
33
import org .gradle .api .file .FileCopyDetails ;
36
34
import org .gradle .api .file .FileTreeElement ;
37
- import org .gradle .api .internal .file .CopyActionProcessingStreamAction ;
38
35
import org .gradle .api .internal .file .copy .CopyAction ;
39
36
import org .gradle .api .internal .file .copy .CopyActionProcessingStream ;
40
- import org .gradle .api .internal .file .copy .FileCopyDetailsInternal ;
41
37
import org .gradle .api .specs .Spec ;
42
- import org .gradle .api .specs .Specs ;
43
38
import org .gradle .api .tasks .WorkResult ;
44
39
45
40
import org .springframework .boot .loader .tools .DefaultLaunchScript ;
50
45
* Stores jar files without compression as required by Spring Boot's loader.
51
46
*
52
47
* @author Andy Wilkinson
48
+ * @author Phillip Webb
53
49
*/
54
50
class BootZipCopyAction implements CopyAction {
55
51
@@ -88,192 +84,155 @@ class BootZipCopyAction implements CopyAction {
88
84
89
85
@ Override
90
86
public WorkResult execute (CopyActionProcessingStream stream ) {
91
- ZipArchiveOutputStream zipStream ;
92
- Spec <FileTreeElement > loaderEntries ;
93
87
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 ;
101
90
}
102
91
catch (IOException ex ) {
103
92
throw new GradleException ("Failed to create " + this .output , ex );
104
93
}
94
+ }
95
+
96
+ private void writeArchive (CopyActionProcessingStream stream ) throws IOException {
97
+ OutputStream outputStream = new FileOutputStream (this .output );
105
98
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 );
110
101
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 ();
112
108
}
113
- catch ( IOException ex ) {
114
- // Continue
109
+ finally {
110
+ closeQuietly ( zipOutputStream );
115
111
}
116
112
}
117
- return () -> true ;
118
- }
119
-
120
- @ SuppressWarnings ("unchecked" )
121
- private Spec <FileTreeElement > createExclusionSpec (Spec <FileTreeElement > loaderEntries ) {
122
- return Specs .union (loaderEntries , this .exclusions );
123
- }
124
-
125
- private Spec <FileTreeElement > writeLoaderClassesIfNecessary (ZipArchiveOutputStream out ) {
126
- if (!this .includeDefaultLoader ) {
127
- return Specs .satisfyNone ();
113
+ finally {
114
+ closeQuietly (outputStream );
128
115
}
129
- return writeLoaderClasses (out );
130
116
}
131
117
132
- private Spec <FileTreeElement > writeLoaderClasses (ZipArchiveOutputStream out ) {
133
- try (ZipInputStream in = new ZipInputStream (
134
- getClass ().getResourceAsStream ("/META-INF/loader/spring-boot-loader.jar" ))) {
135
- Set <String > entries = new HashSet <>();
136
- java .util .zip .ZipEntry entry ;
137
- while ((entry = in .getNextEntry ()) != null ) {
138
- if (entry .isDirectory () && !entry .getName ().startsWith ("META-INF/" )) {
139
- writeDirectory (new ZipArchiveEntry (entry ), out );
140
- entries .add (entry .getName ());
141
- }
142
- else if (entry .getName ().endsWith (".class" )) {
143
- writeClass (new ZipArchiveEntry (entry ), in , out );
144
- }
145
- }
146
- return (element ) -> {
147
- String path = element .getRelativePath ().getPathString ();
148
- if (element .isDirectory () && !path .endsWith (("/" ))) {
149
- path += "/" ;
150
- }
151
- return entries .contains (path );
152
- };
153
- }
154
- catch (IOException ex ) {
155
- throw new GradleException ("Failed to write loader classes" , ex );
118
+ private void writeLaunchScriptIfNecessary (OutputStream outputStream ) {
119
+ if (this .launchScript == null ) {
120
+ return ;
156
121
}
157
- }
158
-
159
- private void writeDirectory (ZipArchiveEntry entry , ZipArchiveOutputStream out ) throws IOException {
160
- prepareEntry (entry , UnixStat .DIR_FLAG | UnixStat .DEFAULT_DIR_PERM );
161
- out .putArchiveEntry (entry );
162
- out .closeArchiveEntry ();
163
- }
164
-
165
- private void writeClass (ZipArchiveEntry entry , ZipInputStream in , ZipArchiveOutputStream out ) throws IOException {
166
- prepareEntry (entry , UnixStat .FILE_FLAG | UnixStat .DEFAULT_FILE_PERM );
167
- out .putArchiveEntry (entry );
168
- byte [] buffer = new byte [4096 ];
169
- int read ;
170
- while ((read = in .read (buffer )) > 0 ) {
171
- 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 );
172
128
}
173
- out .closeArchiveEntry ();
174
- }
175
-
176
- private void prepareEntry (ZipArchiveEntry entry , int unixMode ) {
177
- if (!this .preserveFileTimestamps ) {
178
- entry .setTime (CONSTANT_TIME_FOR_ZIP_ENTRIES );
129
+ catch (IOException ex ) {
130
+ throw new GradleException ("Failed to write launch script to " + this .output , ex );
179
131
}
180
- entry .setUnixMode (unixMode );
181
132
}
182
133
183
- private void writeLaunchScriptIfNecessary ( FileOutputStream fileStream ) {
134
+ private void closeQuietly ( OutputStream outputStream ) {
184
135
try {
185
- if (this .launchScript != null ) {
186
- fileStream
187
- .write (new DefaultLaunchScript (this .launchScript .getScript (), this .launchScript .getProperties ())
188
- .toByteArray ());
189
- this .output .setExecutable (true );
190
- }
136
+ outputStream .close ();
191
137
}
192
138
catch (IOException ex ) {
193
- throw new GradleException ("Failed to write launch script to " + this .output , ex );
194
139
}
195
140
}
196
141
197
- private static final class ZipStreamAction implements CopyActionProcessingStreamAction {
198
-
199
- private final ZipArchiveOutputStream zipStream ;
200
-
201
- private final File output ;
202
-
203
- private final boolean preserveFileTimestamps ;
204
-
205
- 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 {
206
146
207
- private final Spec < FileTreeElement > exclusions ;
147
+ private ZipArchiveOutputStream outputStream ;
208
148
209
- private final Function < FileCopyDetails , ZipCompression > compressionType ;
149
+ private Spec < FileTreeElement > writtenLoaderEntries ;
210
150
211
- private ZipStreamAction (ZipArchiveOutputStream zipStream , File output , boolean preserveFileTimestamps ,
212
- Spec <FileTreeElement > requiresUnpack , Spec <FileTreeElement > exclusions ,
213
- Function <FileCopyDetails , ZipCompression > compressionType ) {
214
- this .zipStream = zipStream ;
215
- this .output = output ;
216
- this .preserveFileTimestamps = preserveFileTimestamps ;
217
- this .requiresUnpack = requiresUnpack ;
218
- this .exclusions = exclusions ;
219
- this .compressionType = compressionType ;
151
+ Processor (ZipArchiveOutputStream outputStream ) {
152
+ this .outputStream = outputStream ;
220
153
}
221
154
222
- @ Override
223
- public void processFile ( FileCopyDetailsInternal details ) {
224
- 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 ) )) {
225
158
return ;
226
159
}
227
160
try {
161
+ writeLoaderEntriesIfNecessary (details );
228
162
if (details .isDirectory ()) {
229
- createDirectory (details );
163
+ processDirectory (details );
230
164
}
231
165
else {
232
- createFile (details );
166
+ processFile (details );
233
167
}
234
168
}
235
169
catch (IOException ex ) {
236
- 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 ;
237
194
}
195
+ String [] segments = details .getRelativePath ().getSegments ();
196
+ return segments .length > 0 && "META-INF" .equals (segments [0 ]);
238
197
}
239
198
240
- private void createDirectory ( FileCopyDetailsInternal details ) throws IOException {
199
+ private void processDirectory ( FileCopyDetails details ) throws IOException {
241
200
ZipArchiveEntry archiveEntry = new ZipArchiveEntry (details .getRelativePath ().getPathString () + '/' );
242
201
archiveEntry .setUnixMode (UnixStat .DIR_FLAG | details .getMode ());
243
202
archiveEntry .setTime (getTime (details ));
244
- this .zipStream .putArchiveEntry (archiveEntry );
245
- this .zipStream .closeArchiveEntry ();
203
+ this .outputStream .putArchiveEntry (archiveEntry );
204
+ this .outputStream .closeArchiveEntry ();
246
205
}
247
206
248
- private void createFile ( FileCopyDetailsInternal details ) throws IOException {
207
+ private void processFile ( FileCopyDetails details ) throws IOException {
249
208
String relativePath = details .getRelativePath ().getPathString ();
250
209
ZipArchiveEntry archiveEntry = new ZipArchiveEntry (relativePath );
251
210
archiveEntry .setUnixMode (UnixStat .FILE_FLAG | details .getMode ());
252
211
archiveEntry .setTime (getTime (details ));
253
- ZipCompression compression = this .compressionType .apply (details );
212
+ ZipCompression compression = BootZipCopyAction . this .compressionResolver .apply (details );
254
213
if (compression == ZipCompression .STORED ) {
255
214
prepareStoredEntry (details , archiveEntry );
256
215
}
257
- this .zipStream .putArchiveEntry (archiveEntry );
258
- details .copyTo (this .zipStream );
259
- this .zipStream .closeArchiveEntry ();
216
+ this .outputStream .putArchiveEntry (archiveEntry );
217
+ details .copyTo (this .outputStream );
218
+ this .outputStream .closeArchiveEntry ();
260
219
}
261
220
262
- private void prepareStoredEntry (FileCopyDetailsInternal details , ZipArchiveEntry archiveEntry )
263
- throws IOException {
221
+ private void prepareStoredEntry (FileCopyDetails details , ZipArchiveEntry archiveEntry ) throws IOException {
264
222
archiveEntry .setMethod (java .util .zip .ZipEntry .STORED );
265
223
archiveEntry .setSize (details .getSize ());
266
224
archiveEntry .setCompressedSize (details .getSize ());
267
225
Crc32OutputStream crcStream = new Crc32OutputStream ();
268
226
details .copyTo (crcStream );
269
227
archiveEntry .setCrc (crcStream .getCrc ());
270
- if (this .requiresUnpack .isSatisfiedBy (details )) {
228
+ if (BootZipCopyAction . this .requiresUnpack .isSatisfiedBy (details )) {
271
229
archiveEntry .setComment ("UNPACK:" + FileUtils .sha1Hash (details .getFile ()));
272
230
}
273
231
}
274
232
275
233
private long getTime (FileCopyDetails details ) {
276
- return this .preserveFileTimestamps ? details .getLastModified () : CONSTANT_TIME_FOR_ZIP_ENTRIES ;
234
+ return BootZipCopyAction .this .preserveFileTimestamps ? details .getLastModified ()
235
+ : CONSTANT_TIME_FOR_ZIP_ENTRIES ;
277
236
}
278
237
279
238
}
@@ -283,25 +242,25 @@ private long getTime(FileCopyDetails details) {
283
242
*/
284
243
private static final class Crc32OutputStream extends OutputStream {
285
244
286
- private final CRC32 crc32 = new CRC32 ();
245
+ private final CRC32 crc = new CRC32 ();
287
246
288
247
@ Override
289
248
public void write (int b ) throws IOException {
290
- this .crc32 .update (b );
249
+ this .crc .update (b );
291
250
}
292
251
293
252
@ Override
294
253
public void write (byte [] b ) throws IOException {
295
- this .crc32 .update (b );
254
+ this .crc .update (b );
296
255
}
297
256
298
257
@ Override
299
258
public void write (byte [] b , int off , int len ) throws IOException {
300
- this .crc32 .update (b , off , len );
259
+ this .crc .update (b , off , len );
301
260
}
302
261
303
262
private long getCrc () {
304
- return this .crc32 .getValue ();
263
+ return this .crc .getValue ();
305
264
}
306
265
307
266
}
0 commit comments