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}