org.springframework.web.accept.ContentNegotiationManagerFactoryBean Maven / Gradle / Ivy
/*
* 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 {@code ContentNegotiationManager} and configure it with
* {@link 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
*
*
* {@link #setFavorParameter favorParameter}
* false
* {@link ParameterContentNegotiationStrategy}
* Off
*
*
* {@link #setFavorPathExtension favorPathExtension}
* false (as of 5.3)
* {@link PathExtensionContentNegotiationStrategy}
* Off
*
*
* {@link #setIgnoreAcceptHeader ignoreAcceptHeader}
* false
* {@link HeaderContentNegotiationStrategy}
* Enabled
*
*
* {@link #setDefaultContentType defaultContentType}
* null
* {@link FixedContentNegotiationStrategy}
* Off
*
*
* {@link #setDefaultContentTypeStrategy defaultContentTypeStrategy}
* null
* {@link ContentNegotiationStrategy}
* Off
*
*
*
* Alternatively you can avoid use of the above convenience builder
* methods and set the exact strategies to use via
* {@link #setStrategies(List)}.
*
*
Deprecation Note: 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
* #24719.
* @author Rossen Stoyanchev
* @author Brian Clozel
* @since 3.2
*/
public class ContentNegotiationManagerFactoryBean
implements FactoryBean, ServletContextAware, InitializingBean {
@Nullable
private List strategies;
private boolean favorParameter = false;
private String parameterName = "format";
private boolean favorPathExtension = false;
private Map 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.
* @param strategies the strategies to use
* @since 5.0
*/
public void setStrategies(@Nullable List 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 {@link #setMediaTypes media type mappings}.
* 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 {@link #setFavorParameter} is on.
*
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 {@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 {@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}.
*
Note: 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 }, the media type mappings are also plugged
* in to:
*
* - Determine the media type of static resources served with
* {@code ResourceHttpRequestHandler}.
*
- Determine the media type of views rendered with
* {@code ContentNegotiatingViewResolver}.
*
- List safe extensions for RFD attack detection (check the Spring
* Framework reference docs for details).
*
* @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 {@link #setMediaTypes} for programmatic registrations.
*/
public void addMediaType(String key, MediaType mediaType) {
this.mediaTypes.put(key.toLowerCase(Locale.ENGLISH), mediaType);
}
/**
* An alternative to {@link #setMediaTypes} for programmatic registrations.
*/
public void addMediaTypes(@Nullable Map 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 {@code false} will result in an
* {@code HttpMediaTypeNotAcceptableException} if there is no match.
* 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 {@link #setUseRegisteredExtensionsOnly(boolean)},
* which has reverse behavior.
*/
@Deprecated
public void setUseJaf(boolean useJaf) {
setUseRegisteredExtensionsOnly(!useJaf);
}
/**
* 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}.
*
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 {@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 #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.
* @since 5.0
* @see #setDefaultContentTypeStrategy
*/
public void setDefaultContentTypes(List contentTypes) {
this.defaultNegotiationStrategy = new FixedContentNegotiationStrategy(contentTypes);
}
/**
* Set a custom {@link ContentNegotiationStrategy} to use to determine
* the content type to use when no content type is requested.
* 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.
*/
@Override
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
@Override
public void afterPropertiesSet() {
build();
}
/**
* Create and initialize a {@link ContentNegotiationManager} instance.
* @since 5.0
*/
@SuppressWarnings("deprecation")
public ContentNegotiationManager build() {
List 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;
}
}