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}