001/* 002 * Copyright 2002-2018 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.web.accept; 018 019import java.util.ArrayList; 020import java.util.Collections; 021import java.util.LinkedList; 022import java.util.List; 023import java.util.Locale; 024import java.util.Map; 025import java.util.concurrent.ConcurrentHashMap; 026import java.util.concurrent.ConcurrentMap; 027 028import org.springframework.http.MediaType; 029import org.springframework.util.LinkedMultiValueMap; 030import org.springframework.util.MultiValueMap; 031 032/** 033 * An implementation of {@code MediaTypeFileExtensionResolver} that maintains 034 * lookups between file extensions and MediaTypes in both directions. 035 * 036 * <p>Initially created with a map of file extensions and media types. 037 * Subsequently subclasses can use {@link #addMapping} to add more mappings. 038 * 039 * @author Rossen Stoyanchev 040 * @since 3.2 041 */ 042public class MappingMediaTypeFileExtensionResolver implements MediaTypeFileExtensionResolver { 043 044 private final ConcurrentMap<String, MediaType> mediaTypes = 045 new ConcurrentHashMap<String, MediaType>(64); 046 047 private final MultiValueMap<MediaType, String> fileExtensions = 048 new LinkedMultiValueMap<MediaType, String>(); 049 050 private final List<String> allFileExtensions = new LinkedList<String>(); 051 052 053 /** 054 * Create an instance with the given map of file extensions and media types. 055 */ 056 public MappingMediaTypeFileExtensionResolver(Map<String, MediaType> mediaTypes) { 057 if (mediaTypes != null) { 058 for (Map.Entry<String, MediaType> entries : mediaTypes.entrySet()) { 059 String extension = entries.getKey().toLowerCase(Locale.ENGLISH); 060 MediaType mediaType = entries.getValue(); 061 this.mediaTypes.put(extension, mediaType); 062 this.fileExtensions.add(mediaType, extension); 063 this.allFileExtensions.add(extension); 064 } 065 } 066 } 067 068 069 public Map<String, MediaType> getMediaTypes() { 070 return this.mediaTypes; 071 } 072 073 protected List<MediaType> getAllMediaTypes() { 074 return new ArrayList<MediaType>(this.mediaTypes.values()); 075 } 076 077 /** 078 * Map an extension to a MediaType. Ignore if extension already mapped. 079 */ 080 protected void addMapping(String extension, MediaType mediaType) { 081 MediaType previous = this.mediaTypes.putIfAbsent(extension, mediaType); 082 if (previous == null) { 083 this.fileExtensions.add(mediaType, extension); 084 this.allFileExtensions.add(extension); 085 } 086 } 087 088 089 @Override 090 public List<String> resolveFileExtensions(MediaType mediaType) { 091 List<String> fileExtensions = this.fileExtensions.get(mediaType); 092 return (fileExtensions != null) ? fileExtensions : Collections.<String>emptyList(); 093 } 094 095 @Override 096 public List<String> getAllFileExtensions() { 097 return Collections.unmodifiableList(this.allFileExtensions); 098 } 099 100 /** 101 * Use this method for a reverse lookup from extension to MediaType. 102 * @return a MediaType for the key, or {@code null} if none found 103 */ 104 protected MediaType lookupMediaType(String extension) { 105 return this.mediaTypes.get(extension.toLowerCase(Locale.ENGLISH)); 106 } 107 108}