001/* 002 * Copyright 2002-2019 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.http; 018 019import java.io.BufferedReader; 020import java.io.IOException; 021import java.io.InputStream; 022import java.io.InputStreamReader; 023import java.nio.charset.StandardCharsets; 024import java.util.Collections; 025import java.util.List; 026import java.util.Locale; 027import java.util.Optional; 028 029import org.springframework.core.io.Resource; 030import org.springframework.lang.Nullable; 031import org.springframework.util.LinkedMultiValueMap; 032import org.springframework.util.MultiValueMap; 033import org.springframework.util.StringUtils; 034 035/** 036 * A factory delegate for resolving {@link MediaType} objects 037 * from {@link Resource} handles or filenames. 038 * 039 * @author Juergen Hoeller 040 * @author Arjen Poutsma 041 * @since 5.0 042 */ 043public final class MediaTypeFactory { 044 045 private static final String MIME_TYPES_FILE_NAME = "/org/springframework/http/mime.types"; 046 047 private static final MultiValueMap<String, MediaType> fileExtensionToMediaTypes = parseMimeTypes(); 048 049 050 private MediaTypeFactory() { 051 } 052 053 054 /** 055 * Parse the {@code mime.types} file found in the resources. Format is: 056 * <code> 057 * # comments begin with a '#'<br> 058 * # the format is <mime type> <space separated file extensions><br> 059 * # for example:<br> 060 * text/plain txt text<br> 061 * # this would map file.txt and file.text to<br> 062 * # the mime type "text/plain"<br> 063 * </code> 064 * @return a multi-value map, mapping media types to file extensions. 065 */ 066 private static MultiValueMap<String, MediaType> parseMimeTypes() { 067 InputStream is = MediaTypeFactory.class.getResourceAsStream(MIME_TYPES_FILE_NAME); 068 try (BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.US_ASCII))) { 069 MultiValueMap<String, MediaType> result = new LinkedMultiValueMap<>(); 070 String line; 071 while ((line = reader.readLine()) != null) { 072 if (line.isEmpty() || line.charAt(0) == '#') { 073 continue; 074 } 075 String[] tokens = StringUtils.tokenizeToStringArray(line, " \t\n\r\f"); 076 MediaType mediaType = MediaType.parseMediaType(tokens[0]); 077 for (int i = 1; i < tokens.length; i++) { 078 String fileExtension = tokens[i].toLowerCase(Locale.ENGLISH); 079 result.add(fileExtension, mediaType); 080 } 081 } 082 return result; 083 } 084 catch (IOException ex) { 085 throw new IllegalStateException("Could not load '" + MIME_TYPES_FILE_NAME + "'", ex); 086 } 087 } 088 089 /** 090 * Determine a media type for the given resource, if possible. 091 * @param resource the resource to introspect 092 * @return the corresponding media type, or {@code null} if none found 093 */ 094 public static Optional<MediaType> getMediaType(@Nullable Resource resource) { 095 return Optional.ofNullable(resource) 096 .map(Resource::getFilename) 097 .flatMap(MediaTypeFactory::getMediaType); 098 } 099 100 /** 101 * Determine a media type for the given file name, if possible. 102 * @param filename the file name plus extension 103 * @return the corresponding media type, or {@code null} if none found 104 */ 105 public static Optional<MediaType> getMediaType(@Nullable String filename) { 106 return getMediaTypes(filename).stream().findFirst(); 107 } 108 109 /** 110 * Determine the media types for the given file name, if possible. 111 * @param filename the file name plus extension 112 * @return the corresponding media types, or an empty list if none found 113 */ 114 public static List<MediaType> getMediaTypes(@Nullable String filename) { 115 return Optional.ofNullable(StringUtils.getFilenameExtension(filename)) 116 .map(s -> s.toLowerCase(Locale.ENGLISH)) 117 .map(fileExtensionToMediaTypes::get) 118 .orElse(Collections.emptyList()); 119 } 120 121}