@ -18,6 +18,7 @@ package org.springframework.boot.loader.tools;
import java.io.BufferedInputStream ;
import java.io.ByteArrayInputStream ;
import java.io.ByteArrayOutputStream ;
import java.io.File ;
import java.io.FileInputStream ;
import java.io.FileNotFoundException ;
@ -54,6 +55,8 @@ import org.apache.commons.compress.archivers.zip.UnixStat;
* /
public class JarWriter implements LoaderClassesWriter , AutoCloseable {
private static final UnpackHandler NEVER_UNPACK = new NeverUnpackHandler ( ) ;
private static final String NESTED_LOADER_JAR = "META-INF/loader/spring-boot-loader.jar" ;
private static final int BUFFER_SIZE = 32 * 1024 ;
@ -119,11 +122,15 @@ public class JarWriter implements LoaderClassesWriter, AutoCloseable {
* @throws IOException if the entries cannot be written
* /
public void writeEntries ( JarFile jarFile ) throws IOException {
this . writeEntries ( jarFile , new IdentityEntryTransformer ( ) );
this . writeEntries ( jarFile , new IdentityEntryTransformer ( ) , NEVER_UNPACK );
}
void writeEntries ( JarFile jarFile , EntryTransformer entryTransformer )
throws IOException {
void writeEntries ( JarFile jarFile , UnpackHandler unpackHandler ) throws IOException {
this . writeEntries ( jarFile , new IdentityEntryTransformer ( ) , unpackHandler ) ;
}
void writeEntries ( JarFile jarFile , EntryTransformer entryTransformer ,
UnpackHandler unpackHandler ) throws IOException {
Enumeration < JarEntry > entries = jarFile . entries ( ) ;
while ( entries . hasMoreElements ( ) ) {
JarArchiveEntry entry = new JarArchiveEntry ( entries . nextElement ( ) ) ;
@ -133,7 +140,7 @@ public class JarWriter implements LoaderClassesWriter, AutoCloseable {
EntryWriter entryWriter = new InputStreamEntryWriter ( inputStream , true ) ;
JarArchiveEntry transformedEntry = entryTransformer . transform ( entry ) ;
if ( transformedEntry ! = null ) {
writeEntry ( transformedEntry , entryWriter );
writeEntry ( transformedEntry , entryWriter , unpackHandler );
}
}
}
@ -172,11 +179,9 @@ public class JarWriter implements LoaderClassesWriter, AutoCloseable {
File file = library . getFile ( ) ;
JarArchiveEntry entry = new JarArchiveEntry ( destination + library . getName ( ) ) ;
entry . setTime ( getNestedLibraryTime ( file ) ) ;
if ( library . isUnpackRequired ( ) ) {
entry . setComment ( "UNPACK:" + FileUtils . sha1Hash ( file ) ) ;
}
new CrcAndSize ( file ) . setupStoredEntry ( entry ) ;
writeEntry ( entry , new InputStreamEntryWriter ( new FileInputStream ( file ) , true ) ) ;
writeEntry ( entry , new InputStreamEntryWriter ( new FileInputStream ( file ) , true ) ,
new LibraryUnpackHandler ( library ) ) ;
}
private long getNestedLibraryTime ( File file ) {
@ -236,15 +241,21 @@ public class JarWriter implements LoaderClassesWriter, AutoCloseable {
this . jarOutput . close ( ) ;
}
private void writeEntry ( JarArchiveEntry entry , EntryWriter entryWriter )
throws IOException {
writeEntry ( entry , entryWriter , NEVER_UNPACK ) ;
}
/ * *
* Perform the actual write of a { @link JarEntry } . All other { @code write } method
* Perform the actual write of a { @link JarEntry } . All other { @code write } method s
* delegate to this one .
* @param entry the entry to write
* @param entryWriter the entry writer or { @code null } if there is no content
* @param unpackHandler handles possible unpacking for the entry
* @throws IOException in case of I / O errors
* /
private void writeEntry ( JarArchiveEntry entry , EntryWriter entryWriter )
throws IOException {
private void writeEntry ( JarArchiveEntry entry , EntryWriter entryWriter ,
UnpackHandler unpackHandler ) throws IOException {
String parent = entry . getName ( ) ;
if ( parent . endsWith ( "/" ) ) {
parent = parent . substring ( 0 , parent . length ( ) - 1 ) ;
@ -256,11 +267,12 @@ public class JarWriter implements LoaderClassesWriter, AutoCloseable {
if ( parent . lastIndexOf ( '/' ) ! = - 1 ) {
parent = parent . substring ( 0 , parent . lastIndexOf ( '/' ) + 1 ) ;
if ( ! parent . isEmpty ( ) ) {
writeEntry ( new JarArchiveEntry ( parent ) , null );
writeEntry ( new JarArchiveEntry ( parent ) , null , unpackHandler );
}
}
if ( this . writtenEntries . add ( entry . getName ( ) ) ) {
entryWriter = addUnpackCommentIfNecessary ( entry , entryWriter , unpackHandler ) ;
this . jarOutput . putArchiveEntry ( entry ) ;
if ( entryWriter ! = null ) {
entryWriter . write ( this . jarOutput ) ;
@ -269,6 +281,18 @@ public class JarWriter implements LoaderClassesWriter, AutoCloseable {
}
}
private EntryWriter addUnpackCommentIfNecessary ( JarArchiveEntry entry ,
EntryWriter entryWriter , UnpackHandler unpackHandler ) throws IOException {
if ( entryWriter = = null | | ! unpackHandler . requiresUnpack ( entry . getName ( ) ) ) {
return entryWriter ;
}
ByteArrayOutputStream output = new ByteArrayOutputStream ( ) ;
entryWriter . write ( output ) ;
entry . setComment ( "UNPACK:" + unpackHandler . sha1Hash ( entry . getName ( ) ) ) ;
return new InputStreamEntryWriter ( new ByteArrayInputStream ( output . toByteArray ( ) ) ,
true ) ;
}
/ * *
* Interface used to write jar entry date .
* /
@ -421,4 +445,50 @@ public class JarWriter implements LoaderClassesWriter, AutoCloseable {
}
/ * *
* An { @code UnpackHandler } determines whether or not unpacking is required and
* provides a SHA1 hash if required .
* /
interface UnpackHandler {
boolean requiresUnpack ( String name ) ;
String sha1Hash ( String name ) throws IOException ;
}
private static final class NeverUnpackHandler implements UnpackHandler {
@Override
public boolean requiresUnpack ( String name ) {
return false ;
}
@Override
public String sha1Hash ( String name ) {
throw new UnsupportedOperationException ( ) ;
}
}
private static final class LibraryUnpackHandler implements UnpackHandler {
private final Library library ;
private LibraryUnpackHandler ( Library library ) {
this . library = library ;
}
@Override
public boolean requiresUnpack ( String name ) {
return this . library . isUnpackRequired ( ) ;
}
@Override
public String sha1Hash ( String name ) throws IOException {
return FileUtils . sha1Hash ( this . library . getFile ( ) ) ;
}
}
}