org.springframework.web.accept.ContentNegotiationManagerFactoryBean Maven / Gradle / Ivy
/*
* Copyright 2002-2018 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
*
* http://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
* one or more {@link ContentNegotiationStrategy} instances.
*
* As of 5.0 you can set the exact strategies to use via
* {@link #setStrategies(List)}.
*
*
As an alternative you can also rely on the set of defaults described below
* which can be turned on or off or customized through the methods of this
* builder:
*
*
*
* Property Setter
* Underlying Strategy
* Default Setting
*
*
* {@link #setFavorPathExtension}
* {@link PathExtensionContentNegotiationStrategy Path Extension strategy}
* On
*
*
* {@link #setFavorParameter favorParameter}
* {@link ParameterContentNegotiationStrategy Parameter strategy}
* Off
*
*
* {@link #setIgnoreAcceptHeader ignoreAcceptHeader}
* {@link HeaderContentNegotiationStrategy Header strategy}
* On
*
*
* {@link #setDefaultContentType defaultContentType}
* {@link FixedContentNegotiationStrategy Fixed content strategy}
* Not set
*
*
* {@link #setDefaultContentTypeStrategy defaultContentTypeStrategy}
* {@link ContentNegotiationStrategy}
* Not set
*
*
*
* Note: if you must use URL-based content type resolution,
* the use of a query parameter is simpler and preferable to the use of a path
* extension since the latter can cause issues with URI variables, path
* parameters, and URI decoding. Consider setting {@link #setFavorPathExtension}
* to {@literal false} or otherwise set the strategies to use explicitly via
* {@link #setStrategies(List)}.
*
* @author Rossen Stoyanchev
* @author Brian Clozel
* @since 3.2
*/
public class ContentNegotiationManagerFactoryBean
implements FactoryBean, ServletContextAware, InitializingBean {
@Nullable
private List strategies;
private boolean favorPathExtension = true;
private boolean favorParameter = false;
private boolean ignoreAcceptHeader = false;
private Map mediaTypes = new HashMap<>();
private boolean ignoreUnknownPathExtensions = true;
@Nullable
private Boolean useRegisteredExtensionsOnly;
private String parameterName = "format";
@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 the path extension in the URL path should be used to determine
* the requested media type.
* By default this is set to {@code true} in which case a request
* for {@code /hotels.pdf} will be interpreted as a request for
* {@code "application/pdf"} regardless of the 'Accept' header.
*/
public void setFavorPathExtension(boolean favorPathExtension) {
this.favorPathExtension = favorPathExtension;
}
/**
* Add a mapping from a key, extracted from a path extension or a query
* parameter, to a MediaType. This is required in order for the parameter
* strategy to work. Any extensions explicitly registered here are also
* whitelisted for the purpose of Reflected File Download attack detection
* (see Spring Framework reference documentation for more details on RFD
* attack protection).
*
The path extension strategy will also try to use
* {@link ServletContext#getMimeType} and
* {@link org.springframework.http.MediaTypeFactory} to resolve path extensions.
* @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) -> {
String extension = ((String) key).toLowerCase(Locale.ENGLISH);
MediaType mediaType = MediaType.valueOf((String) value);
this.mediaTypes.put(extension, mediaType);
});
}
}
/**
* An alternative to {@link #setMediaTypes} for use in Java code.
* @see #setMediaTypes
* @see #addMediaTypes
*/
public void addMediaType(String fileExtension, MediaType mediaType) {
this.mediaTypes.put(fileExtension, mediaType);
}
/**
* An alternative to {@link #setMediaTypes} for use in Java code.
* @see #setMediaTypes
* @see #addMediaType
*/
public void addMediaTypes(@Nullable Map mediaTypes) {
if (mediaTypes != null) {
this.mediaTypes.putAll(mediaTypes);
}
}
/**
* 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}.
*/
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 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 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();
}
/**
* Actually build the {@link ContentNegotiationManager}.
* @since 5.0
*/
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);
return this.contentNegotiationManager;
}
@Override
@Nullable
public ContentNegotiationManager getObject() {
return this.contentNegotiationManager;
}
@Override
public Class getObjectType() {
return ContentNegotiationManager.class;
}
@Override
public boolean isSingleton() {
return true;
}
}