@ -22,11 +22,9 @@ import java.io.IOException;
import java.io.OutputStream ;
import java.io.OutputStream ;
import java.util.Calendar ;
import java.util.Calendar ;
import java.util.GregorianCalendar ;
import java.util.GregorianCalendar ;
import java.util.HashSet ;
import java.util.Map ;
import java.util.Set ;
import java.util.function.Function ;
import java.util.function.Function ;
import java.util.zip.CRC32 ;
import java.util.zip.CRC32 ;
import java.util.zip.ZipInputStream ;
import org.apache.commons.compress.archivers.zip.UnixStat ;
import org.apache.commons.compress.archivers.zip.UnixStat ;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry ;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry ;
@ -34,12 +32,9 @@ import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import org.gradle.api.GradleException ;
import org.gradle.api.GradleException ;
import org.gradle.api.file.FileCopyDetails ;
import org.gradle.api.file.FileCopyDetails ;
import org.gradle.api.file.FileTreeElement ;
import org.gradle.api.file.FileTreeElement ;
import org.gradle.api.internal.file.CopyActionProcessingStreamAction ;
import org.gradle.api.internal.file.copy.CopyAction ;
import org.gradle.api.internal.file.copy.CopyAction ;
import org.gradle.api.internal.file.copy.CopyActionProcessingStream ;
import org.gradle.api.internal.file.copy.CopyActionProcessingStream ;
import org.gradle.api.internal.file.copy.FileCopyDetailsInternal ;
import org.gradle.api.specs.Spec ;
import org.gradle.api.specs.Spec ;
import org.gradle.api.specs.Specs ;
import org.gradle.api.tasks.WorkResult ;
import org.gradle.api.tasks.WorkResult ;
import org.springframework.boot.loader.tools.DefaultLaunchScript ;
import org.springframework.boot.loader.tools.DefaultLaunchScript ;
@ -50,6 +45,7 @@ import org.springframework.boot.loader.tools.FileUtils;
* Stores jar files without compression as required by Spring Boot ' s loader .
* Stores jar files without compression as required by Spring Boot ' s loader .
*
*
* @author Andy Wilkinson
* @author Andy Wilkinson
* @author Phillip Webb
* /
* /
class BootZipCopyAction implements CopyAction {
class BootZipCopyAction implements CopyAction {
@ -88,192 +84,155 @@ class BootZipCopyAction implements CopyAction {
@Override
@Override
public WorkResult execute ( CopyActionProcessingStream stream ) {
public WorkResult execute ( CopyActionProcessingStream stream ) {
ZipArchiveOutputStream zipStream ;
Spec < FileTreeElement > loaderEntries ;
try {
try {
FileOutputStream fileStream = new FileOutputStream ( this . output ) ;
writeArchive ( stream ) ;
writeLaunchScriptIfNecessary ( fileStream ) ;
return ( ) - > true ;
zipStream = new ZipArchiveOutputStream ( fileStream ) ;
if ( this . encoding ! = null ) {
zipStream . setEncoding ( this . encoding ) ;
}
loaderEntries = writeLoaderClassesIfNecessary ( zipStream ) ;
}
}
catch ( IOException ex ) {
catch ( IOException ex ) {
throw new GradleException ( "Failed to create " + this . output , ex ) ;
throw new GradleException ( "Failed to create " + this . output , ex ) ;
}
}
}
private void writeArchive ( CopyActionProcessingStream stream ) throws IOException {
OutputStream outputStream = new FileOutputStream ( this . output ) ;
try {
try {
stream . process ( new ZipStreamAction ( zipStream , this . output , this . preserveFileTimestamps , this . requiresUnpack ,
writeLaunchScriptIfNecessary ( outputStream ) ;
createExclusionSpec ( loaderEntries ) , this . compressionResolver ) ) ;
ZipArchiveOutputStream zipOutputStream = new ZipArchiveOutputStream ( outputStream ) ;
}
finally {
try {
try {
zipStream . close ( ) ;
if ( this . encoding ! = null ) {
zipOutputStream . setEncoding ( this . encoding ) ;
}
Processor processor = new Processor ( zipOutputStream ) ;
stream . process ( processor : : process ) ;
processor . finish ( ) ;
}
}
catch ( IOException ex ) {
finally {
// Continue
closeQuietly ( zipOutputStream ) ;
}
}
}
}
return ( ) - > true ;
finally {
}
closeQuietly ( outputStream ) ;
@SuppressWarnings ( "unchecked" )
private Spec < FileTreeElement > createExclusionSpec ( Spec < FileTreeElement > loaderEntries ) {
return Specs . union ( loaderEntries , this . exclusions ) ;
}
private Spec < FileTreeElement > writeLoaderClassesIfNecessary ( ZipArchiveOutputStream out ) {
if ( ! this . includeDefaultLoader ) {
return Specs . satisfyNone ( ) ;
}
}
return writeLoaderClasses ( out ) ;
}
}
private Spec < FileTreeElement > writeLoaderClasses ( ZipArchiveOutputStream out ) {
private void writeLaunchScriptIfNecessary ( OutputStream outputStream ) {
try ( ZipInputStream in = new ZipInputStream (
if ( this . launchScript = = null ) {
getClass ( ) . getResourceAsStream ( "/META-INF/loader/spring-boot-loader.jar" ) ) ) {
return ;
Set < String > entries = new HashSet < > ( ) ;
java . util . zip . ZipEntry entry ;
while ( ( entry = in . getNextEntry ( ) ) ! = null ) {
if ( entry . isDirectory ( ) & & ! entry . getName ( ) . startsWith ( "META-INF/" ) ) {
writeDirectory ( new ZipArchiveEntry ( entry ) , out ) ;
entries . add ( entry . getName ( ) ) ;
}
else if ( entry . getName ( ) . endsWith ( ".class" ) ) {
writeClass ( new ZipArchiveEntry ( entry ) , in , out ) ;
}
}
return ( element ) - > {
String path = element . getRelativePath ( ) . getPathString ( ) ;
if ( element . isDirectory ( ) & & ! path . endsWith ( ( "/" ) ) ) {
path + = "/" ;
}
return entries . contains ( path ) ;
} ;
}
catch ( IOException ex ) {
throw new GradleException ( "Failed to write loader classes" , ex ) ;
}
}
}
try {
File file = this . launchScript . getScript ( ) ;
private void writeDirectory ( ZipArchiveEntry entry , ZipArchiveOutputStream out ) throws IOException {
Map < String , String > properties = this . launchScript . getProperties ( ) ;
prepareEntry ( entry , UnixStat . DIR_FLAG | UnixStat . DEFAULT_DIR_PERM ) ;
outputStream . write ( new DefaultLaunchScript ( file , properties ) . toByteArray ( ) ) ;
out . putArchiveEntry ( entry ) ;
outputStream . flush ( ) ;
out . closeArchiveEntry ( ) ;
this . output . setExecutable ( true ) ;
}
private void writeClass ( ZipArchiveEntry entry , ZipInputStream in , ZipArchiveOutputStream out ) throws IOException {
prepareEntry ( entry , UnixStat . FILE_FLAG | UnixStat . DEFAULT_FILE_PERM ) ;
out . putArchiveEntry ( entry ) ;
byte [ ] buffer = new byte [ 4096 ] ;
int read ;
while ( ( read = in . read ( buffer ) ) > 0 ) {
out . write ( buffer , 0 , read ) ;
}
}
out . closeArchiveEntry ( ) ;
catch ( IOException ex ) {
}
throw new GradleException ( "Failed to write launch script to " + this . output , ex ) ;
private void prepareEntry ( ZipArchiveEntry entry , int unixMode ) {
if ( ! this . preserveFileTimestamps ) {
entry . setTime ( CONSTANT_TIME_FOR_ZIP_ENTRIES ) ;
}
}
entry . setUnixMode ( unixMode ) ;
}
}
private void writeLaunchScriptIfNecessary( FileOutputStream file Stream) {
private void closeQuietly ( OutputStream outputStream ) {
try {
try {
if ( this . launchScript ! = null ) {
outputStream . close ( ) ;
fileStream
. write ( new DefaultLaunchScript ( this . launchScript . getScript ( ) , this . launchScript . getProperties ( ) )
. toByteArray ( ) ) ;
this . output . setExecutable ( true ) ;
}
}
}
catch ( IOException ex ) {
catch ( IOException ex ) {
throw new GradleException ( "Failed to write launch script to " + this . output , ex ) ;
}
}
}
}
private static final class ZipStreamAction implements CopyActionProcessingStreamAction {
/ * *
* Internal process used to copy { @link FileCopyDetails file details } to the zip file .
private final ZipArchiveOutputStream zipStream ;
* /
private class Processor {
private final File output ;
private final boolean preserveFileTimestamps ;
private final Spec < FileTreeElement > requiresUnpack ;
private final Spec < FileTreeElement > exclusions ;
private ZipArchiveOutputStream outputStream ;
private final Function < FileCopyDetails , ZipCompression > compressionType ;
private Spec < FileTreeElement > writtenLoaderEntries ;
private ZipStreamAction ( ZipArchiveOutputStream zipStream , File output , boolean preserveFileTimestamps ,
Processor ( ZipArchiveOutputStream outputStream ) {
Spec < FileTreeElement > requiresUnpack , Spec < FileTreeElement > exclusions ,
this . outputStream = outputStream ;
Function < FileCopyDetails , ZipCompression > compressionType ) {
this . zipStream = zipStream ;
this . output = output ;
this . preserveFileTimestamps = preserveFileTimestamps ;
this . requiresUnpack = requiresUnpack ;
this . exclusions = exclusions ;
this . compressionType = compressionType ;
}
}
@Override
public void process ( FileCopyDetails details ) {
public void processFile ( FileCopyDetailsInternal details ) {
if ( BootZipCopyAction . this . exclusions . isSatisfiedBy ( details )
if ( this . exclusion s. isSatisfiedBy ( details ) ) {
| | ( this . writtenLoaderEntries ! = null & & this . writtenLoaderEntries . isSatisfiedBy ( details ) ) ) {
return ;
return ;
}
}
try {
try {
writeLoaderEntriesIfNecessary ( details ) ;
if ( details . isDirectory ( ) ) {
if ( details . isDirectory ( ) ) {
create Directory( details ) ;
process Directory( details ) ;
}
}
else {
else {
create File( details ) ;
process File( details ) ;
}
}
}
}
catch ( IOException ex ) {
catch ( IOException ex ) {
throw new GradleException ( "Failed to add " + details + " to " + this . output , ex ) ;
throw new GradleException ( "Failed to add " + details + " to " + BootZipCopyAction . this . output , ex ) ;
}
}
public void finish ( ) throws IOException {
writeLoaderEntriesIfNecessary ( null ) ;
}
private void writeLoaderEntriesIfNecessary ( FileCopyDetails details ) throws IOException {
if ( ! BootZipCopyAction . this . includeDefaultLoader | | this . writtenLoaderEntries ! = null ) {
return ;
}
if ( isInMetaInf ( details ) ) {
// Don't write loader entries until after META-INF folder (see gh-16698)
return ;
}
LoaderZipEntries loaderEntries = new LoaderZipEntries (
BootZipCopyAction . this . preserveFileTimestamps ? null : CONSTANT_TIME_FOR_ZIP_ENTRIES ) ;
this . writtenLoaderEntries = loaderEntries . writeTo ( this . outputStream ) ;
}
private boolean isInMetaInf ( FileCopyDetails details ) {
if ( details = = null ) {
return false ;
}
}
String [ ] segments = details . getRelativePath ( ) . getSegments ( ) ;
return segments . length > 0 & & "META-INF" . equals ( segments [ 0 ] ) ;
}
}
private void createDirectory ( FileCopyDetailsInternal details ) throws IOException {
private void processDirectory( FileCopyDetails details ) throws IOException {
ZipArchiveEntry archiveEntry = new ZipArchiveEntry ( details . getRelativePath ( ) . getPathString ( ) + '/' ) ;
ZipArchiveEntry archiveEntry = new ZipArchiveEntry ( details . getRelativePath ( ) . getPathString ( ) + '/' ) ;
archiveEntry . setUnixMode ( UnixStat . DIR_FLAG | details . getMode ( ) ) ;
archiveEntry . setUnixMode ( UnixStat . DIR_FLAG | details . getMode ( ) ) ;
archiveEntry . setTime ( getTime ( details ) ) ;
archiveEntry . setTime ( getTime ( details ) ) ;
this . zipStream . putArchiveEntry ( archiveEntry ) ;
this . output Stream. putArchiveEntry ( archiveEntry ) ;
this . zipStream . closeArchiveEntry ( ) ;
this . output Stream. closeArchiveEntry ( ) ;
}
}
private void createFile ( FileCopyDetailsInternal details ) throws IOException {
private void processFile( FileCopyDetails details ) throws IOException {
String relativePath = details . getRelativePath ( ) . getPathString ( ) ;
String relativePath = details . getRelativePath ( ) . getPathString ( ) ;
ZipArchiveEntry archiveEntry = new ZipArchiveEntry ( relativePath ) ;
ZipArchiveEntry archiveEntry = new ZipArchiveEntry ( relativePath ) ;
archiveEntry . setUnixMode ( UnixStat . FILE_FLAG | details . getMode ( ) ) ;
archiveEntry . setUnixMode ( UnixStat . FILE_FLAG | details . getMode ( ) ) ;
archiveEntry . setTime ( getTime ( details ) ) ;
archiveEntry . setTime ( getTime ( details ) ) ;
ZipCompression compression = this . compressionType . apply ( details ) ;
ZipCompression compression = BootZipCopyAction . this . compressionResolver . apply ( details ) ;
if ( compression = = ZipCompression . STORED ) {
if ( compression = = ZipCompression . STORED ) {
prepareStoredEntry ( details , archiveEntry ) ;
prepareStoredEntry ( details , archiveEntry ) ;
}
}
this . zipStream . putArchiveEntry ( archiveEntry ) ;
this . output Stream. putArchiveEntry ( archiveEntry ) ;
details . copyTo ( this . zipStream ) ;
details . copyTo ( this . output Stream) ;
this . zipStream . closeArchiveEntry ( ) ;
this . output Stream. closeArchiveEntry ( ) ;
}
}
private void prepareStoredEntry ( FileCopyDetailsInternal details , ZipArchiveEntry archiveEntry )
private void prepareStoredEntry ( FileCopyDetails details , ZipArchiveEntry archiveEntry ) throws IOException {
throws IOException {
archiveEntry . setMethod ( java . util . zip . ZipEntry . STORED ) ;
archiveEntry . setMethod ( java . util . zip . ZipEntry . STORED ) ;
archiveEntry . setSize ( details . getSize ( ) ) ;
archiveEntry . setSize ( details . getSize ( ) ) ;
archiveEntry . setCompressedSize ( details . getSize ( ) ) ;
archiveEntry . setCompressedSize ( details . getSize ( ) ) ;
Crc32OutputStream crcStream = new Crc32OutputStream ( ) ;
Crc32OutputStream crcStream = new Crc32OutputStream ( ) ;
details . copyTo ( crcStream ) ;
details . copyTo ( crcStream ) ;
archiveEntry . setCrc ( crcStream . getCrc ( ) ) ;
archiveEntry . setCrc ( crcStream . getCrc ( ) ) ;
if ( this. requiresUnpack . isSatisfiedBy ( details ) ) {
if ( BootZipCopyAction . this. requiresUnpack . isSatisfiedBy ( details ) ) {
archiveEntry . setComment ( "UNPACK:" + FileUtils . sha1Hash ( details . getFile ( ) ) ) ;
archiveEntry . setComment ( "UNPACK:" + FileUtils . sha1Hash ( details . getFile ( ) ) ) ;
}
}
}
}
private long getTime ( FileCopyDetails details ) {
private long getTime ( FileCopyDetails details ) {
return this . preserveFileTimestamps ? details . getLastModified ( ) : CONSTANT_TIME_FOR_ZIP_ENTRIES ;
return BootZipCopyAction . this . preserveFileTimestamps ? details . getLastModified ( )
: CONSTANT_TIME_FOR_ZIP_ENTRIES ;
}
}
}
}
@ -283,25 +242,25 @@ class BootZipCopyAction implements CopyAction {
* /
* /
private static final class Crc32OutputStream extends OutputStream {
private static final class Crc32OutputStream extends OutputStream {
private final CRC32 crc 32 = new CRC32 ( ) ;
private final CRC32 crc = new CRC32 ( ) ;
@Override
@Override
public void write ( int b ) throws IOException {
public void write ( int b ) throws IOException {
this . crc 32 . update ( b ) ;
this . crc . update ( b ) ;
}
}
@Override
@Override
public void write ( byte [ ] b ) throws IOException {
public void write ( byte [ ] b ) throws IOException {
this . crc 32 . update ( b ) ;
this . crc . update ( b ) ;
}
}
@Override
@Override
public void write ( byte [ ] b , int off , int len ) throws IOException {
public void write ( byte [ ] b , int off , int len ) throws IOException {
this . crc 32 . update ( b , off , len ) ;
this . crc . update ( b , off , len ) ;
}
}
private long getCrc ( ) {
private long getCrc ( ) {
return this . crc 32 . getValue ( ) ;
return this . crc . getValue ( ) ;
}
}
}
}