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.Collections;
020import java.util.List;
021import java.util.Map;
022import java.util.Optional;
023
024import org.apache.commons.logging.Log;
025import org.apache.commons.logging.LogFactory;
026
027import org.springframework.http.MediaType;
028import org.springframework.http.MediaTypeFactory;
029import org.springframework.lang.Nullable;
030import org.springframework.util.StringUtils;
031import org.springframework.web.HttpMediaTypeNotAcceptableException;
032import org.springframework.web.context.request.NativeWebRequest;
033
034/**
035 * Base class for {@code ContentNegotiationStrategy} implementations with the
036 * steps to resolve a request to media types.
037 *
038 * <p>First a key (e.g. "json", "pdf") must be extracted from the request (e.g.
039 * file extension, query param). The key must then be resolved to media type(s)
040 * through the base class {@link MappingMediaTypeFileExtensionResolver} which
041 * stores such mappings.
042 *
043 * <p>The method {@link #handleNoMatch} allow sub-classes to plug in additional
044 * ways of looking up media types (e.g. through the Java Activation framework,
045 * or {@link javax.servlet.ServletContext#getMimeType}. Media types resolved
046 * via base classes are then added to the base class
047 * {@link MappingMediaTypeFileExtensionResolver}, i.e. cached for new lookups.
048 *
049 * @author Rossen Stoyanchev
050 * @since 3.2
051 */
052public abstract class AbstractMappingContentNegotiationStrategy extends MappingMediaTypeFileExtensionResolver
053                implements ContentNegotiationStrategy {
054
055        protected final Log logger = LogFactory.getLog(getClass());
056
057        private boolean useRegisteredExtensionsOnly = false;
058
059        private boolean ignoreUnknownExtensions = false;
060
061
062        /**
063         * Create an instance with the given map of file extensions and media types.
064         */
065        public AbstractMappingContentNegotiationStrategy(@Nullable Map<String, MediaType> mediaTypes) {
066                super(mediaTypes);
067        }
068
069
070        /**
071         * Whether to only use the registered mappings to look up file extensions,
072         * or also to use dynamic resolution (e.g. via {@link MediaTypeFactory}.
073         * <p>By default this is set to {@code false}.
074         */
075        public void setUseRegisteredExtensionsOnly(boolean useRegisteredExtensionsOnly) {
076                this.useRegisteredExtensionsOnly = useRegisteredExtensionsOnly;
077        }
078
079        public boolean isUseRegisteredExtensionsOnly() {
080                return this.useRegisteredExtensionsOnly;
081        }
082
083        /**
084         * Whether to ignore requests with unknown file extension. Setting this to
085         * {@code false} results in {@code HttpMediaTypeNotAcceptableException}.
086         * <p>By default this is set to {@literal false} but is overridden in
087         * {@link PathExtensionContentNegotiationStrategy} to {@literal true}.
088         */
089        public void setIgnoreUnknownExtensions(boolean ignoreUnknownExtensions) {
090                this.ignoreUnknownExtensions = ignoreUnknownExtensions;
091        }
092
093        public boolean isIgnoreUnknownExtensions() {
094                return this.ignoreUnknownExtensions;
095        }
096
097
098        @Override
099        public List<MediaType> resolveMediaTypes(NativeWebRequest webRequest)
100                        throws HttpMediaTypeNotAcceptableException {
101
102                return resolveMediaTypeKey(webRequest, getMediaTypeKey(webRequest));
103        }
104
105        /**
106         * An alternative to {@link #resolveMediaTypes(NativeWebRequest)} that accepts
107         * an already extracted key.
108         * @since 3.2.16
109         */
110        public List<MediaType> resolveMediaTypeKey(NativeWebRequest webRequest, @Nullable String key)
111                        throws HttpMediaTypeNotAcceptableException {
112
113                if (StringUtils.hasText(key)) {
114                        MediaType mediaType = lookupMediaType(key);
115                        if (mediaType != null) {
116                                handleMatch(key, mediaType);
117                                return Collections.singletonList(mediaType);
118                        }
119                        mediaType = handleNoMatch(webRequest, key);
120                        if (mediaType != null) {
121                                addMapping(key, mediaType);
122                                return Collections.singletonList(mediaType);
123                        }
124                }
125                return MEDIA_TYPE_ALL_LIST;
126        }
127
128
129        /**
130         * Extract a key from the request to use to look up media types.
131         * @return the lookup key, or {@code null} if none
132         */
133        @Nullable
134        protected abstract String getMediaTypeKey(NativeWebRequest request);
135
136        /**
137         * Override to provide handling when a key is successfully resolved via
138         * {@link #lookupMediaType}.
139         */
140        protected void handleMatch(String key, MediaType mediaType) {
141        }
142
143        /**
144         * Override to provide handling when a key is not resolved via.
145         * {@link #lookupMediaType}. Sub-classes can take further steps to
146         * determine the media type(s). If a MediaType is returned from
147         * this method it will be added to the cache in the base class.
148         */
149        @Nullable
150        protected MediaType handleNoMatch(NativeWebRequest request, String key)
151                        throws HttpMediaTypeNotAcceptableException {
152
153                if (!isUseRegisteredExtensionsOnly()) {
154                        Optional<MediaType> mediaType = MediaTypeFactory.getMediaType("file." + key);
155                        if (mediaType.isPresent()) {
156                                return mediaType.get();
157                        }
158                }
159                if (isIgnoreUnknownExtensions()) {
160                        return null;
161                }
162                throw new HttpMediaTypeNotAcceptableException(getAllMediaTypes());
163        }
164
165}