2222import java .io .OutputStream ;
2323import java .util .Calendar ;
2424import java .util .GregorianCalendar ;
25- import java .util .HashSet ;
26- import java .util .Set ;
25+ import java .util .Map ;
2726import java .util .function .Function ;
2827import java .util .zip .CRC32 ;
29- import java .util .zip .ZipInputStream ;
3028
3129import org .apache .commons .compress .archivers .zip .UnixStat ;
3230import org .apache .commons .compress .archivers .zip .ZipArchiveEntry ;
3331import org .apache .commons .compress .archivers .zip .ZipArchiveOutputStream ;
3432import org .gradle .api .GradleException ;
3533import org .gradle .api .file .FileCopyDetails ;
3634import org .gradle .api .file .FileTreeElement ;
37- import org .gradle .api .internal .file .CopyActionProcessingStreamAction ;
3835import org .gradle .api .internal .file .copy .CopyAction ;
3936import org .gradle .api .internal .file .copy .CopyActionProcessingStream ;
40- import org .gradle .api .internal .file .copy .FileCopyDetailsInternal ;
4137import org .gradle .api .specs .Spec ;
42- import org .gradle .api .specs .Specs ;
4338import org .gradle .api .tasks .WorkResult ;
4439
4540import org .springframework .boot .loader .tools .DefaultLaunchScript ;
5045 * Stores jar files without compression as required by Spring Boot's loader.
5146 *
5247 * @author Andy Wilkinson
48+ * @author Phillip Webb
5349 */
5450class BootZipCopyAction implements CopyAction {
5551
@@ -88,192 +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- @ 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 );
128115 }
129- return writeLoaderClasses (out );
130116 }
131117
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 ;
156121 }
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 );
172128 }
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 );
179131 }
180- entry .setUnixMode (unixMode );
181132 }
182133
183- private void writeLaunchScriptIfNecessary ( FileOutputStream fileStream ) {
134+ private void closeQuietly ( OutputStream outputStream ) {
184135 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 ();
191137 }
192138 catch (IOException ex ) {
193- throw new GradleException ("Failed to write launch script to " + this .output , ex );
194139 }
195140 }
196141
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 {
206146
207- private final Spec < FileTreeElement > exclusions ;
147+ private ZipArchiveOutputStream outputStream ;
208148
209- private final Function < FileCopyDetails , ZipCompression > compressionType ;
149+ private Spec < FileTreeElement > writtenLoaderEntries ;
210150
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 ;
220153 }
221154
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 ) )) {
225158 return ;
226159 }
227160 try {
161+ writeLoaderEntriesIfNecessary (details );
228162 if (details .isDirectory ()) {
229- createDirectory (details );
163+ processDirectory (details );
230164 }
231165 else {
232- createFile (details );
166+ processFile (details );
233167 }
234168 }
235169 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 ;
237194 }
195+ String [] segments = details .getRelativePath ().getSegments ();
196+ return segments .length > 0 && "META-INF" .equals (segments [0 ]);
238197 }
239198
240- private void createDirectory ( FileCopyDetailsInternal details ) throws IOException {
199+ private void processDirectory ( FileCopyDetails details ) throws IOException {
241200 ZipArchiveEntry archiveEntry = new ZipArchiveEntry (details .getRelativePath ().getPathString () + '/' );
242201 archiveEntry .setUnixMode (UnixStat .DIR_FLAG | details .getMode ());
243202 archiveEntry .setTime (getTime (details ));
244- this .zipStream .putArchiveEntry (archiveEntry );
245- this .zipStream .closeArchiveEntry ();
203+ this .outputStream .putArchiveEntry (archiveEntry );
204+ this .outputStream .closeArchiveEntry ();
246205 }
247206
248- private void createFile ( FileCopyDetailsInternal details ) throws IOException {
207+ private void processFile ( FileCopyDetails details ) throws IOException {
249208 String relativePath = details .getRelativePath ().getPathString ();
250209 ZipArchiveEntry archiveEntry = new ZipArchiveEntry (relativePath );
251210 archiveEntry .setUnixMode (UnixStat .FILE_FLAG | details .getMode ());
252211 archiveEntry .setTime (getTime (details ));
253- ZipCompression compression = this .compressionType .apply (details );
212+ ZipCompression compression = BootZipCopyAction . this .compressionResolver .apply (details );
254213 if (compression == ZipCompression .STORED ) {
255214 prepareStoredEntry (details , archiveEntry );
256215 }
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 ();
260219 }
261220
262- private void prepareStoredEntry (FileCopyDetailsInternal details , ZipArchiveEntry archiveEntry )
263- throws IOException {
221+ private void prepareStoredEntry (FileCopyDetails details , ZipArchiveEntry archiveEntry ) throws IOException {
264222 archiveEntry .setMethod (java .util .zip .ZipEntry .STORED );
265223 archiveEntry .setSize (details .getSize ());
266224 archiveEntry .setCompressedSize (details .getSize ());
267225 Crc32OutputStream crcStream = new Crc32OutputStream ();
268226 details .copyTo (crcStream );
269227 archiveEntry .setCrc (crcStream .getCrc ());
270- if (this .requiresUnpack .isSatisfiedBy (details )) {
228+ if (BootZipCopyAction . this .requiresUnpack .isSatisfiedBy (details )) {
271229 archiveEntry .setComment ("UNPACK:" + FileUtils .sha1Hash (details .getFile ()));
272230 }
273231 }
274232
275233 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 ;
277236 }
278237
279238 }
@@ -283,25 +242,25 @@ private long getTime(FileCopyDetails details) {
283242 */
284243 private static final class Crc32OutputStream extends OutputStream {
285244
286- private final CRC32 crc32 = new CRC32 ();
245+ private final CRC32 crc = new CRC32 ();
287246
288247 @ Override
289248 public void write (int b ) throws IOException {
290- this .crc32 .update (b );
249+ this .crc .update (b );
291250 }
292251
293252 @ Override
294253 public void write (byte [] b ) throws IOException {
295- this .crc32 .update (b );
254+ this .crc .update (b );
296255 }
297256
298257 @ Override
299258 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 );
301260 }
302261
303262 private long getCrc () {
304- return this .crc32 .getValue ();
263+ return this .crc .getValue ();
305264 }
306265
307266 }
0 commit comments