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.HashMap; 021import java.util.List; 022import java.util.Locale; 023import java.util.Map; 024import java.util.Properties; 025import javax.servlet.ServletContext; 026 027import org.springframework.beans.factory.FactoryBean; 028import org.springframework.beans.factory.InitializingBean; 029import org.springframework.http.MediaType; 030import org.springframework.util.Assert; 031import org.springframework.util.CollectionUtils; 032import org.springframework.web.context.ServletContextAware; 033 034/** 035 * Factory to create a {@code ContentNegotiationManager} and configure it with 036 * one or more {@link ContentNegotiationStrategy} instances via simple setters. 037 * The following table shows setters, resulting strategy instances, and if in 038 * use by default: 039 * 040 * <table> 041 * <tr> 042 * <th>Property Setter</th> 043 * <th>Underlying Strategy</th> 044 * <th>Default Setting</th> 045 * </tr> 046 * <tr> 047 * <td>{@link #setFavorPathExtension}</td> 048 * <td>{@link PathExtensionContentNegotiationStrategy Path Extension strategy}</td> 049 * <td>On</td> 050 * </tr> 051 * <tr> 052 * <td>{@link #setFavorParameter favorParameter}</td> 053 * <td>{@link ParameterContentNegotiationStrategy Parameter strategy}</td> 054 * <td>Off</td> 055 * </tr> 056 * <tr> 057 * <td>{@link #setIgnoreAcceptHeader ignoreAcceptHeader}</td> 058 * <td>{@link HeaderContentNegotiationStrategy Header strategy}</td> 059 * <td>On</td> 060 * </tr> 061 * <tr> 062 * <td>{@link #setDefaultContentType defaultContentType}</td> 063 * <td>{@link FixedContentNegotiationStrategy Fixed content strategy}</td> 064 * <td>Not set</td> 065 * </tr> 066 * <tr> 067 * <td>{@link #setDefaultContentTypeStrategy 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. Setters may only 074 * turn individual strategies on or off. If you need a custom order for any 075 * reason simply instantiate {@code ContentNegotiationManager} directly. 076 * 077 * <p>For the path extension and parameter strategies you may explicitly add 078 * {@link #setMediaTypes MediaType mappings}. This will be used to resolve path 079 * extensions or a parameter value such as "json" to a media type such as 080 * "application/json". 081 * 082 * <p>The path extension strategy will also use {@link ServletContext#getMimeType} 083 * and the Java Activation framework (JAF), if available, to resolve a path 084 * extension to a MediaType. You may {@link #setUseJaf suppress} the use of JAF. 085 * 086 * @author Rossen Stoyanchev 087 * @author Brian Clozel 088 * @since 3.2 089 */ 090public class ContentNegotiationManagerFactoryBean 091 implements FactoryBean<ContentNegotiationManager>, ServletContextAware, InitializingBean { 092 093 private boolean favorPathExtension = true; 094 095 private boolean favorParameter = false; 096 097 private boolean ignoreAcceptHeader = false; 098 099 private Map<String, MediaType> mediaTypes = new HashMap<String, MediaType>(); 100 101 private boolean ignoreUnknownPathExtensions = true; 102 103 private Boolean useJaf; 104 105 private String parameterName = "format"; 106 107 private ContentNegotiationStrategy defaultNegotiationStrategy; 108 109 private ContentNegotiationManager contentNegotiationManager; 110 111 private ServletContext servletContext; 112 113 114 /** 115 * Whether the path extension in the URL path should be used to determine 116 * the requested media type. 117 * <p>By default this is set to {@code true} in which case a request 118 * for {@code /hotels.pdf} will be interpreted as a request for 119 * {@code "application/pdf"} regardless of the 'Accept' header. 120 */ 121 public void setFavorPathExtension(boolean favorPathExtension) { 122 this.favorPathExtension = favorPathExtension; 123 } 124 125 /** 126 * Add a mapping from a key, extracted from a path extension or a query 127 * parameter, to a MediaType. This is required in order for the parameter 128 * strategy to work. Any extensions explicitly registered here are also 129 * whitelisted for the purpose of Reflected File Download attack detection 130 * (see Spring Framework reference documentation for more details on RFD 131 * attack protection). 132 * <p>The path extension strategy will also try to use 133 * {@link ServletContext#getMimeType} and JAF (if present) to resolve path 134 * extensions. To change this behavior see the {@link #useJaf} property. 135 * @param mediaTypes media type mappings 136 * @see #addMediaType(String, MediaType) 137 * @see #addMediaTypes(Map) 138 */ 139 public void setMediaTypes(Properties mediaTypes) { 140 if (!CollectionUtils.isEmpty(mediaTypes)) { 141 for (Map.Entry<Object, Object> entry : mediaTypes.entrySet()) { 142 String extension = ((String)entry.getKey()).toLowerCase(Locale.ENGLISH); 143 MediaType mediaType = MediaType.valueOf((String) entry.getValue()); 144 this.mediaTypes.put(extension, mediaType); 145 } 146 } 147 } 148 149 /** 150 * An alternative to {@link #setMediaTypes} for use in Java code. 151 * @see #setMediaTypes 152 * @see #addMediaTypes 153 */ 154 public void addMediaType(String fileExtension, MediaType mediaType) { 155 this.mediaTypes.put(fileExtension, mediaType); 156 } 157 158 /** 159 * An alternative to {@link #setMediaTypes} for use in Java code. 160 * @see #setMediaTypes 161 * @see #addMediaType 162 */ 163 public void addMediaTypes(Map<String, MediaType> mediaTypes) { 164 if (mediaTypes != null) { 165 this.mediaTypes.putAll(mediaTypes); 166 } 167 } 168 169 /** 170 * Whether to ignore requests with path extension that cannot be resolved 171 * to any media type. Setting this to {@code false} will result in an 172 * {@code HttpMediaTypeNotAcceptableException} if there is no match. 173 * <p>By default this is set to {@code true}. 174 */ 175 public void setIgnoreUnknownPathExtensions(boolean ignore) { 176 this.ignoreUnknownPathExtensions = ignore; 177 } 178 179 /** 180 * When {@link #setFavorPathExtension favorPathExtension} is set, this 181 * property determines whether to allow use of JAF (Java Activation Framework) 182 * to resolve a path extension to a specific MediaType. 183 * <p>By default this is not set in which case 184 * {@code PathExtensionContentNegotiationStrategy} will use JAF if available. 185 */ 186 public void setUseJaf(boolean useJaf) { 187 this.useJaf = useJaf; 188 } 189 190 private boolean isUseJafTurnedOff() { 191 return (this.useJaf != null && !this.useJaf); 192 } 193 194 /** 195 * Whether a request parameter ("format" by default) should be used to 196 * determine the requested media type. For this option to work you must 197 * register {@link #setMediaTypes media type mappings}. 198 * <p>By default this is set to {@code false}. 199 * @see #setParameterName 200 */ 201 public void setFavorParameter(boolean favorParameter) { 202 this.favorParameter = favorParameter; 203 } 204 205 /** 206 * Set the query parameter name to use when {@link #setFavorParameter} is on. 207 * <p>The default parameter name is {@code "format"}. 208 */ 209 public void setParameterName(String parameterName) { 210 Assert.notNull(parameterName, "parameterName is required"); 211 this.parameterName = parameterName; 212 } 213 214 /** 215 * Whether to disable checking the 'Accept' request header. 216 * <p>By default this value is set to {@code false}. 217 */ 218 public void setIgnoreAcceptHeader(boolean ignoreAcceptHeader) { 219 this.ignoreAcceptHeader = ignoreAcceptHeader; 220 } 221 222 /** 223 * Set the default content type to use when no content type is requested. 224 * <p>By default this is not set. 225 * @see #setDefaultContentTypeStrategy 226 */ 227 public void setDefaultContentType(MediaType contentType) { 228 this.defaultNegotiationStrategy = new FixedContentNegotiationStrategy(contentType); 229 } 230 231 /** 232 * Set a custom {@link ContentNegotiationStrategy} to use to determine 233 * the content type to use when no content type is requested. 234 * <p>By default this is not set. 235 * @see #setDefaultContentType 236 * @since 4.1.2 237 */ 238 public void setDefaultContentTypeStrategy(ContentNegotiationStrategy strategy) { 239 this.defaultNegotiationStrategy = strategy; 240 } 241 242 /** 243 * Invoked by Spring to inject the ServletContext. 244 */ 245 @Override 246 public void setServletContext(ServletContext servletContext) { 247 this.servletContext = servletContext; 248 } 249 250 251 @Override 252 public void afterPropertiesSet() { 253 List<ContentNegotiationStrategy> strategies = new ArrayList<ContentNegotiationStrategy>(); 254 255 if (this.favorPathExtension) { 256 PathExtensionContentNegotiationStrategy strategy; 257 if (this.servletContext != null && !isUseJafTurnedOff()) { 258 strategy = new ServletPathExtensionContentNegotiationStrategy(this.servletContext, this.mediaTypes); 259 } 260 else { 261 strategy = new PathExtensionContentNegotiationStrategy(this.mediaTypes); 262 } 263 strategy.setIgnoreUnknownExtensions(this.ignoreUnknownPathExtensions); 264 if (this.useJaf != null) { 265 strategy.setUseJaf(this.useJaf); 266 } 267 strategies.add(strategy); 268 } 269 270 if (this.favorParameter) { 271 ParameterContentNegotiationStrategy strategy = new ParameterContentNegotiationStrategy(this.mediaTypes); 272 strategy.setParameterName(this.parameterName); 273 strategies.add(strategy); 274 } 275 276 if (!this.ignoreAcceptHeader) { 277 strategies.add(new HeaderContentNegotiationStrategy()); 278 } 279 280 if (this.defaultNegotiationStrategy != null) { 281 strategies.add(this.defaultNegotiationStrategy); 282 } 283 284 this.contentNegotiationManager = new ContentNegotiationManager(strategies); 285 } 286 287 @Override 288 public ContentNegotiationManager getObject() { 289 return this.contentNegotiationManager; 290 } 291 292 @Override 293 public Class<?> getObjectType() { 294 return ContentNegotiationManager.class; 295 } 296 297 @Override 298 public boolean isSingleton() { 299 return true; 300 } 301 302}