/*
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.accept;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import javax.servlet.ServletContext;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.http.MediaType;
import org.springframework.http.MediaTypeFactory;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.web.context.ServletContextAware;
Factory to create a ContentNegotiationManager
and configure it with ContentNegotiationStrategy
instances. This factory offers properties that in turn result in configuring the
underlying strategies. The table below shows the property names, their
default settings, as well as the strategies that they help to configure:
Property Setter
Default Value
Underlying Strategy
Enabled Or Not
favorParameter
false
ParameterContentNegotiationStrategy
Off
favorPathExtension
false (as of 5.3)
PathExtensionContentNegotiationStrategy
Off
ignoreAcceptHeader
false
HeaderContentNegotiationStrategy
Enabled
defaultContentType
null
FixedContentNegotiationStrategy
Off
defaultContentTypeStrategy
null
ContentNegotiationStrategy
Off
Alternatively you can avoid use of the above convenience builder methods and set the exact strategies to use via setStrategies(List<ContentNegotiationStrategy>)
.
Deprecation Note: As of 5.2.4, favorPathExtension
and ignoreUnknownPathExtensions
are deprecated in order to discourage using path extensions for content negotiation and for request mapping with similar deprecations on
RequestMappingHandlerMapping
. For further context, please read issue #24719.
Author: Rossen Stoyanchev, Brian Clozel Since: 3.2
/**
* Factory to create a {@code ContentNegotiationManager} and configure it with
* {@link ContentNegotiationStrategy} instances.
*
* <p>This factory offers properties that in turn result in configuring the
* underlying strategies. The table below shows the property names, their
* default settings, as well as the strategies that they help to configure:
*
* <table>
* <tr>
* <th>Property Setter</th>
* <th>Default Value</th>
* <th>Underlying Strategy</th>
* <th>Enabled Or Not</th>
* </tr>
* <tr>
* <td>{@link #setFavorParameter favorParameter}</td>
* <td>false</td>
* <td>{@link ParameterContentNegotiationStrategy}</td>
* <td>Off</td>
* </tr>
* <tr>
* <td>{@link #setFavorPathExtension favorPathExtension}</td>
* <td>false (as of 5.3)</td>
* <td>{@link PathExtensionContentNegotiationStrategy}</td>
* <td>Off</td>
* </tr>
* <tr>
* <td>{@link #setIgnoreAcceptHeader ignoreAcceptHeader}</td>
* <td>false</td>
* <td>{@link HeaderContentNegotiationStrategy}</td>
* <td>Enabled</td>
* </tr>
* <tr>
* <td>{@link #setDefaultContentType defaultContentType}</td>
* <td>null</td>
* <td>{@link FixedContentNegotiationStrategy}</td>
* <td>Off</td>
* </tr>
* <tr>
* <td>{@link #setDefaultContentTypeStrategy defaultContentTypeStrategy}</td>
* <td>null</td>
* <td>{@link ContentNegotiationStrategy}</td>
* <td>Off</td>
* </tr>
* </table>
*
* <p>Alternatively you can avoid use of the above convenience builder
* methods and set the exact strategies to use via
* {@link #setStrategies(List)}.
*
* <p><strong>Deprecation Note:</strong> As of 5.2.4,
* {@link #setFavorPathExtension(boolean) favorPathExtension} and
* {@link #setIgnoreUnknownPathExtensions(boolean) ignoreUnknownPathExtensions}
* are deprecated in order to discourage using path extensions for content
* negotiation and for request mapping with similar deprecations on
* {@link org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
* RequestMappingHandlerMapping}. For further context, please read issue
* <a href="https://github.com/spring-projects/spring-framework/issues/24179">#24719</a>.
* @author Rossen Stoyanchev
* @author Brian Clozel
* @since 3.2
*/
public class ContentNegotiationManagerFactoryBean
implements FactoryBean<ContentNegotiationManager>, ServletContextAware, InitializingBean {
@Nullable
private List<ContentNegotiationStrategy> strategies;
private boolean favorParameter = false;
private String parameterName = "format";
private boolean favorPathExtension = false;
private Map<String, MediaType> mediaTypes = new HashMap<>();
private boolean ignoreUnknownPathExtensions = true;
@Nullable
private Boolean useRegisteredExtensionsOnly;
private boolean ignoreAcceptHeader = false;
@Nullable
private ContentNegotiationStrategy defaultNegotiationStrategy;
@Nullable
private ContentNegotiationManager contentNegotiationManager;
@Nullable
private ServletContext servletContext;
Set the exact list of strategies to use.
Note: use of this method is mutually exclusive with
use of all other setters in this class which customize a default, fixed
set of strategies. See class level doc for more details.
Params: - strategies – the strategies to use
Since: 5.0
/**
* Set the exact list of strategies to use.
* <p><strong>Note:</strong> use of this method is mutually exclusive with
* use of all other setters in this class which customize a default, fixed
* set of strategies. See class level doc for more details.
* @param strategies the strategies to use
* @since 5.0
*/
public void setStrategies(@Nullable List<ContentNegotiationStrategy> strategies) {
this.strategies = (strategies != null ? new ArrayList<>(strategies) : null);
}
Whether a request parameter ("format" by default) should be used to determine the requested media type. For this option to work you must register media type mappings
. By default this is set to false
.
See Also:
/**
* Whether a request parameter ("format" by default) should be used to
* determine the requested media type. For this option to work you must
* register {@link #setMediaTypes media type mappings}.
* <p>By default this is set to {@code false}.
* @see #setParameterName
*/
public void setFavorParameter(boolean favorParameter) {
this.favorParameter = favorParameter;
}
Set the query parameter name to use when setFavorParameter
is on. The default parameter name is "format"
.
/**
* Set the query parameter name to use when {@link #setFavorParameter} is on.
* <p>The default parameter name is {@code "format"}.
*/
public void setParameterName(String parameterName) {
Assert.notNull(parameterName, "parameterName is required");
this.parameterName = parameterName;
}
Whether the path extension in the URL path should be used to determine
the requested media type.
By default this is set to false
in which case path extensions have no impact on content negotiation.
Deprecated: as of 5.2.4. See class-level note on the deprecation of path extension config options. As there is no replacement for this method, in 5.2.x it is necessary to set it to false
. In 5.3 the default changes to false
and use of this property becomes unnecessary.
/**
* Whether the path extension in the URL path should be used to determine
* the requested media type.
* <p>By default this is set to {@code false} in which case path extensions
* have no impact on content negotiation.
* @deprecated as of 5.2.4. See class-level note on the deprecation of path
* extension config options. As there is no replacement for this method,
* in 5.2.x it is necessary to set it to {@code false}. In 5.3 the default
* changes to {@code false} and use of this property becomes unnecessary.
*/
@Deprecated
public void setFavorPathExtension(boolean favorPathExtension) {
this.favorPathExtension = favorPathExtension;
}
Add a mapping from a key to a MediaType where the key are normalized to
lowercase and may have been extracted from a path extension, a filename
extension, or passed as a query parameter.
The parameter strategy
requires such mappings in order to work while the
path extension strategy
can fall back on lookups via getMimeType.getMimeType
and MediaTypeFactory
.
Note: Mappings registered here may be accessed via ContentNegotiationManager.getMediaTypeMappings()
and may be used not only in the parameter and path extension strategies. For example, with the Spring MVC config, e.g. @EnableWebMvc
or <mvc:annotation-driven>
, the media type mappings are also plugged in to:
- Determine the media type of static resources served with
ResourceHttpRequestHandler
. - Determine the media type of views rendered with
ContentNegotiatingViewResolver
. - List safe extensions for RFD attack detection (check the Spring
Framework reference docs for details).
Params: - mediaTypes – media type mappings
See Also:
/**
* Add a mapping from a key to a MediaType where the key are normalized to
* lowercase and may have been extracted from a path extension, a filename
* extension, or passed as a query parameter.
* <p>The {@link #setFavorParameter(boolean) parameter strategy} requires
* such mappings in order to work while the {@link #setFavorPathExtension(boolean)
* path extension strategy} can fall back on lookups via
* {@link ServletContext#getMimeType} and
* {@link org.springframework.http.MediaTypeFactory}.
* <p><strong>Note:</strong> Mappings registered here may be accessed via
* {@link ContentNegotiationManager#getMediaTypeMappings()} and may be used
* not only in the parameter and path extension strategies. For example,
* with the Spring MVC config, e.g. {@code @EnableWebMvc} or
* {@code <mvc:annotation-driven>}, the media type mappings are also plugged
* in to:
* <ul>
* <li>Determine the media type of static resources served with
* {@code ResourceHttpRequestHandler}.
* <li>Determine the media type of views rendered with
* {@code ContentNegotiatingViewResolver}.
* <li>List safe extensions for RFD attack detection (check the Spring
* Framework reference docs for details).
* </ul>
* @param mediaTypes media type mappings
* @see #addMediaType(String, MediaType)
* @see #addMediaTypes(Map)
*/
public void setMediaTypes(Properties mediaTypes) {
if (!CollectionUtils.isEmpty(mediaTypes)) {
mediaTypes.forEach((key, value) ->
addMediaType((String) key, MediaType.valueOf((String) value)));
}
}
An alternative to setMediaTypes
for programmatic registrations. /**
* An alternative to {@link #setMediaTypes} for programmatic registrations.
*/
public void addMediaType(String key, MediaType mediaType) {
this.mediaTypes.put(key.toLowerCase(Locale.ENGLISH), mediaType);
}
An alternative to setMediaTypes
for programmatic registrations. /**
* An alternative to {@link #setMediaTypes} for programmatic registrations.
*/
public void addMediaTypes(@Nullable Map<String, MediaType> mediaTypes) {
if (mediaTypes != null) {
mediaTypes.forEach(this::addMediaType);
}
}
Whether to ignore requests with path extension that cannot be resolved to any media type. Setting this to false
will result in an HttpMediaTypeNotAcceptableException
if there is no match. By default this is set to true
.
Deprecated: as of 5.2.4. See class-level note on the deprecation of path
extension config options.
/**
* Whether to ignore requests with path extension that cannot be resolved
* to any media type. Setting this to {@code false} will result in an
* {@code HttpMediaTypeNotAcceptableException} if there is no match.
* <p>By default this is set to {@code true}.
* @deprecated as of 5.2.4. See class-level note on the deprecation of path
* extension config options.
*/
@Deprecated
public void setIgnoreUnknownPathExtensions(boolean ignore) {
this.ignoreUnknownPathExtensions = ignore;
}
Indicate whether to use the Java Activation Framework as a fallback option
to map from file extensions to media types.
Deprecated: as of 5.0, in favor of setUseRegisteredExtensionsOnly(boolean)
, which has reverse behavior.
/**
* Indicate whether to use the Java Activation Framework as a fallback option
* to map from file extensions to media types.
* @deprecated as of 5.0, in favor of {@link #setUseRegisteredExtensionsOnly(boolean)},
* which has reverse behavior.
*/
@Deprecated
public void setUseJaf(boolean useJaf) {
setUseRegisteredExtensionsOnly(!useJaf);
}
When favorPathExtension
or setFavorParameter(boolean)
is set, this property determines whether to use only registered MediaType
mappings or to allow dynamic resolution, e.g. via MediaTypeFactory
. By default this is not set in which case dynamic resolution is on.
/**
* When {@link #setFavorPathExtension favorPathExtension} or
* {@link #setFavorParameter(boolean)} is set, this property determines
* whether to use only registered {@code MediaType} mappings or to allow
* dynamic resolution, e.g. via {@link MediaTypeFactory}.
* <p>By default this is not set in which case dynamic resolution is on.
*/
public void setUseRegisteredExtensionsOnly(boolean useRegisteredExtensionsOnly) {
this.useRegisteredExtensionsOnly = useRegisteredExtensionsOnly;
}
private boolean useRegisteredExtensionsOnly() {
return (this.useRegisteredExtensionsOnly != null && this.useRegisteredExtensionsOnly);
}
Whether to disable checking the 'Accept' request header.
By default this value is set to false
.
/**
* Whether to disable checking the 'Accept' request header.
* <p>By default this value is set to {@code false}.
*/
public void setIgnoreAcceptHeader(boolean ignoreAcceptHeader) {
this.ignoreAcceptHeader = ignoreAcceptHeader;
}
Set the default content type to use when no content type is requested.
By default this is not set.
See Also: - setDefaultContentTypeStrategy
/**
* Set the default content type to use when no content type is requested.
* <p>By default this is not set.
* @see #setDefaultContentTypeStrategy
*/
public void setDefaultContentType(MediaType contentType) {
this.defaultNegotiationStrategy = new FixedContentNegotiationStrategy(contentType);
}
Set the default content types to use when no content type is requested.
By default this is not set.
See Also: Since: 5.0
/**
* Set the default content types to use when no content type is requested.
* <p>By default this is not set.
* @since 5.0
* @see #setDefaultContentTypeStrategy
*/
public void setDefaultContentTypes(List<MediaType> contentTypes) {
this.defaultNegotiationStrategy = new FixedContentNegotiationStrategy(contentTypes);
}
Set a custom ContentNegotiationStrategy
to use to determine the content type to use when no content type is requested. By default this is not set.
See Also: Since: 4.1.2
/**
* Set a custom {@link ContentNegotiationStrategy} to use to determine
* the content type to use when no content type is requested.
* <p>By default this is not set.
* @since 4.1.2
* @see #setDefaultContentType
*/
public void setDefaultContentTypeStrategy(ContentNegotiationStrategy strategy) {
this.defaultNegotiationStrategy = strategy;
}
Invoked by Spring to inject the ServletContext.
/**
* Invoked by Spring to inject the ServletContext.
*/
@Override
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
@Override
public void afterPropertiesSet() {
build();
}
Create and initialize a ContentNegotiationManager
instance. Since: 5.0
/**
* Create and initialize a {@link ContentNegotiationManager} instance.
* @since 5.0
*/
@SuppressWarnings("deprecation")
public ContentNegotiationManager build() {
List<ContentNegotiationStrategy> strategies = new ArrayList<>();
if (this.strategies != null) {
strategies.addAll(this.strategies);
}
else {
if (this.favorPathExtension) {
PathExtensionContentNegotiationStrategy strategy;
if (this.servletContext != null && !useRegisteredExtensionsOnly()) {
strategy = new ServletPathExtensionContentNegotiationStrategy(this.servletContext, this.mediaTypes);
}
else {
strategy = new PathExtensionContentNegotiationStrategy(this.mediaTypes);
}
strategy.setIgnoreUnknownExtensions(this.ignoreUnknownPathExtensions);
if (this.useRegisteredExtensionsOnly != null) {
strategy.setUseRegisteredExtensionsOnly(this.useRegisteredExtensionsOnly);
}
strategies.add(strategy);
}
if (this.favorParameter) {
ParameterContentNegotiationStrategy strategy = new ParameterContentNegotiationStrategy(this.mediaTypes);
strategy.setParameterName(this.parameterName);
if (this.useRegisteredExtensionsOnly != null) {
strategy.setUseRegisteredExtensionsOnly(this.useRegisteredExtensionsOnly);
}
else {
strategy.setUseRegisteredExtensionsOnly(true); // backwards compatibility
}
strategies.add(strategy);
}
if (!this.ignoreAcceptHeader) {
strategies.add(new HeaderContentNegotiationStrategy());
}
if (this.defaultNegotiationStrategy != null) {
strategies.add(this.defaultNegotiationStrategy);
}
}
this.contentNegotiationManager = new ContentNegotiationManager(strategies);
// Ensure media type mappings are available via ContentNegotiationManager#getMediaTypeMappings()
// independent of path extension or parameter strategies.
if (!CollectionUtils.isEmpty(this.mediaTypes) && !this.favorPathExtension && !this.favorParameter) {
this.contentNegotiationManager.addFileExtensionResolvers(
new MappingMediaTypeFileExtensionResolver(this.mediaTypes));
}
return this.contentNegotiationManager;
}
@Override
@Nullable
public ContentNegotiationManager getObject() {
return this.contentNegotiationManager;
}
@Override
public Class<?> getObjectType() {
return ContentNegotiationManager.class;
}
@Override
public boolean isSingleton() {
return true;
}
}