4848import org .springframework .boot .loader .tools .DefaultLaunchScript ;
4949import org .springframework .boot .loader .tools .FileUtils ;
5050import org .springframework .boot .loader .tools .JarModeLibrary ;
51+ import org .springframework .boot .loader .tools .Layer ;
52+ import org .springframework .boot .loader .tools .LayersIndex ;
53+ import org .springframework .util .Assert ;
54+ import org .springframework .util .FileCopyUtils ;
5155import org .springframework .util .StreamUtils ;
56+ import org .springframework .util .StringUtils ;
5257
5358/**
5459 * A {@link CopyAction} for creating a Spring Boot zip archive (typically a jar or war).
@@ -70,7 +75,7 @@ class BootZipCopyAction implements CopyAction {
7075
7176 private final boolean includeDefaultLoader ;
7277
73- private final boolean includeLayerTools ;
78+ private final String layerToolsLocation ;
7479
7580 private final Spec <FileTreeElement > requiresUnpack ;
7681
@@ -87,15 +92,15 @@ class BootZipCopyAction implements CopyAction {
8792 private final LayerResolver layerResolver ;
8893
8994 BootZipCopyAction (File output , Manifest manifest , boolean preserveFileTimestamps , boolean includeDefaultLoader ,
90- boolean includeLayerTools , Spec <FileTreeElement > requiresUnpack , Spec <FileTreeElement > exclusions ,
95+ String layerToolsLocation , Spec <FileTreeElement > requiresUnpack , Spec <FileTreeElement > exclusions ,
9196 LaunchScriptConfiguration launchScript , Spec <FileCopyDetails > librarySpec ,
9297 Function <FileCopyDetails , ZipCompression > compressionResolver , String encoding ,
9398 LayerResolver layerResolver ) {
9499 this .output = output ;
95100 this .manifest = manifest ;
96101 this .preserveFileTimestamps = preserveFileTimestamps ;
97102 this .includeDefaultLoader = includeDefaultLoader ;
98- this .includeLayerTools = includeLayerTools ;
103+ this .layerToolsLocation = layerToolsLocation ;
99104 this .requiresUnpack = requiresUnpack ;
100105 this .exclusions = exclusions ;
101106 this .launchScript = launchScript ;
@@ -177,14 +182,18 @@ private class Processor {
177182
178183 private ZipArchiveOutputStream out ;
179184
180- private Spec <FileTreeElement > writtenLoaderEntries ;
185+ private final LayersIndex layerIndex ;
186+
187+ private LoaderZipEntries .WrittenEntries writtenLoaderEntries ;
181188
182189 private Set <String > writtenDirectories = new LinkedHashSet <>();
183190
184191 private Set <String > writtenLibraries = new LinkedHashSet <>();
185192
186193 Processor (ZipArchiveOutputStream out ) {
187194 this .out = out ;
195+ this .layerIndex = (BootZipCopyAction .this .layerResolver != null )
196+ ? new LayersIndex (BootZipCopyAction .this .layerResolver .getLayers ()) : null ;
188197 }
189198
190199 void process (FileCopyDetails details ) {
@@ -207,11 +216,11 @@ void process(FileCopyDetails details) {
207216
208217 private boolean skipProcessing (FileCopyDetails details ) {
209218 return BootZipCopyAction .this .exclusions .isSatisfiedBy (details )
210- || (this .writtenLoaderEntries != null && this .writtenLoaderEntries .isSatisfiedBy (details ));
219+ || (this .writtenLoaderEntries != null && this .writtenLoaderEntries .isWrittenDirectory (details ));
211220 }
212221
213222 private void processDirectory (FileCopyDetails details ) throws IOException {
214- String name = getEntryName ( details );
223+ String name = details . getRelativePath (). getPathString ( );
215224 long time = getTime (details );
216225 writeParentDirectoriesIfNecessary (name , time );
217226 ZipArchiveEntry entry = new ZipArchiveEntry (name + '/' );
@@ -223,7 +232,7 @@ private void processDirectory(FileCopyDetails details) throws IOException {
223232 }
224233
225234 private void processFile (FileCopyDetails details ) throws IOException {
226- String name = getEntryName ( details );
235+ String name = details . getRelativePath (). getPathString ( );
227236 long time = getTime (details );
228237 writeParentDirectoriesIfNecessary (name , time );
229238 ZipArchiveEntry entry = new ZipArchiveEntry (name );
@@ -239,6 +248,10 @@ private void processFile(FileCopyDetails details) throws IOException {
239248 if (BootZipCopyAction .this .librarySpec .isSatisfiedBy (details )) {
240249 this .writtenLibraries .add (name .substring (name .lastIndexOf ('/' ) + 1 ));
241250 }
251+ if (BootZipCopyAction .this .layerResolver != null ) {
252+ Layer layer = BootZipCopyAction .this .layerResolver .getLayer (details );
253+ this .layerIndex .add (layer , name );
254+ }
242255 }
243256
244257 private void writeParentDirectoriesIfNecessary (String name , long time ) throws IOException {
@@ -261,34 +274,12 @@ private String getParentDirectory(String name) {
261274 return name .substring (0 , lastSlash );
262275 }
263276
264- private String getEntryName (FileCopyDetails details ) {
265- if (BootZipCopyAction .this .layerResolver == null ) {
266- return details .getRelativePath ().getPathString ();
267- }
268- return BootZipCopyAction .this .layerResolver .getPath (details );
269- }
270-
271- private void prepareStoredEntry (FileCopyDetails details , ZipArchiveEntry archiveEntry ) throws IOException {
272- archiveEntry .setMethod (java .util .zip .ZipEntry .STORED );
273- archiveEntry .setSize (details .getSize ());
274- archiveEntry .setCompressedSize (details .getSize ());
275- archiveEntry .setCrc (getCrc (details ));
276- if (BootZipCopyAction .this .requiresUnpack .isSatisfiedBy (details )) {
277- archiveEntry .setComment ("UNPACK:" + FileUtils .sha1Hash (details .getFile ()));
278- }
279- }
280-
281- private long getCrc (FileCopyDetails details ) {
282- Crc32OutputStream crcStream = new Crc32OutputStream ();
283- details .copyTo (crcStream );
284- return crcStream .getCrc ();
285- }
286-
287277 void finish () throws IOException {
288278 writeLoaderEntriesIfNecessary (null );
289279 writeJarToolsIfNecessary ();
290- writeLayersIndexIfNecessary ();
291280 writeClassPathIndexIfNecessary ();
281+ // We must write the layer index last
282+ writeLayersIndexIfNecessary ();
292283 }
293284
294285 private void writeLoaderEntriesIfNecessary (FileCopyDetails details ) throws IOException {
@@ -299,9 +290,15 @@ private void writeLoaderEntriesIfNecessary(FileCopyDetails details) throws IOExc
299290 // Don't write loader entries until after META-INF folder (see gh-16698)
300291 return ;
301292 }
302- LoaderZipEntries entries = new LoaderZipEntries (
293+ LoaderZipEntries loaderEntries = new LoaderZipEntries (
303294 BootZipCopyAction .this .preserveFileTimestamps ? null : CONSTANT_TIME_FOR_ZIP_ENTRIES );
304- this .writtenLoaderEntries = entries .writeTo (this .out );
295+ this .writtenLoaderEntries = loaderEntries .writeTo (this .out );
296+ if (BootZipCopyAction .this .layerResolver != null ) {
297+ for (String name : this .writtenLoaderEntries .getFiles ()) {
298+ Layer layer = BootZipCopyAction .this .layerResolver .getLayer (name );
299+ this .layerIndex .add (layer , name );
300+ }
301+ }
305302 }
306303
307304 private boolean isInMetaInf (FileCopyDetails details ) {
@@ -313,44 +310,75 @@ private boolean isInMetaInf(FileCopyDetails details) {
313310 }
314311
315312 private void writeJarToolsIfNecessary () throws IOException {
316- if (BootZipCopyAction .this .layerResolver == null || ! BootZipCopyAction . this . includeLayerTools ) {
317- return ;
313+ if (BootZipCopyAction .this .layerToolsLocation != null ) {
314+ writeJarModeLibrary ( BootZipCopyAction . this . layerToolsLocation , JarModeLibrary . LAYER_TOOLS ) ;
318315 }
319- writeJarModeLibrary (JarModeLibrary .LAYER_TOOLS );
320316 }
321317
322- private void writeJarModeLibrary (JarModeLibrary jarModeLibrary ) throws IOException {
323- String name = BootZipCopyAction .this .layerResolver .getPath (jarModeLibrary );
324- writeFile (name , ZipEntryWriter .fromInputStream (jarModeLibrary .openStream ()));
318+ private void writeJarModeLibrary (String location , JarModeLibrary library ) throws IOException {
319+ String name = location + library .getName ();
320+ writeEntry (name , ZipEntryWriter .fromInputStream (library .openStream ()), false ,
321+ (entry ) -> prepareStoredEntry (library .openStream (), entry ));
322+ if (BootZipCopyAction .this .layerResolver != null ) {
323+ Layer layer = BootZipCopyAction .this .layerResolver .getLayer (library );
324+ this .layerIndex .add (layer , name );
325+ }
325326 }
326327
327- private void writeLayersIndexIfNecessary () throws IOException {
328- Attributes manifestAttributes = BootZipCopyAction .this .manifest .getAttributes ();
329- String layersIndex = (String ) manifestAttributes .get ("Spring-Boot-Layers-Index" );
330- if (layersIndex != null && BootZipCopyAction .this .layerResolver != null ) {
331- writeFile (layersIndex , ZipEntryWriter .fromLines (BootZipCopyAction .this .encoding ,
332- BootZipCopyAction .this .layerResolver .getLayerNames ()));
328+ private void prepareStoredEntry (FileCopyDetails details , ZipArchiveEntry archiveEntry ) throws IOException {
329+ prepareStoredEntry (details .open (), archiveEntry );
330+ if (BootZipCopyAction .this .requiresUnpack .isSatisfiedBy (details )) {
331+ archiveEntry .setComment ("UNPACK:" + FileUtils .sha1Hash (details .getFile ()));
333332 }
334333 }
335334
335+ private void prepareStoredEntry (InputStream input , ZipArchiveEntry archiveEntry ) throws IOException {
336+ archiveEntry .setMethod (java .util .zip .ZipEntry .STORED );
337+ Crc32OutputStream crcStream = new Crc32OutputStream ();
338+ int size = FileCopyUtils .copy (input , crcStream );
339+ archiveEntry .setSize (size );
340+ archiveEntry .setCompressedSize (size );
341+ archiveEntry .setCrc (crcStream .getCrc ());
342+ }
343+
336344 private void writeClassPathIndexIfNecessary () throws IOException {
337345 Attributes manifestAttributes = BootZipCopyAction .this .manifest .getAttributes ();
338346 String classPathIndex = (String ) manifestAttributes .get ("Spring-Boot-Classpath-Index" );
339347 if (classPathIndex != null ) {
340- writeFile (classPathIndex ,
341- ZipEntryWriter .fromLines (BootZipCopyAction .this .encoding , this .writtenLibraries ));
348+ writeEntry (classPathIndex ,
349+ ZipEntryWriter .fromLines (BootZipCopyAction .this .encoding , this .writtenLibraries ), true );
350+ }
351+ }
352+
353+ private void writeLayersIndexIfNecessary () throws IOException {
354+ if (BootZipCopyAction .this .layerResolver != null ) {
355+ Attributes manifestAttributes = BootZipCopyAction .this .manifest .getAttributes ();
356+ String name = (String ) manifestAttributes .get ("Spring-Boot-Layers-Index" );
357+ Assert .state (StringUtils .hasText (name ), "Missing layer index manifest attribute" );
358+ Layer layer = BootZipCopyAction .this .layerResolver .getLayer (name );
359+ this .layerIndex .add (layer , name );
360+ writeEntry (name , (entry , out ) -> this .layerIndex .writeTo (out ), false );
342361 }
343362 }
344363
345- private void writeFile (String name , ZipEntryWriter entryWriter ) throws IOException {
364+ private void writeEntry (String name , ZipEntryWriter entryWriter , boolean addToLayerIndex ) throws IOException {
365+ writeEntry (name , entryWriter , addToLayerIndex , ZipEntryCustomizer .NONE );
366+ }
367+
368+ private void writeEntry (String name , ZipEntryWriter entryWriter , boolean addToLayerIndex ,
369+ ZipEntryCustomizer entryCustomizer ) throws IOException {
346370 writeParentDirectoriesIfNecessary (name , CONSTANT_TIME_FOR_ZIP_ENTRIES );
347371 ZipArchiveEntry entry = new ZipArchiveEntry (name );
348372 entry .setUnixMode (UnixStat .FILE_FLAG );
349373 entry .setTime (CONSTANT_TIME_FOR_ZIP_ENTRIES );
374+ entryCustomizer .customize (entry );
350375 this .out .putArchiveEntry (entry );
351376 entryWriter .writeTo (entry , this .out );
352377 this .out .closeArchiveEntry ();
353-
378+ if (addToLayerIndex && BootZipCopyAction .this .layerResolver != null ) {
379+ Layer layer = BootZipCopyAction .this .layerResolver .getLayer (name );
380+ this .layerIndex .add (layer , name );
381+ }
354382 }
355383
356384 private long getTime (FileCopyDetails details ) {
@@ -360,6 +388,24 @@ private long getTime(FileCopyDetails details) {
360388
361389 }
362390
391+ /**
392+ * Callback interface used to customize a {@link ZipArchiveEntry}.
393+ */
394+ @ FunctionalInterface
395+ private interface ZipEntryCustomizer {
396+
397+ ZipEntryCustomizer NONE = (entry ) -> {
398+ };
399+
400+ /**
401+ * Customize the entry.
402+ * @param entry the entry to customize
403+ * @throws IOException on IO error
404+ */
405+ void customize (ZipArchiveEntry entry ) throws IOException ;
406+
407+ }
408+
363409 /**
364410 * Callback used to write a zip entry data.
365411 */
0 commit comments