001/*
002 * Copyright 2002-2020 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.servlet.config.annotation;
018
019import java.util.Arrays;
020import java.util.HashMap;
021import java.util.List;
022import java.util.Map;
023
024import javax.servlet.ServletContext;
025
026import org.springframework.http.MediaType;
027import org.springframework.http.MediaTypeFactory;
028import org.springframework.lang.Nullable;
029import org.springframework.web.accept.ContentNegotiationManager;
030import org.springframework.web.accept.ContentNegotiationManagerFactoryBean;
031import org.springframework.web.accept.ContentNegotiationStrategy;
032import org.springframework.web.accept.FixedContentNegotiationStrategy;
033import org.springframework.web.accept.HeaderContentNegotiationStrategy;
034import org.springframework.web.accept.ParameterContentNegotiationStrategy;
035
036/**
037 * Creates a {@code ContentNegotiationManager} and configures it with
038 * one or more {@link ContentNegotiationStrategy} instances.
039 *
040 * <p>This factory offers properties that in turn result in configuring the
041 * underlying strategies. The table below shows the property names, their
042 * default settings, as well as the strategies that they help to configure:
043 *
044 * <table>
045 * <tr>
046 * <th>Property Setter</th>
047 * <th>Default Value</th>
048 * <th>Underlying Strategy</th>
049 * <th>Enabled Or Not</th>
050 * </tr>
051 * <tr>
052 * <td>{@link #favorPathExtension}</td>
053 * <td>true</td>
054 * <td>{@link org.springframework.web.accept.PathExtensionContentNegotiationStrategy
055 * PathExtensionContentNegotiationStrategy}</td>
056 * <td>Enabled</td>
057 * </tr>
058 * <tr>
059 * <td>{@link #favorParameter}</td>
060 * <td>false</td>
061 * <td>{@link ParameterContentNegotiationStrategy}</td>
062 * <td>Off</td>
063 * </tr>
064 * <tr>
065 * <td>{@link #ignoreAcceptHeader}</td>
066 * <td>false</td>
067 * <td>{@link HeaderContentNegotiationStrategy}</td>
068 * <td>Enabled</td>
069 * </tr>
070 * <tr>
071 * <td>{@link #defaultContentType}</td>
072 * <td>null</td>
073 * <td>{@link FixedContentNegotiationStrategy}</td>
074 * <td>Off</td>
075 * </tr>
076 * <tr>
077 * <td>{@link #defaultContentTypeStrategy}</td>
078 * <td>null</td>
079 * <td>{@link ContentNegotiationStrategy}</td>
080 * <td>Off</td>
081 * </tr>
082 * </table>
083 *
084 * <p>As of 5.0 you can set the exact strategies to use via
085 * {@link #strategies(List)}.
086 *
087 * <p><strong>Note:</strong> if you must use URL-based content type resolution,
088 * the use of a query parameter is simpler and preferable to the use of a path
089 * extension since the latter can cause issues with URI variables, path
090 * parameters, and URI decoding. Consider setting {@link #favorPathExtension}
091 * to {@literal false} or otherwise set the strategies to use explicitly via
092 * {@link #strategies(List)}.
093 *
094 * @author Rossen Stoyanchev
095 * @since 3.2
096 */
097public class ContentNegotiationConfigurer {
098
099        private final ContentNegotiationManagerFactoryBean factory = new ContentNegotiationManagerFactoryBean();
100
101        private final Map<String, MediaType> mediaTypes = new HashMap<>();
102
103
104        /**
105         * Class constructor with {@link javax.servlet.ServletContext}.
106         */
107        public ContentNegotiationConfigurer(@Nullable ServletContext servletContext) {
108                if (servletContext != null) {
109                        this.factory.setServletContext(servletContext);
110                }
111        }
112
113
114        /**
115         * Set the exact list of strategies to use.
116         * <p><strong>Note:</strong> use of this method is mutually exclusive with
117         * use of all other setters in this class which customize a default, fixed
118         * set of strategies. See class level doc for more details.
119         * @param strategies the strategies to use
120         * @since 5.0
121         */
122        public void strategies(@Nullable List<ContentNegotiationStrategy> strategies) {
123                this.factory.setStrategies(strategies);
124        }
125
126        /**
127         * Whether the path extension in the URL path should be used to determine
128         * the requested media type.
129         * <p>By default this is set to {@code true} in which case a request
130         * for {@code /hotels.pdf} will be interpreted as a request for
131         * {@code "application/pdf"} regardless of the 'Accept' header.
132         * @deprecated as of 5.2.4. See class-level note in
133         * {@link ContentNegotiationManagerFactoryBean} on the deprecation of path
134         * extension config options. As there is no replacement for this method,
135         * for the time being it's necessary to continue using it in order to set it
136         * to {@code false}. In 5.3 when {@code false} becomes the default, use of
137         * this property will no longer be necessary.
138         */
139        @Deprecated
140        public ContentNegotiationConfigurer favorPathExtension(boolean favorPathExtension) {
141                this.factory.setFavorPathExtension(favorPathExtension);
142                return this;
143        }
144
145        /**
146         * Add a mapping from a key, extracted from a path extension or a query
147         * parameter, to a MediaType. This is required in order for the parameter
148         * strategy to work. Any extensions explicitly registered here are also
149         * treated as safe for the purpose of Reflected File Download attack
150         * detection (see Spring Framework reference documentation for more details
151         * on RFD attack protection).
152         * <p>The path extension strategy will also try to use
153         * {@link ServletContext#getMimeType} and {@link MediaTypeFactory} to resolve path
154         * extensions. To change this behavior see the {@link #useRegisteredExtensionsOnly} property.
155         * @param extension the key to look up
156         * @param mediaType the media type
157         * @see #mediaTypes(Map)
158         * @see #replaceMediaTypes(Map)
159         */
160        public ContentNegotiationConfigurer mediaType(String extension, MediaType mediaType) {
161                this.mediaTypes.put(extension, mediaType);
162                return this;
163        }
164
165        /**
166         * An alternative to {@link #mediaType}.
167         * @see #mediaType(String, MediaType)
168         * @see #replaceMediaTypes(Map)
169         */
170        public ContentNegotiationConfigurer mediaTypes(@Nullable Map<String, MediaType> mediaTypes) {
171                if (mediaTypes != null) {
172                        this.mediaTypes.putAll(mediaTypes);
173                }
174                return this;
175        }
176
177        /**
178         * Similar to {@link #mediaType} but for replacing existing mappings.
179         * @see #mediaType(String, MediaType)
180         * @see #mediaTypes(Map)
181         */
182        public ContentNegotiationConfigurer replaceMediaTypes(Map<String, MediaType> mediaTypes) {
183                this.mediaTypes.clear();
184                mediaTypes(mediaTypes);
185                return this;
186        }
187
188        /**
189         * Whether to ignore requests with path extension that cannot be resolved
190         * to any media type. Setting this to {@code false} will result in an
191         * {@code HttpMediaTypeNotAcceptableException} if there is no match.
192         * <p>By default this is set to {@code true}.
193         * @deprecated as of 5.2.4. See class-level note in
194         * {@link ContentNegotiationManagerFactoryBean} on the deprecation of path
195         * extension config options.
196         */
197        @Deprecated
198        public ContentNegotiationConfigurer ignoreUnknownPathExtensions(boolean ignore) {
199                this.factory.setIgnoreUnknownPathExtensions(ignore);
200                return this;
201        }
202
203        /**
204         * When {@link #favorPathExtension} is set, this property determines whether
205         * to allow use of JAF (Java Activation Framework) to resolve a path
206         * extension to a specific MediaType.
207         * @deprecated as of 5.0, in favor of {@link #useRegisteredExtensionsOnly(boolean)}
208         * which has reverse behavior
209         */
210        @Deprecated
211        public ContentNegotiationConfigurer useJaf(boolean useJaf) {
212                return this.useRegisteredExtensionsOnly(!useJaf);
213        }
214
215        /**
216         * When {@link #favorPathExtension favorPathExtension} is set, this
217         * property determines whether to use only registered {@code MediaType} mappings
218         * to resolve a path extension to a specific MediaType.
219         * <p>By default this is not set in which case
220         * {@code PathExtensionContentNegotiationStrategy} will use defaults if available.
221         */
222        public ContentNegotiationConfigurer useRegisteredExtensionsOnly(boolean useRegisteredExtensionsOnly) {
223                this.factory.setUseRegisteredExtensionsOnly(useRegisteredExtensionsOnly);
224                return this;
225        }
226
227        /**
228         * Whether a request parameter ("format" by default) should be used to
229         * determine the requested media type. For this option to work you must
230         * register {@link #mediaType(String, MediaType) media type mappings}.
231         * <p>By default this is set to {@code false}.
232         * @see #parameterName(String)
233         */
234        public ContentNegotiationConfigurer favorParameter(boolean favorParameter) {
235                this.factory.setFavorParameter(favorParameter);
236                return this;
237        }
238
239        /**
240         * Set the query parameter name to use when {@link #favorParameter} is on.
241         * <p>The default parameter name is {@code "format"}.
242         */
243        public ContentNegotiationConfigurer parameterName(String parameterName) {
244                this.factory.setParameterName(parameterName);
245                return this;
246        }
247
248        /**
249         * Whether to disable checking the 'Accept' request header.
250         * <p>By default this value is set to {@code false}.
251         */
252        public ContentNegotiationConfigurer ignoreAcceptHeader(boolean ignoreAcceptHeader) {
253                this.factory.setIgnoreAcceptHeader(ignoreAcceptHeader);
254                return this;
255        }
256
257        /**
258         * Set the default content type(s) to use when no content type is requested
259         * in order of priority.
260         * <p>If destinations are present that do not support any of the given media
261         * types, consider appending {@link MediaType#ALL} at the end.
262         * <p>By default this is not set.
263         * @see #defaultContentTypeStrategy
264         */
265        public ContentNegotiationConfigurer defaultContentType(MediaType... defaultContentTypes) {
266                this.factory.setDefaultContentTypes(Arrays.asList(defaultContentTypes));
267                return this;
268        }
269
270        /**
271         * Set a custom {@link ContentNegotiationStrategy} to use to determine
272         * the content type to use when no content type is requested.
273         * <p>By default this is not set.
274         * @since 4.1.2
275         * @see #defaultContentType
276         */
277        public ContentNegotiationConfigurer defaultContentTypeStrategy(ContentNegotiationStrategy defaultStrategy) {
278                this.factory.setDefaultContentTypeStrategy(defaultStrategy);
279                return this;
280        }
281
282
283        /**
284         * Build a {@link ContentNegotiationManager} based on this configurer's settings.
285         * @since 4.3.12
286         * @see ContentNegotiationManagerFactoryBean#getObject()
287         */
288        protected ContentNegotiationManager buildContentNegotiationManager() {
289                this.factory.addMediaTypes(this.mediaTypes);
290                return this.factory.build();
291        }
292
293}