001/*
002 * Copyright 2002-2017 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.HashMap;
020import java.util.Map;
021import javax.servlet.ServletContext;
022
023import org.springframework.http.MediaType;
024import org.springframework.web.accept.ContentNegotiationManager;
025import org.springframework.web.accept.ContentNegotiationManagerFactoryBean;
026import org.springframework.web.accept.ContentNegotiationStrategy;
027import org.springframework.web.accept.FixedContentNegotiationStrategy;
028import org.springframework.web.accept.HeaderContentNegotiationStrategy;
029import org.springframework.web.accept.ParameterContentNegotiationStrategy;
030import org.springframework.web.accept.PathExtensionContentNegotiationStrategy;
031
032/**
033 * Creates a {@code ContentNegotiationManager} and configures it with
034 * one or more {@link ContentNegotiationStrategy} instances.
035 *
036 * <p>As an alternative you can also rely on the set of defaults described below
037 * which can be turned on or off or customized through the methods of this
038 * builder:
039 *
040 * <table>
041 * <tr>
042 * <th>Configurer Property</th>
043 * <th>Underlying Strategy</th>
044 * <th>Default Setting</th>
045 * </tr>
046 * <tr>
047 * <td>{@link #favorPathExtension}</td>
048 * <td>{@link PathExtensionContentNegotiationStrategy Path Extension strategy}</td>
049 * <td>On</td>
050 * </tr>
051 * <tr>
052 * <td>{@link #favorParameter}</td>
053 * <td>{@link ParameterContentNegotiationStrategy Parameter strategy}</td>
054 * <td>Off</td>
055 * </tr>
056 * <tr>
057 * <td>{@link #ignoreAcceptHeader}</td>
058 * <td>{@link HeaderContentNegotiationStrategy Header strategy}</td>
059 * <td>On</td>
060 * </tr>
061 * <tr>
062 * <td>{@link #defaultContentType}</td>
063 * <td>{@link FixedContentNegotiationStrategy Fixed content strategy}</td>
064 * <td>Not set</td>
065 * </tr>
066 * <tr>
067 * <td>{@link #defaultContentTypeStrategy}</td>
068 * <td>{@link ContentNegotiationStrategy}</td>
069 * <td>Not set</td>
070 * </tr>
071 * </table>
072 *
073 * <p>The order in which strategies are configured is fixed. You can only turn
074 * them on or off.
075 *
076 * <p>For the path extension and parameter strategies you may explicitly add
077 * {@link #mediaType MediaType mappings}. Those will be used to resolve path
078 * extensions and/or a query parameter value such as "json" to a concrete media
079 * type such as "application/json".
080 *
081 * <p>The path extension strategy will also use {@link ServletContext#getMimeType}
082 * and the Java Activation framework (JAF), if available, to resolve a path
083 * extension to a MediaType. You may however {@link #useJaf suppress} the use
084 * of JAF.
085 *
086 * @author Rossen Stoyanchev
087 * @author Brian Clozel
088 * @author Juergen Hoeller
089 * @since 3.2
090 * @see ContentNegotiationManagerFactoryBean
091 */
092public class ContentNegotiationConfigurer {
093
094        private final ContentNegotiationManagerFactoryBean factory = new ContentNegotiationManagerFactoryBean();
095
096        private final Map<String, MediaType> mediaTypes = new HashMap<String, MediaType>();
097
098
099        /**
100         * Class constructor with {@link javax.servlet.ServletContext}.
101         */
102        public ContentNegotiationConfigurer(ServletContext servletContext) {
103                this.factory.setServletContext(servletContext);
104        }
105
106
107        /**
108         * Whether the path extension in the URL path should be used to determine
109         * the requested media type.
110         * <p>By default this is set to {@code true} in which case a request
111         * for {@code /hotels.pdf} will be interpreted as a request for
112         * {@code "application/pdf"} regardless of the 'Accept' header.
113         */
114        public ContentNegotiationConfigurer favorPathExtension(boolean favorPathExtension) {
115                this.factory.setFavorPathExtension(favorPathExtension);
116                return this;
117        }
118
119        /**
120         * Add a mapping from a key, extracted from a path extension or a query
121         * parameter, to a MediaType. This is required in order for the parameter
122         * strategy to work. Any extensions explicitly registered here are also
123         * whitelisted for the purpose of Reflected File Download attack detection
124         * (see Spring Framework reference documentation for more details on RFD
125         * attack protection).
126         * <p>The path extension strategy will also try to use
127         * {@link ServletContext#getMimeType} and JAF (if present) to resolve path
128         * extensions. To change this behavior see the {@link #useJaf} property.
129         * @param extension the key to look up
130         * @param mediaType the media type
131         * @see #mediaTypes(Map)
132         * @see #replaceMediaTypes(Map)
133         */
134        public ContentNegotiationConfigurer mediaType(String extension, MediaType mediaType) {
135                this.mediaTypes.put(extension, mediaType);
136                return this;
137        }
138
139        /**
140         * An alternative to {@link #mediaType}.
141         * @see #mediaType(String, MediaType)
142         * @see #replaceMediaTypes(Map)
143         */
144        public ContentNegotiationConfigurer mediaTypes(Map<String, MediaType> mediaTypes) {
145                if (mediaTypes != null) {
146                        this.mediaTypes.putAll(mediaTypes);
147                }
148                return this;
149        }
150
151        /**
152         * Similar to {@link #mediaType} but for replacing existing mappings.
153         * @see #mediaType(String, MediaType)
154         * @see #mediaTypes(Map)
155         */
156        public ContentNegotiationConfigurer replaceMediaTypes(Map<String, MediaType> mediaTypes) {
157                this.mediaTypes.clear();
158                mediaTypes(mediaTypes);
159                return this;
160        }
161
162        /**
163         * Whether to ignore requests with path extension that cannot be resolved
164         * to any media type. Setting this to {@code false} will result in an
165         * {@code HttpMediaTypeNotAcceptableException} if there is no match.
166         * <p>By default this is set to {@code true}.
167         */
168        public ContentNegotiationConfigurer ignoreUnknownPathExtensions(boolean ignore) {
169                this.factory.setIgnoreUnknownPathExtensions(ignore);
170                return this;
171        }
172
173        /**
174         * When {@link #favorPathExtension} is set, this property determines whether
175         * to allow use of JAF (Java Activation Framework) to resolve a path
176         * extension to a specific MediaType.
177         * <p>By default this is not set in which case
178         * {@code PathExtensionContentNegotiationStrategy} will use JAF if available.
179         */
180        public ContentNegotiationConfigurer useJaf(boolean useJaf) {
181                this.factory.setUseJaf(useJaf);
182                return this;
183        }
184
185        /**
186         * Whether a request parameter ("format" by default) should be used to
187         * determine the requested media type. For this option to work you must
188         * register {@link #mediaType(String, MediaType) media type mappings}.
189         * <p>By default this is set to {@code false}.
190         * @see #parameterName(String)
191         */
192        public ContentNegotiationConfigurer favorParameter(boolean favorParameter) {
193                this.factory.setFavorParameter(favorParameter);
194                return this;
195        }
196
197        /**
198         * Set the query parameter name to use when {@link #favorParameter} is on.
199         * <p>The default parameter name is {@code "format"}.
200         */
201        public ContentNegotiationConfigurer parameterName(String parameterName) {
202                this.factory.setParameterName(parameterName);
203                return this;
204        }
205
206        /**
207         * Whether to disable checking the 'Accept' request header.
208         * <p>By default this value is set to {@code false}.
209         */
210        public ContentNegotiationConfigurer ignoreAcceptHeader(boolean ignoreAcceptHeader) {
211                this.factory.setIgnoreAcceptHeader(ignoreAcceptHeader);
212                return this;
213        }
214
215        /**
216         * Set the default content type to use when no content type is requested.
217         * <p>By default this is not set.
218         * @see #defaultContentTypeStrategy
219         */
220        public ContentNegotiationConfigurer defaultContentType(MediaType defaultContentType) {
221                this.factory.setDefaultContentType(defaultContentType);
222                return this;
223        }
224
225        /**
226         * Set a custom {@link ContentNegotiationStrategy} to use to determine
227         * the content type to use when no content type is requested.
228         * <p>By default this is not set.
229         * @see #defaultContentType
230         * @since 4.1.2
231         */
232        public ContentNegotiationConfigurer defaultContentTypeStrategy(ContentNegotiationStrategy defaultStrategy) {
233                this.factory.setDefaultContentTypeStrategy(defaultStrategy);
234                return this;
235        }
236
237
238        /**
239         * Build a {@link ContentNegotiationManager} based on this configurer's settings.
240         * @since 4.3.12
241         * @see ContentNegotiationManagerFactoryBean#getObject()
242         */
243        protected ContentNegotiationManager buildContentNegotiationManager() {
244                this.factory.addMediaTypes(this.mediaTypes);
245                this.factory.afterPropertiesSet();
246                return this.factory.getObject();
247        }
248
249        /**
250         * @deprecated as of 4.3.12, in favor of {@link #buildContentNegotiationManager()}
251         */
252        @Deprecated
253        protected ContentNegotiationManager getContentNegotiationManager() throws Exception {
254                return buildContentNegotiationManager();
255        }
256
257}