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,191 +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
- 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 );
127
115
}
128
- return writeLoaderClasses (out );
129
116
}
130
117
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 ;
155
121
}
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 );
171
128
}
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 );
178
131
}
179
- entry .setUnixMode (unixMode );
180
132
}
181
133
182
- private void writeLaunchScriptIfNecessary ( FileOutputStream fileStream ) {
134
+ private void closeQuietly ( OutputStream outputStream ) {
183
135
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 ();
190
137
}
191
138
catch (IOException ex ) {
192
- throw new GradleException ("Failed to write launch script to " + this .output , ex );
193
139
}
194
140
}
195
141
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 {
205
146
206
- private final Spec < FileTreeElement > exclusions ;
147
+ private ZipArchiveOutputStream outputStream ;
207
148
208
- private final Function < FileCopyDetails , ZipCompression > compressionType ;
149
+ private Spec < FileTreeElement > writtenLoaderEntries ;
209
150
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 ;
219
153
}
220
154
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 ) )) {
224
158
return ;
225
159
}
226
160
try {
161
+ writeLoaderEntriesIfNecessary (details );
227
162
if (details .isDirectory ()) {
228
- createDirectory (details );
163
+ processDirectory (details );
229
164
}
230
165
else {
231
- createFile (details );
166
+ processFile (details );
232
167
}
233
168
}
234
169
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 ;
236
194
}
195
+ String [] segments = details .getRelativePath ().getSegments ();
196
+ return segments .length > 0 && "META-INF" .equals (segments [0 ]);
237
197
}
238
198
239
- private void createDirectory ( FileCopyDetailsInternal details ) throws IOException {
199
+ private void processDirectory ( FileCopyDetails details ) throws IOException {
240
200
ZipArchiveEntry archiveEntry = new ZipArchiveEntry (details .getRelativePath ().getPathString () + '/' );
241
201
archiveEntry .setUnixMode (UnixStat .DIR_FLAG | details .getMode ());
242
202
archiveEntry .setTime (getTime (details ));
243
- this .zipStream .putArchiveEntry (archiveEntry );
244
- this .zipStream .closeArchiveEntry ();
203
+ this .outputStream .putArchiveEntry (archiveEntry );
204
+ this .outputStream .closeArchiveEntry ();
245
205
}
246
206
247
- private void createFile ( FileCopyDetailsInternal details ) throws IOException {
207
+ private void processFile ( FileCopyDetails details ) throws IOException {
248
208
String relativePath = details .getRelativePath ().getPathString ();
249
209
ZipArchiveEntry archiveEntry = new ZipArchiveEntry (relativePath );
250
210
archiveEntry .setUnixMode (UnixStat .FILE_FLAG | details .getMode ());
251
211
archiveEntry .setTime (getTime (details ));
252
- ZipCompression compression = this .compressionType .apply (details );
212
+ ZipCompression compression = BootZipCopyAction . this .compressionResolver .apply (details );
253
213
if (compression == ZipCompression .STORED ) {
254
214
prepareStoredEntry (details , archiveEntry );
255
215
}
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 ();
259
219
}
260
220
261
- private void prepareStoredEntry (FileCopyDetailsInternal details , ZipArchiveEntry archiveEntry )
262
- throws IOException {
221
+ private void prepareStoredEntry (FileCopyDetails details , ZipArchiveEntry archiveEntry ) throws IOException {
263
222
archiveEntry .setMethod (java .util .zip .ZipEntry .STORED );
264
223
archiveEntry .setSize (details .getSize ());
265
224
archiveEntry .setCompressedSize (details .getSize ());
266
225
Crc32OutputStream crcStream = new Crc32OutputStream ();
267
226
details .copyTo (crcStream );
268
227
archiveEntry .setCrc (crcStream .getCrc ());
269
- if (this .requiresUnpack .isSatisfiedBy (details )) {
228
+ if (BootZipCopyAction . this .requiresUnpack .isSatisfiedBy (details )) {
270
229
archiveEntry .setComment ("UNPACK:" + FileUtils .sha1Hash (details .getFile ()));
271
230
}
272
231
}
273
232
274
233
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 ;
276
236
}
277
237
278
238
}
@@ -282,25 +242,25 @@ private long getTime(FileCopyDetails details) {
282
242
*/
283
243
private static final class Crc32OutputStream extends OutputStream {
284
244
285
- private final CRC32 crc32 = new CRC32 ();
245
+ private final CRC32 crc = new CRC32 ();
286
246
287
247
@ Override
288
248
public void write (int b ) throws IOException {
289
- this .crc32 .update (b );
249
+ this .crc .update (b );
290
250
}
291
251
292
252
@ Override
293
253
public void write (byte [] b ) throws IOException {
294
- this .crc32 .update (b );
254
+ this .crc .update (b );
295
255
}
296
256
297
257
@ Override
298
258
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 );
300
260
}
301
261
302
262
private long getCrc () {
303
- return this .crc32 .getValue ();
263
+ return this .crc .getValue ();
304
264
}
305
265
306
266
}
0 commit comments