/*
 * 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 ContentNegotiationManager and configure it with one or more ContentNegotiationStrategy instances.

As of 5.0 you can set the exact strategies to use via setStrategies(List<ContentNegotiationStrategy>).

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
setFavorPathExtension Path Extension strategy On
favorParameter Parameter strategy Off
ignoreAcceptHeader Header strategy On
defaultContentType Fixed content strategy Not set
defaultContentTypeStrategy 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 setFavorPathExtension to false or otherwise set the strategies to use explicitly via setStrategies(List<ContentNegotiationStrategy>).
Author:Rossen Stoyanchev, Brian Clozel
Since:3.2
/** * Factory to create a {@code ContentNegotiationManager} and configure it with * one or more {@link ContentNegotiationStrategy} instances. * * <p>As of 5.0 you can set the exact strategies to use via * {@link #setStrategies(List)}. * * <p>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: * * <table> * <tr> * <th>Property Setter</th> * <th>Underlying Strategy</th> * <th>Default Setting</th> * </tr> * <tr> * <td>{@link #setFavorPathExtension}</td> * <td>{@link PathExtensionContentNegotiationStrategy Path Extension strategy}</td> * <td>On</td> * </tr> * <tr> * <td>{@link #setFavorParameter favorParameter}</td> * <td>{@link ParameterContentNegotiationStrategy Parameter strategy}</td> * <td>Off</td> * </tr> * <tr> * <td>{@link #setIgnoreAcceptHeader ignoreAcceptHeader}</td> * <td>{@link HeaderContentNegotiationStrategy Header strategy}</td> * <td>On</td> * </tr> * <tr> * <td>{@link #setDefaultContentType defaultContentType}</td> * <td>{@link FixedContentNegotiationStrategy Fixed content strategy}</td> * <td>Not set</td> * </tr> * <tr> * <td>{@link #setDefaultContentTypeStrategy defaultContentTypeStrategy}</td> * <td>{@link ContentNegotiationStrategy}</td> * <td>Not set</td> * </tr> * </table> * * <strong>Note:</strong> 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<ContentNegotiationManager>, ServletContextAware, InitializingBean { @Nullable private List<ContentNegotiationStrategy> strategies; private boolean favorPathExtension = true; private boolean favorParameter = false; private boolean ignoreAcceptHeader = false; private Map<String, MediaType> 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.

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 the path extension in the URL path should be used to determine the requested media type.

By default this is set to true in which case a request for /hotels.pdf will be interpreted as a request for "application/pdf" regardless of the 'Accept' header.

/** * 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 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 ServletContext.getMimeType and MediaTypeFactory to resolve path extensions.

Params:
  • mediaTypes – media type mappings
See Also:
/** * 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). * <p>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 setMediaTypes for use in Java code.
See Also:
/** * 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 setMediaTypes for use in Java code.
See Also:
/** * An alternative to {@link #setMediaTypes} for use in Java code. * @see #setMediaTypes * @see #addMediaType */
public void addMediaTypes(@Nullable Map<String, MediaType> 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 false will result in an HttpMediaTypeNotAcceptableException if there is no match.

By default this is set to true.

/** * 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}. */
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 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 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(); }
Actually build the ContentNegotiationManager.
Since:5.0
/** * Actually build the {@link ContentNegotiationManager}. * @since 5.0 */
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); return this.contentNegotiationManager; } @Override @Nullable public ContentNegotiationManager getObject() { return this.contentNegotiationManager; } @Override public Class<?> getObjectType() { return ContentNegotiationManager.class; } @Override public boolean isSingleton() { return true; } }