@ -21,9 +21,13 @@ import java.util.Collection;
import java.util.Collections ;
import java.util.Iterator ;
import java.util.LinkedHashMap ;
import java.util.Locale ;
import java.util.Map ;
import java.util.Properties ;
import java.util.Map.Entry ;
import java.util.concurrent.atomic.AtomicBoolean ;
import org.springframework.aot.hint.RuntimeHints ;
import org.springframework.aot.hint.RuntimeHintsRegistrar ;
import org.springframework.core.io.ClassPathResource ;
import org.springframework.core.io.support.PropertiesLoaderUtils ;
import org.springframework.util.Assert ;
@ -33,29 +37,15 @@ import org.springframework.util.Assert;
* { @literal & lt ; mime - mapping & gt ; } element traditionally found in web . xml .
*
* @author Phillip Webb
* @author Guirong Hu
* @since 2.0 .0
* /
public final class MimeMappings implements Iterable < MimeMappings . Mapping > {
public sealed class MimeMappings implements Iterable < MimeMappings . Mapping > {
/ * *
* Default mime mapping commonly used .
* /
public static final MimeMappings DEFAULT ;
static {
MimeMappings mappings = new MimeMappings ( ) ;
try {
Properties defaultMimeMappings = PropertiesLoaderUtils
. loadProperties ( new ClassPathResource ( "mime-mappings.properties" , MimeMappings . class ) ) ;
for ( String extension : defaultMimeMappings . stringPropertyNames ( ) ) {
mappings . add ( extension , defaultMimeMappings . getProperty ( extension ) ) ;
}
}
catch ( IOException ex ) {
throw new IllegalArgumentException ( "Unable to load the default MIME types" , ex ) ;
}
DEFAULT = unmodifiableMappings ( mappings ) ;
}
public static final MimeMappings DEFAULT = new DefaultMimeMappings ( ) ;
private final Map < String , Mapping > map ;
@ -90,24 +80,11 @@ public final class MimeMappings implements Iterable<MimeMappings.Mapping> {
* @param mappings source mappings
* @param mutable if the new object should be mutable .
* /
private MimeMappings( MimeMappings mappings , boolean mutable ) {
MimeMappings( MimeMappings mappings , boolean mutable ) {
Assert . notNull ( mappings , "Mappings must not be null" ) ;
this . map = ( mutable ? new LinkedHashMap < > ( mappings . map ) : Collections . unmodifiableMap ( mappings . map ) ) ;
}
@Override
public Iterator < Mapping > iterator ( ) {
return getAll ( ) . iterator ( ) ;
}
/ * *
* Returns all defined mappings .
* @return the mappings .
* /
public Collection < Mapping > getAll ( ) {
return this . map . values ( ) ;
}
/ * *
* Add a new mime mapping .
* @param extension the file extension ( excluding '.' )
@ -115,7 +92,20 @@ public final class MimeMappings implements Iterable<MimeMappings.Mapping> {
* @return any previous mapping or { @code null }
* /
public String add ( String extension , String mimeType ) {
Mapping previous = this . map . put ( extension , new Mapping ( extension , mimeType ) ) ;
Assert . notNull ( extension , "Extension must not be null" ) ;
Assert . notNull ( mimeType , "MimeType must not be null" ) ;
Mapping previous = this . map . put ( extension . toLowerCase ( Locale . ENGLISH ) , new Mapping ( extension , mimeType ) ) ;
return ( previous ! = null ) ? previous . getMimeType ( ) : null ;
}
/ * *
* Remove an existing mapping .
* @param extension the file extension ( excluding '.' )
* @return the removed mime mapping or { @code null } if no item was removed
* /
public String remove ( String extension ) {
Assert . notNull ( extension , "Extension must not be null" ) ;
Mapping previous = this . map . remove ( extension . toLowerCase ( Locale . ENGLISH ) ) ;
return ( previous ! = null ) ? previous . getMimeType ( ) : null ;
}
@ -125,18 +115,22 @@ public final class MimeMappings implements Iterable<MimeMappings.Mapping> {
* @return a mime mapping or { @code null }
* /
public String get ( String extension ) {
Mapping mapping = this . map . get ( extension ) ;
Assert . notNull ( extension , "Extension must not be null" ) ;
Mapping mapping = this . map . get ( extension . toLowerCase ( Locale . ENGLISH ) ) ;
return ( mapping ! = null ) ? mapping . getMimeType ( ) : null ;
}
/ * *
* Remove an existing mapping .
* @param extension the file extension ( excluding '.' )
* @return the removed mime mapping or { @code null } if no item was removed
* Returns all defined mappings .
* @return the mappings .
* /
public String remove ( String extension ) {
Mapping previous = this . map . remove ( extension ) ;
return ( previous ! = null ) ? previous . getMimeType ( ) : null ;
public Collection < Mapping > getAll ( ) {
return this . map . values ( ) ;
}
@Override
public final Iterator < Mapping > iterator ( ) {
return getAll ( ) . iterator ( ) ;
}
@Override
@ -148,14 +142,18 @@ public final class MimeMappings implements Iterable<MimeMappings.Mapping> {
return true ;
}
if ( obj instanceof MimeMappings other ) {
return this . map . equals ( other . map ) ;
return getMap ( ) . equals ( other . map ) ;
}
return false ;
}
@Override
public int hashCode ( ) {
return this . map . hashCode ( ) ;
return getMap ( ) . hashCode ( ) ;
}
Map < String , Mapping > getMap ( ) {
return this . map ;
}
/ * *
@ -165,9 +163,22 @@ public final class MimeMappings implements Iterable<MimeMappings.Mapping> {
* @return an unmodifiable view of the specified mappings .
* /
public static MimeMappings unmodifiableMappings ( MimeMappings mappings ) {
Assert . notNull ( mappings , "Mappings must not be null" ) ;
return new MimeMappings ( mappings , false ) ;
}
/ * *
* Crate a new lazy copy of the given mappings will only copy entries if the mappings
* are mutated .
* @param mappings the source mappings
* @return a new mappings instance
* @since 3.0 .0
* /
public static MimeMappings lazyCopy ( MimeMappings mappings ) {
Assert . notNull ( mappings , "Mappings must not be null" ) ;
return new LazyMimeMappingsCopy ( mappings ) ;
}
/ * *
* A single mime mapping .
* /
@ -218,4 +229,175 @@ public final class MimeMappings implements Iterable<MimeMappings.Mapping> {
}
/ * *
* { @link MimeMappings } implementation used for { @link MimeMappings # DEFAULT } . Provides
* in - memory access for common mappings and lazily loads the complete set when
* necessary .
* /
static final class DefaultMimeMappings extends MimeMappings {
static final String MIME_MAPPINGS_PROPERTIES = "mime-mappings.properties" ;
private static final MimeMappings COMMON ;
static {
MimeMappings mappings = new MimeMappings ( ) ;
mappings . add ( "avi" , "video/x-msvideo" ) ;
mappings . add ( "bin" , "application/octet-stream" ) ;
mappings . add ( "body" , "text/html" ) ;
mappings . add ( "class" , "application/java" ) ;
mappings . add ( "css" , "text/css" ) ;
mappings . add ( "dtd" , "application/xml-dtd" ) ;
mappings . add ( "gif" , "image/gif" ) ;
mappings . add ( "gtar" , "application/x-gtar" ) ;
mappings . add ( "gz" , "application/x-gzip" ) ;
mappings . add ( "htm" , "text/html" ) ;
mappings . add ( "html" , "text/html" ) ;
mappings . add ( "jar" , "application/java-archive" ) ;
mappings . add ( "java" , "text/x-java-source" ) ;
mappings . add ( "jnlp" , "application/x-java-jnlp-file" ) ;
mappings . add ( "jpe" , "image/jpeg" ) ;
mappings . add ( "jpeg" , "image/jpeg" ) ;
mappings . add ( "jpg" , "image/jpeg" ) ;
mappings . add ( "js" , "application/javascript" ) ;
mappings . add ( "json" , "application/json" ) ;
mappings . add ( "otf" , "application/x-font-opentype" ) ;
mappings . add ( "pdf" , "application/pdf" ) ;
mappings . add ( "png" , "image/png" ) ;
mappings . add ( "ps" , "application/postscript" ) ;
mappings . add ( "tar" , "application/x-tar" ) ;
mappings . add ( "tif" , "image/tiff" ) ;
mappings . add ( "tiff" , "image/tiff" ) ;
mappings . add ( "ttf" , "application/x-font-ttf" ) ;
mappings . add ( "txt" , "text/plain" ) ;
mappings . add ( "xht" , "application/xhtml+xml" ) ;
mappings . add ( "xhtml" , "application/xhtml+xml" ) ;
mappings . add ( "xls" , "application/vnd.ms-excel" ) ;
mappings . add ( "xml" , "application/xml" ) ;
mappings . add ( "xsl" , "application/xml" ) ;
mappings . add ( "xslt" , "application/xslt+xml" ) ;
mappings . add ( "wasm" , "application/wasm" ) ;
mappings . add ( "zip" , "application/zip" ) ;
COMMON = unmodifiableMappings ( mappings ) ;
}
private volatile Map < String , Mapping > loaded ;
DefaultMimeMappings ( ) {
super ( new MimeMappings ( ) , false ) ;
}
@Override
public Collection < Mapping > getAll ( ) {
return load ( ) . values ( ) ;
}
@Override
public String get ( String extension ) {
Assert . notNull ( extension , "Extension must not be null" ) ;
extension = extension . toLowerCase ( Locale . ENGLISH ) ;
Map < String , Mapping > loaded = this . loaded ;
if ( loaded ! = null ) {
return get ( loaded , extension ) ;
}
String commonMimeType = COMMON . get ( extension ) ;
if ( commonMimeType ! = null ) {
return commonMimeType ;
}
loaded = load ( ) ;
return get ( loaded , extension ) ;
}
private String get ( Map < String , Mapping > mappings , String extension ) {
Mapping mapping = mappings . get ( extension ) ;
return ( mapping ! = null ) ? mapping . getMimeType ( ) : null ;
}
@Override
Map < String , Mapping > getMap ( ) {
return load ( ) ;
}
private Map < String , Mapping > load ( ) {
Map < String , Mapping > loaded = this . loaded ;
if ( loaded ! = null ) {
return loaded ;
}
try {
loaded = new LinkedHashMap < > ( ) ;
for ( Entry < ? , ? > entry : PropertiesLoaderUtils
. loadProperties ( new ClassPathResource ( MIME_MAPPINGS_PROPERTIES , getClass ( ) ) ) . entrySet ( ) ) {
loaded . put ( ( String ) entry . getKey ( ) ,
new Mapping ( ( String ) entry . getKey ( ) , ( String ) entry . getValue ( ) ) ) ;
}
loaded = Collections . unmodifiableMap ( loaded ) ;
this . loaded = loaded ;
return loaded ;
}
catch ( IOException ex ) {
throw new IllegalArgumentException ( "Unable to load the default MIME types" , ex ) ;
}
}
}
/ * *
* { @link MimeMappings } implementation used to create a lazy copy only when the
* mappings are mutated .
* /
static final class LazyMimeMappingsCopy extends MimeMappings {
private final MimeMappings source ;
private AtomicBoolean copied = new AtomicBoolean ( ) ;
LazyMimeMappingsCopy ( MimeMappings source ) {
this . source = source ;
}
@Override
public String add ( String extension , String mimeType ) {
copyIfNecessary ( ) ;
return super . add ( extension , mimeType ) ;
}
@Override
public String remove ( String extension ) {
copyIfNecessary ( ) ;
return super . remove ( extension ) ;
}
private void copyIfNecessary ( ) {
if ( this . copied . compareAndSet ( false , true ) ) {
this . source . forEach ( ( mapping ) - > add ( mapping . getExtension ( ) , mapping . getMimeType ( ) ) ) ;
}
}
@Override
public String get ( String extension ) {
return ! this . copied . get ( ) ? this . source . get ( extension ) : super . get ( extension ) ;
}
@Override
public Collection < Mapping > getAll ( ) {
return ! this . copied . get ( ) ? this . source . getAll ( ) : super . getAll ( ) ;
}
@Override
Map < String , Mapping > getMap ( ) {
return ! this . copied . get ( ) ? this . source . getMap ( ) : super . getMap ( ) ;
}
}
static class MimeMappingsRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints ( RuntimeHints hints , ClassLoader classLoader ) {
hints . resources ( ) . registerPattern (
"org/springframework/boot/web/server/" + DefaultMimeMappings . MIME_MAPPINGS_PROPERTIES ) ;
}
}
}