Add abstractions for content negotiation
Introduced ContentNeogtiationStrategy for resolving the requested media types from an incoming request. The available implementations are based on path extension, request parameter, 'Accept' header, and a fixed default content type. The logic for these implementations is based on equivalent options, previously available only in the ContentNegotiatingViewResolver. Also in this commit is ContentNegotiationManager, the central class to use when configuring content negotiation options. It accepts one or more ContentNeogtiationStrategy instances and delegates to them. The ContentNeogiationManager can now be used to configure the following classes: - RequestMappingHandlerMappingm - RequestMappingHandlerAdapter - ExceptionHandlerExceptionResolver - ContentNegotiatingViewResolver Issue: SPR-8410, SPR-8417, SPR-8418,SPR-8416, SPR-8419,SPR-7722
This commit is contained in:
parent
35055fd866
commit
f05e2bc56f
|
|
@ -368,6 +368,7 @@ project('spring-web') {
|
|||
optional dep
|
||||
exclude group: 'org.mortbay.jetty', module: 'servlet-api-2.5'
|
||||
}
|
||||
testCompile project(":spring-context-support") // for JafMediaTypeFactory
|
||||
testCompile "xmlunit:xmlunit:1.2"
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Copyright 2002-2012 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.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
|
||||
/**
|
||||
* A base class for ContentNegotiationStrategy types that maintain a map with keys
|
||||
* such as "json" and media types such as "application/json".
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.2
|
||||
*/
|
||||
public abstract class AbstractMappingContentNegotiationStrategy extends MappingMediaTypeExtensionsResolver
|
||||
implements ContentNegotiationStrategy, MediaTypeExtensionsResolver {
|
||||
|
||||
/**
|
||||
* Create an instance with the given extension-to-MediaType lookup.
|
||||
* @throws IllegalArgumentException if a media type string cannot be parsed
|
||||
*/
|
||||
public AbstractMappingContentNegotiationStrategy(Map<String, String> mediaTypes) {
|
||||
super(mediaTypes);
|
||||
}
|
||||
|
||||
public List<MediaType> resolveMediaTypes(NativeWebRequest webRequest) {
|
||||
String key = getMediaTypeKey(webRequest);
|
||||
if (StringUtils.hasText(key)) {
|
||||
MediaType mediaType = lookupMediaType(key);
|
||||
if (mediaType != null) {
|
||||
handleMatch(key, mediaType);
|
||||
return Collections.singletonList(mediaType);
|
||||
}
|
||||
mediaType = handleNoMatch(webRequest, key);
|
||||
if (mediaType != null) {
|
||||
addMapping(key, mediaType);
|
||||
return Collections.singletonList(mediaType);
|
||||
}
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sub-classes must extract the key to use to look up a media type.
|
||||
* @return the lookup key or {@code null} if the key cannot be derived
|
||||
*/
|
||||
protected abstract String getMediaTypeKey(NativeWebRequest request);
|
||||
|
||||
/**
|
||||
* Invoked when a matching media type is found in the lookup map.
|
||||
*/
|
||||
protected void handleMatch(String mappingKey, MediaType mediaType) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when no matching media type is found in the lookup map.
|
||||
* Sub-classes can take further steps to determine the media type.
|
||||
*/
|
||||
protected MediaType handleNoMatch(NativeWebRequest request, String mappingKey) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* Copyright 2002-2012 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.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.HttpMediaTypeNotAcceptableException;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
|
||||
/**
|
||||
* This class is used to determine the requested {@linkplain MediaType media types}
|
||||
* in a request by delegating to a list of {@link ContentNegotiationStrategy} instances.
|
||||
*
|
||||
* <p>It may also be used to determine the extensions associated with a MediaType by
|
||||
* delegating to a list of {@link MediaTypeExtensionsResolver} instances.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.2
|
||||
*/
|
||||
public class ContentNegotiationManager implements ContentNegotiationStrategy, MediaTypeExtensionsResolver {
|
||||
|
||||
private final List<ContentNegotiationStrategy> contentNegotiationStrategies = new ArrayList<ContentNegotiationStrategy>();
|
||||
|
||||
private final Set<MediaTypeExtensionsResolver> extensionResolvers = new LinkedHashSet<MediaTypeExtensionsResolver>();
|
||||
|
||||
/**
|
||||
* Create an instance with the given ContentNegotiationStrategy instances.
|
||||
* <p>Each instance is checked to see if it is also an implementation of
|
||||
* MediaTypeExtensionsResolver, and if so it is registered as such.
|
||||
*/
|
||||
public ContentNegotiationManager(ContentNegotiationStrategy... strategies) {
|
||||
Assert.notEmpty(strategies, "At least one ContentNegotiationStrategy is expected");
|
||||
this.contentNegotiationStrategies.addAll(Arrays.asList(strategies));
|
||||
for (ContentNegotiationStrategy strategy : this.contentNegotiationStrategies) {
|
||||
if (strategy instanceof MediaTypeExtensionsResolver) {
|
||||
this.extensionResolvers.add((MediaTypeExtensionsResolver) strategy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance with a {@link HeaderContentNegotiationStrategy}.
|
||||
*/
|
||||
public ContentNegotiationManager() {
|
||||
this(new HeaderContentNegotiationStrategy());
|
||||
}
|
||||
|
||||
/**
|
||||
* Add MediaTypeExtensionsResolver instances.
|
||||
*/
|
||||
public void addExtensionsResolver(MediaTypeExtensionsResolver... resolvers) {
|
||||
this.extensionResolvers.addAll(Arrays.asList(resolvers));
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegate to all configured ContentNegotiationStrategy instances until one
|
||||
* returns a non-empty list.
|
||||
* @param request the current request
|
||||
* @return the requested media types or an empty list, never {@code null}
|
||||
* @throws HttpMediaTypeNotAcceptableException if the requested media types cannot be parsed
|
||||
*/
|
||||
public List<MediaType> resolveMediaTypes(NativeWebRequest webRequest) throws HttpMediaTypeNotAcceptableException {
|
||||
for (ContentNegotiationStrategy strategy : this.contentNegotiationStrategies) {
|
||||
List<MediaType> mediaTypes = strategy.resolveMediaTypes(webRequest);
|
||||
if (!mediaTypes.isEmpty()) {
|
||||
return mediaTypes;
|
||||
}
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegate to all configured MediaTypeExtensionsResolver instances and aggregate
|
||||
* the list of all extensions found.
|
||||
*/
|
||||
public List<String> resolveExtensions(MediaType mediaType) {
|
||||
Set<String> extensions = new LinkedHashSet<String>();
|
||||
for (MediaTypeExtensionsResolver resolver : this.extensionResolvers) {
|
||||
extensions.addAll(resolver.resolveExtensions(mediaType));
|
||||
}
|
||||
return new ArrayList<String>(extensions);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright 2002-2012 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.List;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.HttpMediaTypeNotAcceptableException;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
|
||||
/**
|
||||
* A strategy for resolving the requested media types in a request.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.2
|
||||
*/
|
||||
public interface ContentNegotiationStrategy {
|
||||
|
||||
/**
|
||||
* Resolve the given request to a list of media types. The returned list is
|
||||
* ordered by specificity first and by quality parameter second.
|
||||
*
|
||||
* @param request the current request
|
||||
* @return the requested media types or an empty list, never {@code null}
|
||||
*
|
||||
* @throws HttpMediaTypeNotAcceptableException if the requested media types cannot be parsed
|
||||
*/
|
||||
List<MediaType> resolveMediaTypes(NativeWebRequest webRequest) throws HttpMediaTypeNotAcceptableException;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright 2002-2012 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.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
|
||||
/**
|
||||
* A ContentNegotiationStrategy that returns a fixed content type.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.2
|
||||
*/
|
||||
public class FixedContentNegotiationStrategy implements ContentNegotiationStrategy {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(FixedContentNegotiationStrategy.class);
|
||||
|
||||
private final MediaType defaultContentType;
|
||||
|
||||
/**
|
||||
* Create an instance that always returns the given content type.
|
||||
*/
|
||||
public FixedContentNegotiationStrategy(MediaType defaultContentType) {
|
||||
this.defaultContentType = defaultContentType;
|
||||
}
|
||||
|
||||
public List<MediaType> resolveMediaTypes(NativeWebRequest webRequest) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Requested media types is " + this.defaultContentType + " (based on default MediaType)");
|
||||
}
|
||||
return Collections.singletonList(this.defaultContentType);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Copyright 2002-2012 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.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.HttpMediaTypeNotAcceptableException;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
|
||||
/**
|
||||
* A ContentNegotiationStrategy that parses the 'Accept' header of the request.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.2
|
||||
*/
|
||||
public class HeaderContentNegotiationStrategy implements ContentNegotiationStrategy {
|
||||
|
||||
private static final String ACCEPT_HEADER = "Accept";
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @throws HttpMediaTypeNotAcceptableException if the 'Accept' header cannot be parsed.
|
||||
*/
|
||||
public List<MediaType> resolveMediaTypes(NativeWebRequest webRequest) throws HttpMediaTypeNotAcceptableException {
|
||||
String acceptHeader = webRequest.getHeader(ACCEPT_HEADER);
|
||||
try {
|
||||
if (StringUtils.hasText(acceptHeader)) {
|
||||
List<MediaType> mediaTypes = MediaType.parseMediaTypes(acceptHeader);
|
||||
MediaType.sortBySpecificityAndQuality(mediaTypes);
|
||||
return mediaTypes;
|
||||
}
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
throw new HttpMediaTypeNotAcceptableException(
|
||||
"Could not parse accept header [" + acceptHeader + "]: " + ex.getMessage());
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Copyright 2002-2012 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.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
|
||||
/**
|
||||
* An implementation of {@link MediaTypeExtensionsResolver} that maintains a lookup
|
||||
* from extension to MediaType.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.2
|
||||
*/
|
||||
public class MappingMediaTypeExtensionsResolver implements MediaTypeExtensionsResolver {
|
||||
|
||||
private ConcurrentMap<String, MediaType> mediaTypes = new ConcurrentHashMap<String, MediaType>();
|
||||
|
||||
/**
|
||||
* Create an instance with the given mappings between extensions and media types.
|
||||
* @throws IllegalArgumentException if a media type string cannot be parsed
|
||||
*/
|
||||
public MappingMediaTypeExtensionsResolver(Map<String, String> mediaTypes) {
|
||||
if (mediaTypes != null) {
|
||||
for (Map.Entry<String, String> entry : mediaTypes.entrySet()) {
|
||||
String extension = entry.getKey().toLowerCase(Locale.ENGLISH);
|
||||
MediaType mediaType = MediaType.parseMediaType(entry.getValue());
|
||||
this.mediaTypes.put(extension, mediaType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the extensions applicable to the given MediaType.
|
||||
* @return 0 or more extensions, never {@code null}
|
||||
*/
|
||||
public List<String> resolveExtensions(MediaType mediaType) {
|
||||
List<String> result = new ArrayList<String>();
|
||||
for (Entry<String, MediaType> entry : this.mediaTypes.entrySet()) {
|
||||
if (mediaType.includes(entry.getValue())) {
|
||||
result.add(entry.getKey());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the MediaType mapped to the given extension.
|
||||
* @return a MediaType for the key or {@code null}
|
||||
*/
|
||||
public MediaType lookupMediaType(String extension) {
|
||||
return this.mediaTypes.get(extension);
|
||||
}
|
||||
|
||||
/**
|
||||
* Map a MediaType to an extension or ignore if the extensions is already mapped.
|
||||
*/
|
||||
protected void addMapping(String extension, MediaType mediaType) {
|
||||
this.mediaTypes.putIfAbsent(extension, mediaType);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright 2002-2012 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.List;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
|
||||
/**
|
||||
* A strategy for resolving a {@link MediaType} to one or more path extensions.
|
||||
* For example "application/json" to "json".
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.2
|
||||
*/
|
||||
public interface MediaTypeExtensionsResolver {
|
||||
|
||||
/**
|
||||
* Resolve the given media type to a list of path extensions.
|
||||
*
|
||||
* @param mediaType the media type to resolve
|
||||
* @return a list of extensions or an empty list, never {@code null}
|
||||
*/
|
||||
List<String> resolveExtensions(MediaType mediaType);
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* Copyright 2002-2012 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.Map;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
|
||||
/**
|
||||
* A ContentNegotiationStrategy that uses a request parameter to determine what
|
||||
* media types are requested. The default parameter name is {@code format}.
|
||||
* Its value is used to look up the media type in the map given to the constructor.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.2
|
||||
*/
|
||||
public class ParameterContentNegotiationStrategy extends AbstractMappingContentNegotiationStrategy {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(ParameterContentNegotiationStrategy.class);
|
||||
|
||||
private String parameterName = "format";
|
||||
|
||||
/**
|
||||
* Create an instance with the given extension-to-MediaType lookup.
|
||||
* @throws IllegalArgumentException if a media type string cannot be parsed
|
||||
*/
|
||||
public ParameterContentNegotiationStrategy(Map<String, String> mediaTypes) {
|
||||
super(mediaTypes);
|
||||
Assert.notEmpty(mediaTypes, "Cannot look up media types without any mappings");
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the parameter name that can be used to determine the requested media type.
|
||||
* <p>The default parameter name is {@code format}.
|
||||
*/
|
||||
public void setParameterName(String parameterName) {
|
||||
this.parameterName = parameterName;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMediaTypeKey(NativeWebRequest webRequest) {
|
||||
return webRequest.getParameter(this.parameterName);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleMatch(String mediaTypeKey, MediaType mediaType) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Requested media type is '" + mediaType + "' (based on parameter '" +
|
||||
this.parameterName + "'='" + mediaTypeKey + "')");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,187 @@
|
|||
/*
|
||||
* Copyright 2002-2012 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.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.activation.FileTypeMap;
|
||||
import javax.activation.MimetypesFileTypeMap;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.util.UrlPathHelper;
|
||||
import org.springframework.web.util.WebUtils;
|
||||
|
||||
/**
|
||||
* A ContentNegotiationStrategy that uses the path extension of the URL to determine
|
||||
* what media types are requested. The path extension is used as follows:
|
||||
*
|
||||
* <ol>
|
||||
* <li>Look upin the map of media types provided to the constructor
|
||||
* <li>Call to {@link ServletContext#getMimeType(String)}
|
||||
* <li>Use the Java Activation framework
|
||||
* </ol>
|
||||
*
|
||||
* <p>The presence of the Java Activation framework is detected and enabled automatically
|
||||
* but the {@link #setUseJaf(boolean)} property may be used to override that setting.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.2
|
||||
*/
|
||||
public class PathExtensionContentNegotiationStrategy extends AbstractMappingContentNegotiationStrategy {
|
||||
|
||||
private static final boolean JAF_PRESENT =
|
||||
ClassUtils.isPresent("javax.activation.FileTypeMap", PathExtensionContentNegotiationStrategy.class.getClassLoader());
|
||||
|
||||
private static final Log logger = LogFactory.getLog(PathExtensionContentNegotiationStrategy.class);
|
||||
|
||||
private static final UrlPathHelper urlPathHelper = new UrlPathHelper();
|
||||
|
||||
static {
|
||||
urlPathHelper.setUrlDecode(false);
|
||||
}
|
||||
|
||||
private boolean useJaf = JAF_PRESENT;
|
||||
|
||||
/**
|
||||
* Create an instance with the given extension-to-MediaType lookup.
|
||||
* @throws IllegalArgumentException if a media type string cannot be parsed
|
||||
*/
|
||||
public PathExtensionContentNegotiationStrategy(Map<String, String> mediaTypes) {
|
||||
super(mediaTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance without any mappings to start with. Mappings may be added
|
||||
* later on if any extensions are resolved through {@link ServletContext#getMimeType(String)}
|
||||
* or through the Java Activation framework.
|
||||
*/
|
||||
public PathExtensionContentNegotiationStrategy() {
|
||||
super(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate whether to use the Java Activation Framework to map from file extensions to media types.
|
||||
* <p>Default is {@code true}, i.e. the Java Activation Framework is used (if available).
|
||||
*/
|
||||
public void setUseJaf(boolean useJaf) {
|
||||
this.useJaf = useJaf;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMediaTypeKey(NativeWebRequest webRequest) {
|
||||
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
|
||||
if (servletRequest == null) {
|
||||
logger.warn("An HttpServletRequest is required to determine the media type key");
|
||||
return null;
|
||||
}
|
||||
String path = urlPathHelper.getLookupPathForRequest(servletRequest);
|
||||
String filename = WebUtils.extractFullFilenameFromUrlPath(path);
|
||||
String extension = StringUtils.getFilenameExtension(filename);
|
||||
return (StringUtils.hasText(extension)) ? extension.toLowerCase(Locale.ENGLISH) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleMatch(String extension, MediaType mediaType) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Requested media type is '" + mediaType + "' (based on file extension '" + extension + "')");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MediaType handleNoMatch(NativeWebRequest webRequest, String extension) {
|
||||
MediaType mediaType = null;
|
||||
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
|
||||
if (servletRequest != null) {
|
||||
String mimeType = servletRequest.getServletContext().getMimeType("file." + extension);
|
||||
if (StringUtils.hasText(mimeType)) {
|
||||
mediaType = MediaType.parseMediaType(mimeType);
|
||||
}
|
||||
}
|
||||
if ((mediaType == null || MediaType.APPLICATION_OCTET_STREAM.equals(mediaType)) && this.useJaf) {
|
||||
MediaType jafMediaType = JafMediaTypeFactory.getMediaType("file." + extension);
|
||||
if (jafMediaType != null && !MediaType.APPLICATION_OCTET_STREAM.equals(jafMediaType)) {
|
||||
mediaType = jafMediaType;
|
||||
}
|
||||
}
|
||||
return mediaType;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Inner class to avoid hard-coded dependency on JAF.
|
||||
*/
|
||||
private static class JafMediaTypeFactory {
|
||||
|
||||
private static final FileTypeMap fileTypeMap;
|
||||
|
||||
static {
|
||||
fileTypeMap = initFileTypeMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find extended mime.types from the spring-context-support module.
|
||||
*/
|
||||
private static FileTypeMap initFileTypeMap() {
|
||||
Resource resource = new ClassPathResource("org/springframework/mail/javamail/mime.types");
|
||||
if (resource.exists()) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Loading Java Activation Framework FileTypeMap from " + resource);
|
||||
}
|
||||
InputStream inputStream = null;
|
||||
try {
|
||||
inputStream = resource.getInputStream();
|
||||
return new MimetypesFileTypeMap(inputStream);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
// ignore
|
||||
}
|
||||
finally {
|
||||
if (inputStream != null) {
|
||||
try {
|
||||
inputStream.close();
|
||||
}
|
||||
catch (IOException ex) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Loading default Java Activation Framework FileTypeMap");
|
||||
}
|
||||
return FileTypeMap.getDefaultFileTypeMap();
|
||||
}
|
||||
|
||||
public static MediaType getMediaType(String filename) {
|
||||
String mediaType = fileTypeMap.getContentType(filename);
|
||||
return (StringUtils.hasText(mediaType) ? MediaType.parseMediaType(mediaType) : null);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
|
||||
/**
|
||||
* This package contains classes used to determine the requested the media types in a request.
|
||||
*
|
||||
* <p>{@link org.springframework.web.accept.ContentNegotiationStrategy} is the main
|
||||
* abstraction for determining requested {@linkplain org.springframework.http.MediaType media types}
|
||||
* with implementations based on
|
||||
* {@linkplain org.springframework.web.accept.PathExtensionContentNegotiationStrategy path extensions}, a
|
||||
* {@linkplain org.springframework.web.accept.ParameterContentNegotiationStrategy a request parameter}, the
|
||||
* {@linkplain org.springframework.web.accept.HeaderContentNegotiationStrategy 'Accept' header}, or a
|
||||
* {@linkplain org.springframework.web.accept.FixedContentNegotiationStrategy default content type}.
|
||||
*
|
||||
* <p>{@link org.springframework.web.accept.ContentNegotiationManager} is used to delegate to one
|
||||
* ore more of the above strategies in a specific order.
|
||||
*/
|
||||
package org.springframework.web.accept;
|
||||
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* Copyright 2002-2012 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 static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
|
||||
/**
|
||||
* A test fixture with a test sub-class of AbstractMappingContentNegotiationStrategy.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.2
|
||||
*/
|
||||
public class AbstractMappingContentNegotiationStrategyTests {
|
||||
|
||||
@Test
|
||||
public void resolveMediaTypes() {
|
||||
Map<String, String> mapping = Collections.singletonMap("json", "application/json");
|
||||
TestMappingContentNegotiationStrategy strategy = new TestMappingContentNegotiationStrategy("json", mapping);
|
||||
|
||||
List<MediaType> mediaTypes = strategy.resolveMediaTypes(null);
|
||||
|
||||
assertEquals(1, mediaTypes.size());
|
||||
assertEquals("application/json", mediaTypes.get(0).toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveMediaTypesNoMatch() {
|
||||
Map<String, String> mapping = null;
|
||||
TestMappingContentNegotiationStrategy strategy = new TestMappingContentNegotiationStrategy("blah", mapping);
|
||||
|
||||
List<MediaType> mediaTypes = strategy.resolveMediaTypes(null);
|
||||
|
||||
assertEquals(0, mediaTypes.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveMediaTypesNoKey() {
|
||||
Map<String, String> mapping = Collections.singletonMap("json", "application/json");
|
||||
TestMappingContentNegotiationStrategy strategy = new TestMappingContentNegotiationStrategy(null, mapping);
|
||||
|
||||
List<MediaType> mediaTypes = strategy.resolveMediaTypes(null);
|
||||
|
||||
assertEquals(0, mediaTypes.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveMediaTypesHandleNoMatch() {
|
||||
Map<String, String> mapping = null;
|
||||
TestMappingContentNegotiationStrategy strategy = new TestMappingContentNegotiationStrategy("xml", mapping);
|
||||
|
||||
List<MediaType> mediaTypes = strategy.resolveMediaTypes(null);
|
||||
|
||||
assertEquals(1, mediaTypes.size());
|
||||
assertEquals("application/xml", mediaTypes.get(0).toString());
|
||||
}
|
||||
|
||||
|
||||
private static class TestMappingContentNegotiationStrategy extends AbstractMappingContentNegotiationStrategy {
|
||||
|
||||
private final String extension;
|
||||
|
||||
public TestMappingContentNegotiationStrategy(String extension, Map<String, String> mapping) {
|
||||
super(mapping);
|
||||
this.extension = extension;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMediaTypeKey(NativeWebRequest request) {
|
||||
return this.extension;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MediaType handleNoMatch(NativeWebRequest request, String mappingKey) {
|
||||
return "xml".equals(mappingKey) ? MediaType.APPLICATION_XML : null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* Copyright 2002-2012 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 static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.web.HttpMediaTypeNotAcceptableException;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.context.request.ServletWebRequest;
|
||||
|
||||
/**
|
||||
* Test fixture for HeaderContentNegotiationStrategy tests.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class HeaderContentNegotiationStrategyTests {
|
||||
|
||||
private HeaderContentNegotiationStrategy strategy;
|
||||
|
||||
private NativeWebRequest webRequest;
|
||||
|
||||
private MockHttpServletRequest servletRequest;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.strategy = new HeaderContentNegotiationStrategy();
|
||||
this.servletRequest = new MockHttpServletRequest();
|
||||
this.webRequest = new ServletWebRequest(servletRequest );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveMediaTypes() throws Exception {
|
||||
this.servletRequest.addHeader("Accept", "text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c");
|
||||
List<MediaType> mediaTypes = this.strategy.resolveMediaTypes(this.webRequest);
|
||||
|
||||
assertEquals(4, mediaTypes.size());
|
||||
assertEquals("text/html", mediaTypes.get(0).toString());
|
||||
assertEquals("text/x-c", mediaTypes.get(1).toString());
|
||||
assertEquals("text/x-dvi;q=0.8", mediaTypes.get(2).toString());
|
||||
assertEquals("text/plain;q=0.5", mediaTypes.get(3).toString());
|
||||
}
|
||||
|
||||
@Test(expected=HttpMediaTypeNotAcceptableException.class)
|
||||
public void resolveMediaTypesParseError() throws Exception {
|
||||
this.servletRequest.addHeader("Accept", "textplain; q=0.5");
|
||||
this.strategy.resolveMediaTypes(this.webRequest);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright 2002-2012 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 static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.http.MediaType;
|
||||
|
||||
/**
|
||||
* Test fixture for MappingMediaTypeExtensionsResolver.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class MappingMediaTypeExtensionsResolverTests {
|
||||
|
||||
@Test
|
||||
public void resolveExtensions() {
|
||||
Map<String, String> mapping = Collections.singletonMap("json", "application/json");
|
||||
MappingMediaTypeExtensionsResolver resolver = new MappingMediaTypeExtensionsResolver(mapping);
|
||||
List<String> extensions = resolver.resolveExtensions(MediaType.APPLICATION_JSON);
|
||||
|
||||
assertEquals(1, extensions.size());
|
||||
assertEquals("json", extensions.get(0));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* Copyright 2002-2012 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 static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.context.request.ServletWebRequest;
|
||||
|
||||
/**
|
||||
* A test fixture for PathExtensionContentNegotiationStrategy.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.2
|
||||
*/
|
||||
public class PathExtensionContentNegotiationStrategyTests {
|
||||
|
||||
private NativeWebRequest webRequest;
|
||||
|
||||
private MockHttpServletRequest servletRequest;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.servletRequest = new MockHttpServletRequest();
|
||||
this.webRequest = new ServletWebRequest(servletRequest );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveMediaTypesFromMapping() {
|
||||
this.servletRequest.setRequestURI("test.html");
|
||||
PathExtensionContentNegotiationStrategy strategy = new PathExtensionContentNegotiationStrategy();
|
||||
|
||||
List<MediaType> mediaTypes = strategy.resolveMediaTypes(this.webRequest);
|
||||
|
||||
assertEquals(Arrays.asList(new MediaType("text", "html")), mediaTypes);
|
||||
|
||||
strategy = new PathExtensionContentNegotiationStrategy(Collections.singletonMap("HTML", "application/xhtml+xml"));
|
||||
mediaTypes = strategy.resolveMediaTypes(this.webRequest);
|
||||
|
||||
assertEquals(Arrays.asList(new MediaType("application", "xhtml+xml")), mediaTypes);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveMediaTypesFromJaf() {
|
||||
this.servletRequest.setRequestURI("test.xls");
|
||||
PathExtensionContentNegotiationStrategy strategy = new PathExtensionContentNegotiationStrategy();
|
||||
|
||||
List<MediaType> mediaTypes = strategy.resolveMediaTypes(this.webRequest);
|
||||
|
||||
assertEquals(Arrays.asList(new MediaType("application", "vnd.ms-excel")), mediaTypes);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getMediaTypeFromFilenameNoJaf() {
|
||||
this.servletRequest.setRequestURI("test.xls");
|
||||
PathExtensionContentNegotiationStrategy strategy = new PathExtensionContentNegotiationStrategy();
|
||||
strategy.setUseJaf(false);
|
||||
|
||||
List<MediaType> mediaTypes = strategy.resolveMediaTypes(this.webRequest);
|
||||
|
||||
assertEquals(Arrays.asList(MediaType.APPLICATION_OCTET_STREAM), mediaTypes);
|
||||
}
|
||||
|
||||
// SPR-8678
|
||||
|
||||
@Test
|
||||
public void getMediaTypeFilenameWithContextPath() {
|
||||
this.servletRequest.setContextPath("/project-1.0.0.M3");
|
||||
this.servletRequest.setRequestURI("/project-1.0.0.M3/");
|
||||
PathExtensionContentNegotiationStrategy strategy = new PathExtensionContentNegotiationStrategy();
|
||||
|
||||
assertTrue("Context path should be excluded", strategy.resolveMediaTypes(webRequest).isEmpty());
|
||||
|
||||
this.servletRequest.setRequestURI("/project-1.0.0.M3");
|
||||
|
||||
assertTrue("Context path should be excluded", strategy.resolveMediaTypes(webRequest).isEmpty());
|
||||
}
|
||||
|
||||
// SPR-9390
|
||||
|
||||
@Test
|
||||
public void getMediaTypeFilenameWithEncodedURI() {
|
||||
this.servletRequest.setRequestURI("/quo%20vadis%3f.html");
|
||||
PathExtensionContentNegotiationStrategy strategy = new PathExtensionContentNegotiationStrategy();
|
||||
|
||||
List<MediaType> result = strategy.resolveMediaTypes(webRequest);
|
||||
|
||||
assertEquals("Invalid content type", Collections.singletonList(new MediaType("text", "html")), result);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -21,6 +21,7 @@ import javax.servlet.http.HttpServletRequest;
|
|||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.HttpMediaTypeException;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
/**
|
||||
|
|
@ -68,15 +69,12 @@ abstract class AbstractMediaTypeExpression implements Comparable<AbstractMediaTy
|
|||
boolean match = matchMediaType(request);
|
||||
return !isNegated ? match : !match;
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Could not parse media type header: " + ex.getMessage());
|
||||
}
|
||||
catch (HttpMediaTypeException ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract boolean matchMediaType(HttpServletRequest request);
|
||||
protected abstract boolean matchMediaType(HttpServletRequest request) throws HttpMediaTypeException;
|
||||
|
||||
public int compareTo(AbstractMediaTypeExpression other) {
|
||||
return MediaType.SPECIFICITY_COMPARATOR.compare(this.getMediaType(), other.getMediaType());
|
||||
|
|
|
|||
|
|
@ -28,17 +28,18 @@ import javax.servlet.http.HttpServletRequest;
|
|||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.HttpMediaTypeNotSupportedException;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.servlet.mvc.condition.HeadersRequestCondition.HeaderExpression;
|
||||
|
||||
/**
|
||||
* A logical disjunction (' || ') request condition to match a request's
|
||||
* 'Content-Type' header to a list of media type expressions. Two kinds of
|
||||
* media type expressions are supported, which are described in
|
||||
* {@link RequestMapping#consumes()} and {@link RequestMapping#headers()}
|
||||
* where the header name is 'Content-Type'. Regardless of which syntax is
|
||||
* A logical disjunction (' || ') request condition to match a request's
|
||||
* 'Content-Type' header to a list of media type expressions. Two kinds of
|
||||
* media type expressions are supported, which are described in
|
||||
* {@link RequestMapping#consumes()} and {@link RequestMapping#headers()}
|
||||
* where the header name is 'Content-Type'. Regardless of which syntax is
|
||||
* used, the semantics are the same.
|
||||
*
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.1
|
||||
|
|
@ -49,8 +50,8 @@ public final class ConsumesRequestCondition extends AbstractRequestCondition<Con
|
|||
|
||||
/**
|
||||
* Creates a new instance from 0 or more "consumes" expressions.
|
||||
* @param consumes expressions with the syntax described in
|
||||
* {@link RequestMapping#consumes()}; if 0 expressions are provided,
|
||||
* @param consumes expressions with the syntax described in
|
||||
* {@link RequestMapping#consumes()}; if 0 expressions are provided,
|
||||
* the condition will match to every request.
|
||||
*/
|
||||
public ConsumesRequestCondition(String... consumes) {
|
||||
|
|
@ -58,9 +59,9 @@ public final class ConsumesRequestCondition extends AbstractRequestCondition<Con
|
|||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance with "consumes" and "header" expressions.
|
||||
* Creates a new instance with "consumes" and "header" expressions.
|
||||
* "Header" expressions where the header name is not 'Content-Type' or have
|
||||
* no header value defined are ignored. If 0 expressions are provided in
|
||||
* no header value defined are ignored. If 0 expressions are provided in
|
||||
* total, the condition will match to every request
|
||||
* @param consumes as described in {@link RequestMapping#consumes()}
|
||||
* @param headers as described in {@link RequestMapping#headers()}
|
||||
|
|
@ -116,7 +117,7 @@ public final class ConsumesRequestCondition extends AbstractRequestCondition<Con
|
|||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Whether the condition has any media type expressions.
|
||||
*/
|
||||
|
|
@ -135,8 +136,8 @@ public final class ConsumesRequestCondition extends AbstractRequestCondition<Con
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the "other" instance if it has any expressions; returns "this"
|
||||
* instance otherwise. Practically that means a method-level "consumes"
|
||||
* Returns the "other" instance if it has any expressions; returns "this"
|
||||
* instance otherwise. Practically that means a method-level "consumes"
|
||||
* overrides a type-level "consumes" condition.
|
||||
*/
|
||||
public ConsumesRequestCondition combine(ConsumesRequestCondition other) {
|
||||
|
|
@ -144,15 +145,15 @@ public final class ConsumesRequestCondition extends AbstractRequestCondition<Con
|
|||
}
|
||||
|
||||
/**
|
||||
* Checks if any of the contained media type expressions match the given
|
||||
* request 'Content-Type' header and returns an instance that is guaranteed
|
||||
* Checks if any of the contained media type expressions match the given
|
||||
* request 'Content-Type' header and returns an instance that is guaranteed
|
||||
* to contain matching expressions only. The match is performed via
|
||||
* {@link MediaType#includes(MediaType)}.
|
||||
*
|
||||
*
|
||||
* @param request the current request
|
||||
*
|
||||
* @return the same instance if the condition contains no expressions;
|
||||
* or a new condition with matching expressions only;
|
||||
*
|
||||
* @return the same instance if the condition contains no expressions;
|
||||
* or a new condition with matching expressions only;
|
||||
* or {@code null} if no expressions match.
|
||||
*/
|
||||
public ConsumesRequestCondition getMatchingCondition(HttpServletRequest request) {
|
||||
|
|
@ -175,10 +176,10 @@ public final class ConsumesRequestCondition extends AbstractRequestCondition<Con
|
|||
* <li>0 if the two conditions have the same number of expressions
|
||||
* <li>Less than 0 if "this" has more or more specific media type expressions
|
||||
* <li>Greater than 0 if "other" has more or more specific media type expressions
|
||||
* </ul>
|
||||
*
|
||||
* <p>It is assumed that both instances have been obtained via
|
||||
* {@link #getMatchingCondition(HttpServletRequest)} and each instance contains
|
||||
* </ul>
|
||||
*
|
||||
* <p>It is assumed that both instances have been obtained via
|
||||
* {@link #getMatchingCondition(HttpServletRequest)} and each instance contains
|
||||
* the matching consumable media type expression only or is otherwise empty.
|
||||
*/
|
||||
public int compareTo(ConsumesRequestCondition other, HttpServletRequest request) {
|
||||
|
|
@ -197,7 +198,7 @@ public final class ConsumesRequestCondition extends AbstractRequestCondition<Con
|
|||
}
|
||||
|
||||
/**
|
||||
* Parses and matches a single media type expression to a request's 'Content-Type' header.
|
||||
* Parses and matches a single media type expression to a request's 'Content-Type' header.
|
||||
*/
|
||||
static class ConsumeMediaTypeExpression extends AbstractMediaTypeExpression {
|
||||
|
||||
|
|
@ -210,11 +211,17 @@ public final class ConsumesRequestCondition extends AbstractRequestCondition<Con
|
|||
}
|
||||
|
||||
@Override
|
||||
protected boolean matchMediaType(HttpServletRequest request) {
|
||||
MediaType contentType = StringUtils.hasLength(request.getContentType()) ?
|
||||
MediaType.parseMediaType(request.getContentType()) :
|
||||
MediaType.APPLICATION_OCTET_STREAM ;
|
||||
return getMediaType().includes(contentType);
|
||||
protected boolean matchMediaType(HttpServletRequest request) throws HttpMediaTypeNotSupportedException {
|
||||
try {
|
||||
MediaType contentType = StringUtils.hasLength(request.getContentType()) ?
|
||||
MediaType.parseMediaType(request.getContentType()) :
|
||||
MediaType.APPLICATION_OCTET_STREAM;
|
||||
return getMediaType().includes(contentType);
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
throw new HttpMediaTypeNotSupportedException(
|
||||
"Can't parse Content-Type [" + request.getContentType() + "]: " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
* Copyright 2002-2012 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.
|
||||
|
|
@ -17,7 +17,6 @@
|
|||
package org.springframework.web.servlet.mvc.condition;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
|
|
@ -28,17 +27,19 @@ import java.util.Set;
|
|||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.HttpMediaTypeNotAcceptableException;
|
||||
import org.springframework.web.accept.ContentNegotiationManager;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.context.request.ServletWebRequest;
|
||||
import org.springframework.web.servlet.mvc.condition.HeadersRequestCondition.HeaderExpression;
|
||||
|
||||
/**
|
||||
* A logical disjunction (' || ') request condition to match a request's 'Accept' header
|
||||
* to a list of media type expressions. Two kinds of media type expressions are
|
||||
* to a list of media type expressions. Two kinds of media type expressions are
|
||||
* supported, which are described in {@link RequestMapping#produces()} and
|
||||
* {@link RequestMapping#headers()} where the header name is 'Accept'.
|
||||
* {@link RequestMapping#headers()} where the header name is 'Accept'.
|
||||
* Regardless of which syntax is used, the semantics are the same.
|
||||
*
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.1
|
||||
|
|
@ -47,35 +48,45 @@ public final class ProducesRequestCondition extends AbstractRequestCondition<Pro
|
|||
|
||||
private final List<ProduceMediaTypeExpression> expressions;
|
||||
|
||||
private final ContentNegotiationManager contentNegotiationManager;
|
||||
|
||||
/**
|
||||
* Creates a new instance from 0 or more "produces" expressions.
|
||||
* @param produces expressions with the syntax described in {@link RequestMapping#produces()}
|
||||
* if 0 expressions are provided, the condition matches to every request
|
||||
* Creates a new instance from "produces" expressions. If 0 expressions
|
||||
* are provided in total, this condition will match to any request.
|
||||
* @param produces expressions with syntax defined by {@link RequestMapping#produces()}
|
||||
*/
|
||||
public ProducesRequestCondition(String... produces) {
|
||||
this(parseExpressions(produces, null));
|
||||
this(produces, (String[]) null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new instance with "produces" and "header" expressions. "Header" expressions
|
||||
* where the header name is not 'Accept' or have no header value defined are ignored.
|
||||
* If 0 expressions are provided in total, the condition matches to every request
|
||||
* @param produces expressions with the syntax described in {@link RequestMapping#produces()}
|
||||
* @param headers expressions with the syntax described in {@link RequestMapping#headers()}
|
||||
* Creates a new instance with "produces" and "header" expressions. "Header"
|
||||
* expressions where the header name is not 'Accept' or have no header value
|
||||
* defined are ignored. If 0 expressions are provided in total, this condition
|
||||
* will match to any request.
|
||||
* @param produces expressions with syntax defined by {@link RequestMapping#produces()}
|
||||
* @param headers expressions with syntax defined by {@link RequestMapping#headers()}
|
||||
*/
|
||||
public ProducesRequestCondition(String[] produces, String[] headers) {
|
||||
this(parseExpressions(produces, headers));
|
||||
this(produces, headers, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Private constructor accepting parsed media type expressions.
|
||||
* Same as {@link #ProducesRequestCondition(String[], String[])} but also
|
||||
* accepting a {@link ContentNegotiationManager}.
|
||||
* @param produces expressions with syntax defined by {@link RequestMapping#produces()}
|
||||
* @param headers expressions with syntax defined by {@link RequestMapping#headers()}
|
||||
* @param contentNegotiationManager used to determine requested media types
|
||||
*/
|
||||
private ProducesRequestCondition(Collection<ProduceMediaTypeExpression> expressions) {
|
||||
this.expressions = new ArrayList<ProduceMediaTypeExpression>(expressions);
|
||||
public ProducesRequestCondition(String[] produces, String[] headers,
|
||||
ContentNegotiationManager manager) {
|
||||
|
||||
this.expressions = new ArrayList<ProduceMediaTypeExpression>(parseExpressions(produces, headers));
|
||||
Collections.sort(this.expressions);
|
||||
this.contentNegotiationManager = (manager != null) ? manager : new ContentNegotiationManager();
|
||||
}
|
||||
|
||||
private static Set<ProduceMediaTypeExpression> parseExpressions(String[] produces, String[] headers) {
|
||||
private Set<ProduceMediaTypeExpression> parseExpressions(String[] produces, String[] headers) {
|
||||
Set<ProduceMediaTypeExpression> result = new LinkedHashSet<ProduceMediaTypeExpression>();
|
||||
if (headers != null) {
|
||||
for (String header : headers) {
|
||||
|
|
@ -95,6 +106,17 @@ public final class ProducesRequestCondition extends AbstractRequestCondition<Pro
|
|||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Private constructor with already parsed media type expressions.
|
||||
*/
|
||||
private ProducesRequestCondition(Collection<ProduceMediaTypeExpression> expressions,
|
||||
ContentNegotiationManager manager) {
|
||||
|
||||
this.expressions = new ArrayList<ProduceMediaTypeExpression>(expressions);
|
||||
Collections.sort(this.expressions);
|
||||
this.contentNegotiationManager = (manager != null) ? manager : new ContentNegotiationManager();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the contained "produces" expressions.
|
||||
*/
|
||||
|
|
@ -133,8 +155,8 @@ public final class ProducesRequestCondition extends AbstractRequestCondition<Pro
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the "other" instance if it has any expressions; returns "this"
|
||||
* instance otherwise. Practically that means a method-level "produces"
|
||||
* Returns the "other" instance if it has any expressions; returns "this"
|
||||
* instance otherwise. Practically that means a method-level "produces"
|
||||
* overrides a type-level "produces" condition.
|
||||
*/
|
||||
public ProducesRequestCondition combine(ProducesRequestCondition other) {
|
||||
|
|
@ -142,15 +164,15 @@ public final class ProducesRequestCondition extends AbstractRequestCondition<Pro
|
|||
}
|
||||
|
||||
/**
|
||||
* Checks if any of the contained media type expressions match the given
|
||||
* request 'Content-Type' header and returns an instance that is guaranteed
|
||||
* Checks if any of the contained media type expressions match the given
|
||||
* request 'Content-Type' header and returns an instance that is guaranteed
|
||||
* to contain matching expressions only. The match is performed via
|
||||
* {@link MediaType#isCompatibleWith(MediaType)}.
|
||||
*
|
||||
*
|
||||
* @param request the current request
|
||||
*
|
||||
* @return the same instance if there are no expressions;
|
||||
* or a new condition with matching expressions;
|
||||
*
|
||||
* @return the same instance if there are no expressions;
|
||||
* or a new condition with matching expressions;
|
||||
* or {@code null} if no expressions match.
|
||||
*/
|
||||
public ProducesRequestCondition getMatchingCondition(HttpServletRequest request) {
|
||||
|
|
@ -164,58 +186,57 @@ public final class ProducesRequestCondition extends AbstractRequestCondition<Pro
|
|||
iterator.remove();
|
||||
}
|
||||
}
|
||||
return (result.isEmpty()) ? null : new ProducesRequestCondition(result);
|
||||
return (result.isEmpty()) ? null : new ProducesRequestCondition(result, this.contentNegotiationManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares this and another "produces" condition as follows:
|
||||
*
|
||||
*
|
||||
* <ol>
|
||||
* <li>Sort 'Accept' header media types by quality value via
|
||||
* {@link MediaType#sortByQualityValue(List)} and iterate the list.
|
||||
* <li>Get the first index of matching media types in each "produces"
|
||||
* condition first matching with {@link MediaType#equals(Object)} and
|
||||
* condition first matching with {@link MediaType#equals(Object)} and
|
||||
* then with {@link MediaType#includes(MediaType)}.
|
||||
* <li>If a lower index is found, the condition at that index wins.
|
||||
* <li>If both indexes are equal, the media types at the index are
|
||||
* <li>If both indexes are equal, the media types at the index are
|
||||
* compared further with {@link MediaType#SPECIFICITY_COMPARATOR}.
|
||||
* </ol>
|
||||
*
|
||||
* <p>It is assumed that both instances have been obtained via
|
||||
* {@link #getMatchingCondition(HttpServletRequest)} and each instance
|
||||
* contains the matching producible media type expression only or
|
||||
*
|
||||
* <p>It is assumed that both instances have been obtained via
|
||||
* {@link #getMatchingCondition(HttpServletRequest)} and each instance
|
||||
* contains the matching producible media type expression only or
|
||||
* is otherwise empty.
|
||||
*/
|
||||
public int compareTo(ProducesRequestCondition other, HttpServletRequest request) {
|
||||
List<MediaType> acceptedMediaTypes = getAcceptedMediaTypes(request);
|
||||
MediaType.sortByQualityValue(acceptedMediaTypes);
|
||||
try {
|
||||
List<MediaType> acceptedMediaTypes = getAcceptedMediaTypes(request);
|
||||
|
||||
for (MediaType acceptedMediaType : acceptedMediaTypes) {
|
||||
int thisIndex = this.indexOfEqualMediaType(acceptedMediaType);
|
||||
int otherIndex = other.indexOfEqualMediaType(acceptedMediaType);
|
||||
int result = compareMatchingMediaTypes(this, thisIndex, other, otherIndex);
|
||||
if (result != 0) {
|
||||
return result;
|
||||
}
|
||||
thisIndex = this.indexOfIncludedMediaType(acceptedMediaType);
|
||||
otherIndex = other.indexOfIncludedMediaType(acceptedMediaType);
|
||||
result = compareMatchingMediaTypes(this, thisIndex, other, otherIndex);
|
||||
if (result != 0) {
|
||||
return result;
|
||||
for (MediaType acceptedMediaType : acceptedMediaTypes) {
|
||||
int thisIndex = this.indexOfEqualMediaType(acceptedMediaType);
|
||||
int otherIndex = other.indexOfEqualMediaType(acceptedMediaType);
|
||||
int result = compareMatchingMediaTypes(this, thisIndex, other, otherIndex);
|
||||
if (result != 0) {
|
||||
return result;
|
||||
}
|
||||
thisIndex = this.indexOfIncludedMediaType(acceptedMediaType);
|
||||
otherIndex = other.indexOfIncludedMediaType(acceptedMediaType);
|
||||
result = compareMatchingMediaTypes(this, thisIndex, other, otherIndex);
|
||||
if (result != 0) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
catch (HttpMediaTypeNotAcceptableException e) {
|
||||
// should never happen
|
||||
throw new IllegalStateException("Cannot compare without having any requested media types");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static List<MediaType> getAcceptedMediaTypes(HttpServletRequest request) {
|
||||
String acceptHeader = request.getHeader("Accept");
|
||||
if (StringUtils.hasLength(acceptHeader)) {
|
||||
return MediaType.parseMediaTypes(acceptHeader);
|
||||
}
|
||||
else {
|
||||
return Collections.singletonList(MediaType.ALL);
|
||||
}
|
||||
private List<MediaType> getAcceptedMediaTypes(HttpServletRequest request) throws HttpMediaTypeNotAcceptableException {
|
||||
List<MediaType> mediaTypes = this.contentNegotiationManager.resolveMediaTypes(new ServletWebRequest(request));
|
||||
return mediaTypes.isEmpty() ? Collections.singletonList(MediaType.ALL) : mediaTypes;
|
||||
}
|
||||
|
||||
private int indexOfEqualMediaType(MediaType mediaType) {
|
||||
|
|
@ -238,8 +259,9 @@ public final class ProducesRequestCondition extends AbstractRequestCondition<Pro
|
|||
return -1;
|
||||
}
|
||||
|
||||
private static int compareMatchingMediaTypes(ProducesRequestCondition condition1, int index1,
|
||||
ProducesRequestCondition condition2, int index2) {
|
||||
private int compareMatchingMediaTypes(ProducesRequestCondition condition1,
|
||||
int index1, ProducesRequestCondition condition2, int index2) {
|
||||
|
||||
int result = 0;
|
||||
if (index1 != index2) {
|
||||
result = index2 - index1;
|
||||
|
|
@ -254,22 +276,22 @@ public final class ProducesRequestCondition extends AbstractRequestCondition<Pro
|
|||
}
|
||||
|
||||
/**
|
||||
* Return the contained "produces" expressions or if that's empty, a list
|
||||
* with a {@code MediaType_ALL} expression.
|
||||
*/
|
||||
* Return the contained "produces" expressions or if that's empty, a list
|
||||
* with a {@code MediaType_ALL} expression.
|
||||
*/
|
||||
private List<ProduceMediaTypeExpression> getExpressionsToCompare() {
|
||||
return this.expressions.isEmpty() ? DEFAULT_EXPRESSION_LIST : this.expressions;
|
||||
return this.expressions.isEmpty() ? MEDIA_TYPE_ALL_LIST : this.expressions;
|
||||
}
|
||||
|
||||
private static final List<ProduceMediaTypeExpression> DEFAULT_EXPRESSION_LIST =
|
||||
Arrays.asList(new ProduceMediaTypeExpression("*/*"));
|
||||
private final List<ProduceMediaTypeExpression> MEDIA_TYPE_ALL_LIST =
|
||||
Collections.singletonList(new ProduceMediaTypeExpression("*/*"));
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Parses and matches a single media type expression to a request's 'Accept' header.
|
||||
* Parses and matches a single media type expression to a request's 'Accept' header.
|
||||
*/
|
||||
static class ProduceMediaTypeExpression extends AbstractMediaTypeExpression {
|
||||
|
||||
class ProduceMediaTypeExpression extends AbstractMediaTypeExpression {
|
||||
|
||||
ProduceMediaTypeExpression(MediaType mediaType, boolean negated) {
|
||||
super(mediaType, negated);
|
||||
}
|
||||
|
|
@ -279,7 +301,7 @@ public final class ProducesRequestCondition extends AbstractRequestCondition<Pro
|
|||
}
|
||||
|
||||
@Override
|
||||
protected boolean matchMediaType(HttpServletRequest request) {
|
||||
protected boolean matchMediaType(HttpServletRequest request) throws HttpMediaTypeNotAcceptableException {
|
||||
List<MediaType> acceptedMediaTypes = getAcceptedMediaTypes(request);
|
||||
for (MediaType acceptedMediaType : acceptedMediaTypes) {
|
||||
if (getMediaType().isCompatibleWith(acceptedMediaType)) {
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ import javax.servlet.http.HttpServletRequest;
|
|||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
import org.springframework.http.HttpOutputMessage;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
|
|
@ -35,7 +34,9 @@ import org.springframework.http.server.ServletServerHttpRequest;
|
|||
import org.springframework.http.server.ServletServerHttpResponse;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.HttpMediaTypeNotAcceptableException;
|
||||
import org.springframework.web.accept.ContentNegotiationManager;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.context.request.ServletWebRequest;
|
||||
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
|
||||
|
|
@ -52,8 +53,17 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe
|
|||
|
||||
private static final MediaType MEDIA_TYPE_APPLICATION = new MediaType("application");
|
||||
|
||||
private final ContentNegotiationManager contentNegotiationManager;
|
||||
|
||||
protected AbstractMessageConverterMethodProcessor(List<HttpMessageConverter<?>> messageConverters) {
|
||||
this(messageConverters, null);
|
||||
}
|
||||
|
||||
protected AbstractMessageConverterMethodProcessor(List<HttpMessageConverter<?>> messageConverters,
|
||||
ContentNegotiationManager manager) {
|
||||
|
||||
super(messageConverters);
|
||||
this.contentNegotiationManager = (manager != null) ? manager : new ContentNegotiationManager();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -100,14 +110,15 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe
|
|||
|
||||
Class<?> returnValueClass = returnValue.getClass();
|
||||
|
||||
List<MediaType> acceptableMediaTypes = getAcceptableMediaTypes(inputMessage);
|
||||
List<MediaType> producibleMediaTypes = getProducibleMediaTypes(inputMessage.getServletRequest(), returnValueClass);
|
||||
HttpServletRequest servletRequest = inputMessage.getServletRequest();
|
||||
List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(servletRequest);
|
||||
List<MediaType> producibleMediaTypes = getProducibleMediaTypes(servletRequest, returnValueClass);
|
||||
|
||||
Set<MediaType> compatibleMediaTypes = new LinkedHashSet<MediaType>();
|
||||
for (MediaType a : acceptableMediaTypes) {
|
||||
for (MediaType r : requestedMediaTypes) {
|
||||
for (MediaType p : producibleMediaTypes) {
|
||||
if (a.isCompatibleWith(p)) {
|
||||
compatibleMediaTypes.add(getMostSpecificMediaType(a, p));
|
||||
if (r.isCompatibleWith(p)) {
|
||||
compatibleMediaTypes.add(getMostSpecificMediaType(r, p));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -175,17 +186,9 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe
|
|||
}
|
||||
}
|
||||
|
||||
private List<MediaType> getAcceptableMediaTypes(HttpInputMessage inputMessage) {
|
||||
try {
|
||||
List<MediaType> result = inputMessage.getHeaders().getAccept();
|
||||
return result.isEmpty() ? Collections.singletonList(MediaType.ALL) : result;
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Could not parse Accept header: " + ex.getMessage());
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
private List<MediaType> getAcceptableMediaTypes(HttpServletRequest request) throws HttpMediaTypeNotAcceptableException {
|
||||
List<MediaType> mediaTypes = this.contentNegotiationManager.resolveMediaTypes(new ServletWebRequest(request));
|
||||
return mediaTypes.isEmpty() ? Collections.singletonList(MediaType.ALL) : mediaTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import org.springframework.http.converter.HttpMessageConverter;
|
|||
import org.springframework.http.converter.StringHttpMessageConverter;
|
||||
import org.springframework.http.converter.xml.SourceHttpMessageConverter;
|
||||
import org.springframework.http.converter.xml.XmlAwareFormHttpMessageConverter;
|
||||
import org.springframework.web.accept.ContentNegotiationManager;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.context.request.ServletWebRequest;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
|
|
@ -69,6 +70,8 @@ public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExce
|
|||
|
||||
private List<HttpMessageConverter<?>> messageConverters;
|
||||
|
||||
private ContentNegotiationManager contentNegotiationManager = new ContentNegotiationManager();
|
||||
|
||||
private final Map<Class<?>, ExceptionHandlerMethodResolver> exceptionHandlerMethodResolvers =
|
||||
new ConcurrentHashMap<Class<?>, ExceptionHandlerMethodResolver>();
|
||||
|
||||
|
|
@ -182,6 +185,14 @@ public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExce
|
|||
return messageConverters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link ContentNegotiationManager} to use to determine requested media types.
|
||||
* If not set, the default constructor is used.
|
||||
*/
|
||||
public void setContentNegotiationManager(ContentNegotiationManager contentNegotiationManager) {
|
||||
this.contentNegotiationManager = contentNegotiationManager;
|
||||
}
|
||||
|
||||
public void afterPropertiesSet() {
|
||||
if (this.argumentResolvers == null) {
|
||||
List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
|
||||
|
|
@ -223,11 +234,11 @@ public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExce
|
|||
handlers.add(new ModelAndViewMethodReturnValueHandler());
|
||||
handlers.add(new ModelMethodProcessor());
|
||||
handlers.add(new ViewMethodReturnValueHandler());
|
||||
handlers.add(new HttpEntityMethodProcessor(getMessageConverters()));
|
||||
handlers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.contentNegotiationManager));
|
||||
|
||||
// Annotation-based return value types
|
||||
handlers.add(new ModelAttributeMethodProcessor(false));
|
||||
handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters()));
|
||||
handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.contentNegotiationManager));
|
||||
|
||||
// Multi-purpose return value types
|
||||
handlers.add(new ViewNameMethodReturnValueHandler());
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import org.springframework.http.server.ServletServerHttpRequest;
|
|||
import org.springframework.http.server.ServletServerHttpResponse;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.HttpMediaTypeNotSupportedException;
|
||||
import org.springframework.web.accept.ContentNegotiationManager;
|
||||
import org.springframework.web.bind.support.WebDataBinderFactory;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.method.support.ModelAndViewContainer;
|
||||
|
|
@ -56,6 +57,12 @@ public class HttpEntityMethodProcessor extends AbstractMessageConverterMethodPro
|
|||
super(messageConverters);
|
||||
}
|
||||
|
||||
public HttpEntityMethodProcessor(List<HttpMessageConverter<?>> messageConverters,
|
||||
ContentNegotiationManager contentNegotiationManager) {
|
||||
|
||||
super(messageConverters, contentNegotiationManager);
|
||||
}
|
||||
|
||||
public boolean supportsParameter(MethodParameter parameter) {
|
||||
Class<?> parameterType = parameter.getParameterType();
|
||||
return HttpEntity.class.equals(parameterType);
|
||||
|
|
@ -123,7 +130,7 @@ public class HttpEntityMethodProcessor extends AbstractMessageConverterMethodPro
|
|||
if (!entityHeaders.isEmpty()) {
|
||||
outputMessage.getHeaders().putAll(entityHeaders);
|
||||
}
|
||||
|
||||
|
||||
Object body = responseEntity.getBody();
|
||||
if (body != null) {
|
||||
writeWithMessageConverters(body, returnType, inputMessage, outputMessage);
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ import org.springframework.ui.ModelMap;
|
|||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.ReflectionUtils.MethodFilter;
|
||||
import org.springframework.web.accept.ContentNegotiationManager;
|
||||
import org.springframework.web.bind.annotation.InitBinder;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
|
@ -147,6 +148,8 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
|
|||
|
||||
private Long asyncRequestTimeout;
|
||||
|
||||
private ContentNegotiationManager contentNegotiationManager = new ContentNegotiationManager();
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
|
|
@ -410,6 +413,14 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
|
|||
this.asyncRequestTimeout = asyncRequestTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link ContentNegotiationManager} to use to determine requested media types.
|
||||
* If not set, the default constructor is used.
|
||||
*/
|
||||
public void setContentNegotiationManager(ContentNegotiationManager contentNegotiationManager) {
|
||||
this.contentNegotiationManager = contentNegotiationManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p>A {@link ConfigurableBeanFactory} is expected for resolving
|
||||
|
|
@ -525,12 +536,12 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
|
|||
handlers.add(new ModelAndViewMethodReturnValueHandler());
|
||||
handlers.add(new ModelMethodProcessor());
|
||||
handlers.add(new ViewMethodReturnValueHandler());
|
||||
handlers.add(new HttpEntityMethodProcessor(getMessageConverters()));
|
||||
handlers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.contentNegotiationManager));
|
||||
handlers.add(new AsyncMethodReturnValueHandler());
|
||||
|
||||
// Annotation-based return value types
|
||||
handlers.add(new ModelAttributeMethodProcessor(false));
|
||||
handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters()));
|
||||
handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.contentNegotiationManager));
|
||||
|
||||
// Multi-purpose return value types
|
||||
handlers.add(new ViewNameMethodReturnValueHandler());
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ import java.lang.reflect.Method;
|
|||
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.accept.ContentNegotiationManager;
|
||||
import org.springframework.web.accept.HeaderContentNegotiationStrategy;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.servlet.mvc.condition.AbstractRequestCondition;
|
||||
import org.springframework.web.servlet.mvc.condition.CompositeRequestCondition;
|
||||
|
|
@ -48,6 +50,8 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
|
|||
|
||||
private boolean useTrailingSlashMatch = true;
|
||||
|
||||
private ContentNegotiationManager contentNegotiationManager = new ContentNegotiationManager();
|
||||
|
||||
/**
|
||||
* Whether to use suffix pattern match (".*") when matching patterns to
|
||||
* requests. If enabled a method mapped to "/users" also matches to "/users.*".
|
||||
|
|
@ -66,6 +70,14 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
|
|||
this.useTrailingSlashMatch = useTrailingSlashMatch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link ContentNegotiationManager} to use to determine requested media types.
|
||||
* If not set, the default constructor is used.
|
||||
*/
|
||||
public void setContentNegotiationManager(ContentNegotiationManager contentNegotiationManager) {
|
||||
this.contentNegotiationManager = contentNegotiationManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to use suffix pattern matching.
|
||||
*/
|
||||
|
|
@ -79,6 +91,13 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
|
|||
return this.useTrailingSlashMatch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the configured {@link ContentNegotiationManager}.
|
||||
*/
|
||||
public ContentNegotiationManager getContentNegotiationManager() {
|
||||
return contentNegotiationManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* Expects a handler to have a type-level @{@link Controller} annotation.
|
||||
|
|
@ -160,7 +179,7 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
|
|||
new ParamsRequestCondition(annotation.params()),
|
||||
new HeadersRequestCondition(annotation.headers()),
|
||||
new ConsumesRequestCondition(annotation.consumes(), annotation.headers()),
|
||||
new ProducesRequestCondition(annotation.produces(), annotation.headers()),
|
||||
new ProducesRequestCondition(annotation.produces(), annotation.headers(), getContentNegotiationManager()),
|
||||
customCondition);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import org.springframework.http.converter.HttpMessageNotReadableException;
|
|||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.web.HttpMediaTypeNotAcceptableException;
|
||||
import org.springframework.web.HttpMediaTypeNotSupportedException;
|
||||
import org.springframework.web.accept.ContentNegotiationManager;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.WebDataBinder;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
|
|
@ -60,6 +61,12 @@ public class RequestResponseBodyMethodProcessor extends AbstractMessageConverter
|
|||
super(messageConverters);
|
||||
}
|
||||
|
||||
public RequestResponseBodyMethodProcessor(List<HttpMessageConverter<?>> messageConverters,
|
||||
ContentNegotiationManager contentNegotiationManager) {
|
||||
|
||||
super(messageConverters, contentNegotiationManager);
|
||||
}
|
||||
|
||||
public boolean supportsParameter(MethodParameter parameter) {
|
||||
return parameter.hasParameterAnnotation(RequestBody.class);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,48 +16,47 @@
|
|||
|
||||
package org.springframework.web.servlet.view;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import javax.activation.FileTypeMap;
|
||||
import javax.activation.MimetypesFileTypeMap;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactoryUtils;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.core.OrderComparator;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.HttpMediaTypeNotAcceptableException;
|
||||
import org.springframework.web.accept.ContentNegotiationManager;
|
||||
import org.springframework.web.accept.FixedContentNegotiationStrategy;
|
||||
import org.springframework.web.accept.PathExtensionContentNegotiationStrategy;
|
||||
import org.springframework.web.accept.HeaderContentNegotiationStrategy;
|
||||
import org.springframework.web.accept.ParameterContentNegotiationStrategy;
|
||||
import org.springframework.web.accept.ContentNegotiationStrategy;
|
||||
import org.springframework.web.context.request.RequestAttributes;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
import org.springframework.web.context.request.ServletWebRequest;
|
||||
import org.springframework.web.context.support.WebApplicationObjectSupport;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
import org.springframework.web.servlet.SmartView;
|
||||
import org.springframework.web.servlet.View;
|
||||
import org.springframework.web.servlet.ViewResolver;
|
||||
import org.springframework.web.util.UrlPathHelper;
|
||||
import org.springframework.web.util.WebUtils;
|
||||
|
||||
/**
|
||||
* Implementation of {@link ViewResolver} that resolves a view based on the request file name or {@code Accept} header.
|
||||
|
|
@ -69,23 +68,8 @@ import org.springframework.web.util.WebUtils;
|
|||
* property needs to be set to a higher precedence than the others (the default is {@link Ordered#HIGHEST_PRECEDENCE}.)
|
||||
*
|
||||
* <p>This view resolver uses the requested {@linkplain MediaType media type} to select a suitable {@link View} for a
|
||||
* request. This media type is determined by using the following criteria:
|
||||
* <ol>
|
||||
* <li>If the requested path has a file extension and if the {@link #setFavorPathExtension} property is
|
||||
* {@code true}, the {@link #setMediaTypes(Map) mediaTypes} property is inspected for a matching media type.</li>
|
||||
* <li>If the request contains a parameter defining the extension and if the {@link #setFavorParameter}
|
||||
* property is <code>true</code>, the {@link #setMediaTypes(Map) mediaTypes} property is inspected for a matching
|
||||
* media type. The default name of the parameter is <code>format</code> and it can be configured using the
|
||||
* {@link #setParameterName(String) parameterName} property.</li>
|
||||
* <li>If there is no match in the {@link #setMediaTypes(Map) mediaTypes} property and if the Java Activation
|
||||
* Framework (JAF) is both {@linkplain #setUseJaf enabled} and present on the classpath,
|
||||
* {@link FileTypeMap#getContentType(String)} is used instead.</li>
|
||||
* <li>If the previous steps did not result in a media type, and
|
||||
* {@link #setIgnoreAcceptHeader ignoreAcceptHeader} is {@code false}, the request {@code Accept} header is
|
||||
* used.</li>
|
||||
* </ol>
|
||||
*
|
||||
* <p>Once the requested media type has been determined, this resolver queries each delegate view resolver for a
|
||||
* request. The requested media type is determined through the configured {@link ContentNegotiationManager}.
|
||||
* Once the requested media type has been determined, this resolver queries each delegate view resolver for a
|
||||
* {@link View} and determines if the requested media type is {@linkplain MediaType#includes(MediaType) compatible}
|
||||
* with the view's {@linkplain View#getContentType() content type}). The most compatible view is returned.
|
||||
*
|
||||
|
|
@ -107,44 +91,33 @@ import org.springframework.web.util.WebUtils;
|
|||
* @see InternalResourceViewResolver
|
||||
* @see BeanNameViewResolver
|
||||
*/
|
||||
public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport implements ViewResolver, Ordered {
|
||||
public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport implements ViewResolver, Ordered, InitializingBean {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(ContentNegotiatingViewResolver.class);
|
||||
|
||||
private static final String ACCEPT_HEADER = "Accept";
|
||||
|
||||
private static final boolean jafPresent =
|
||||
ClassUtils.isPresent("javax.activation.FileTypeMap", ContentNegotiatingViewResolver.class.getClassLoader());
|
||||
|
||||
private static final UrlPathHelper urlPathHelper = new UrlPathHelper();
|
||||
|
||||
static {
|
||||
urlPathHelper.setUrlDecode(false);
|
||||
}
|
||||
|
||||
private int order = Ordered.HIGHEST_PRECEDENCE;
|
||||
|
||||
private ContentNegotiationManager contentNegotiationManager;
|
||||
|
||||
private boolean favorPathExtension = true;
|
||||
|
||||
private boolean favorParameter = false;
|
||||
|
||||
private String parameterName = "format";
|
||||
private boolean ignoreAcceptHeader = false;
|
||||
private Map<String, String> mediaTypes = new HashMap<String, String>();
|
||||
private Boolean useJaf;
|
||||
private String parameterName;
|
||||
private MediaType defaultContentType;
|
||||
|
||||
private boolean useNotAcceptableStatusCode = false;
|
||||
|
||||
private boolean ignoreAcceptHeader = false;
|
||||
|
||||
private boolean useJaf = jafPresent;
|
||||
|
||||
private ConcurrentMap<String, MediaType> mediaTypes = new ConcurrentHashMap<String, MediaType>();
|
||||
|
||||
private List<View> defaultViews;
|
||||
|
||||
private MediaType defaultContentType;
|
||||
|
||||
private List<ViewResolver> viewResolvers;
|
||||
|
||||
|
||||
public ContentNegotiatingViewResolver() {
|
||||
super();
|
||||
}
|
||||
|
||||
public void setOrder(int order) {
|
||||
this.order = order;
|
||||
}
|
||||
|
|
@ -153,23 +126,45 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
|
|||
return this.order;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link ContentNegotiationManager} to use to determine requested media types.
|
||||
* If not set, the default constructor is used.
|
||||
*/
|
||||
public void setContentNegotiationManager(ContentNegotiationManager contentNegotiationManager) {
|
||||
this.contentNegotiationManager = contentNegotiationManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate whether the extension of the request path should be used to determine the requested media type,
|
||||
* in favor of looking at the {@code Accept} header. The default value is {@code true}.
|
||||
* <p>For instance, when this flag is <code>true</code> (the default), a request for {@code /hotels.pdf}
|
||||
* will result in an {@code AbstractPdfView} being resolved, while the {@code Accept} header can be the
|
||||
* browser-defined {@code text/html,application/xhtml+xml}.
|
||||
*
|
||||
* @deprecated use {@link #setContentNegotiationManager(ContentNegotiationManager)}
|
||||
*/
|
||||
public void setFavorPathExtension(boolean favorPathExtension) {
|
||||
this.favorPathExtension = favorPathExtension;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate whether to use the Java Activation Framework to map from file extensions to media types.
|
||||
* <p>Default is {@code true}, i.e. the Java Activation Framework is used (if available).
|
||||
*
|
||||
* @deprecated use {@link #setContentNegotiationManager(ContentNegotiationManager)}
|
||||
*/
|
||||
public void setUseJaf(boolean useJaf) {
|
||||
this.useJaf = useJaf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate whether a request parameter should be used to determine the requested media type,
|
||||
* in favor of looking at the {@code Accept} header. The default value is {@code false}.
|
||||
* <p>For instance, when this flag is <code>true</code>, a request for {@code /hotels?format=pdf} will result
|
||||
* in an {@code AbstractPdfView} being resolved, while the {@code Accept} header can be the browser-defined
|
||||
* {@code text/html,application/xhtml+xml}.
|
||||
*
|
||||
* @deprecated use {@link #setContentNegotiationManager(ContentNegotiationManager)}
|
||||
*/
|
||||
public void setFavorParameter(boolean favorParameter) {
|
||||
this.favorParameter = favorParameter;
|
||||
|
|
@ -178,6 +173,8 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
|
|||
/**
|
||||
* Set the parameter name that can be used to determine the requested media type if the {@link
|
||||
* #setFavorParameter} property is {@code true}. The default parameter name is {@code format}.
|
||||
*
|
||||
* @deprecated use {@link #setContentNegotiationManager(ContentNegotiationManager)}
|
||||
*/
|
||||
public void setParameterName(String parameterName) {
|
||||
this.parameterName = parameterName;
|
||||
|
|
@ -188,11 +185,35 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
|
|||
* <p>If set to {@code true}, this view resolver will only refer to the file extension and/or
|
||||
* parameter, as indicated by the {@link #setFavorPathExtension favorPathExtension} and
|
||||
* {@link #setFavorParameter favorParameter} properties.
|
||||
*
|
||||
* @deprecated use {@link #setContentNegotiationManager(ContentNegotiationManager)}
|
||||
*/
|
||||
public void setIgnoreAcceptHeader(boolean ignoreAcceptHeader) {
|
||||
this.ignoreAcceptHeader = ignoreAcceptHeader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the mapping from file extensions to media types.
|
||||
* <p>When this mapping is not set or when an extension is not present, this view resolver
|
||||
* will fall back to using a {@link FileTypeMap} when the Java Action Framework is available.
|
||||
*
|
||||
* @deprecated use {@link #setContentNegotiationManager(ContentNegotiationManager)}
|
||||
*/
|
||||
public void setMediaTypes(Map<String, String> mediaTypes) {
|
||||
this.mediaTypes = mediaTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default content type.
|
||||
* <p>This content type will be used when file extension, parameter, nor {@code Accept}
|
||||
* header define a content-type, either through being disabled or empty.
|
||||
*
|
||||
* @deprecated use {@link #setContentNegotiationManager(ContentNegotiationManager)}
|
||||
*/
|
||||
public void setDefaultContentType(MediaType defaultContentType) {
|
||||
this.defaultContentType = defaultContentType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate whether a {@link HttpServletResponse#SC_NOT_ACCEPTABLE 406 Not Acceptable}
|
||||
* status code should be returned if no suitable view can be found.
|
||||
|
|
@ -206,20 +227,6 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
|
|||
this.useNotAcceptableStatusCode = useNotAcceptableStatusCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the mapping from file extensions to media types.
|
||||
* <p>When this mapping is not set or when an extension is not present, this view resolver
|
||||
* will fall back to using a {@link FileTypeMap} when the Java Action Framework is available.
|
||||
*/
|
||||
public void setMediaTypes(Map<String, String> mediaTypes) {
|
||||
Assert.notNull(mediaTypes, "'mediaTypes' must not be null");
|
||||
for (Map.Entry<String, String> entry : mediaTypes.entrySet()) {
|
||||
String extension = entry.getKey().toLowerCase(Locale.ENGLISH);
|
||||
MediaType mediaType = MediaType.parseMediaType(entry.getValue());
|
||||
this.mediaTypes.put(extension, mediaType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default views to use when a more specific view can not be obtained
|
||||
* from the {@link ViewResolver} chain.
|
||||
|
|
@ -228,23 +235,6 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
|
|||
this.defaultViews = defaultViews;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default content type.
|
||||
* <p>This content type will be used when file extension, parameter, nor {@code Accept}
|
||||
* header define a content-type, either through being disabled or empty.
|
||||
*/
|
||||
public void setDefaultContentType(MediaType defaultContentType) {
|
||||
this.defaultContentType = defaultContentType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate whether to use the Java Activation Framework to map from file extensions to media types.
|
||||
* <p>Default is {@code true}, i.e. the Java Activation Framework is used (if available).
|
||||
*/
|
||||
public void setUseJaf(boolean useJaf) {
|
||||
this.useJaf = useJaf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the view resolvers to be wrapped by this view resolver.
|
||||
* <p>If this property is not set, view resolvers will be detected automatically.
|
||||
|
|
@ -283,6 +273,32 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
|
|||
OrderComparator.sort(this.viewResolvers);
|
||||
}
|
||||
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
if (this.contentNegotiationManager == null) {
|
||||
List<ContentNegotiationStrategy> strategies = new ArrayList<ContentNegotiationStrategy>();
|
||||
if (this.favorPathExtension) {
|
||||
PathExtensionContentNegotiationStrategy strategy = new PathExtensionContentNegotiationStrategy(this.mediaTypes);
|
||||
if (this.useJaf != null) {
|
||||
strategy.setUseJaf(this.useJaf);
|
||||
}
|
||||
strategies.add(strategy);
|
||||
}
|
||||
if (this.favorParameter) {
|
||||
ParameterContentNegotiationStrategy strategy = new ParameterContentNegotiationStrategy(this.mediaTypes);
|
||||
strategy.setParameterName(this.parameterName);
|
||||
strategies.add(strategy);
|
||||
}
|
||||
if (!this.ignoreAcceptHeader) {
|
||||
strategies.add(new HeaderContentNegotiationStrategy());
|
||||
}
|
||||
if (this.defaultContentType != null) {
|
||||
strategies.add(new FixedContentNegotiationStrategy(this.defaultContentType));
|
||||
}
|
||||
ContentNegotiationStrategy[] array = strategies.toArray(new ContentNegotiationStrategy[strategies.size()]);
|
||||
this.contentNegotiationManager = new ContentNegotiationManager(array);
|
||||
}
|
||||
}
|
||||
|
||||
public View resolveViewName(String viewName, Locale locale) throws Exception {
|
||||
RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
|
||||
Assert.isInstanceOf(ServletRequestAttributes.class, attrs);
|
||||
|
|
@ -317,69 +333,28 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
|
|||
* @return the list of media types requested, if any
|
||||
*/
|
||||
protected List<MediaType> getMediaTypes(HttpServletRequest request) {
|
||||
if (this.favorPathExtension) {
|
||||
String requestUri = urlPathHelper.getLookupPathForRequest(request);
|
||||
String filename = WebUtils.extractFullFilenameFromUrlPath(requestUri);
|
||||
MediaType mediaType = getMediaTypeFromFilename(filename);
|
||||
if (mediaType != null) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Requested media type is '" + mediaType + "' (based on filename '" + filename + "')");
|
||||
}
|
||||
return Collections.singletonList(mediaType);
|
||||
}
|
||||
}
|
||||
if (this.favorParameter) {
|
||||
if (request.getParameter(this.parameterName) != null) {
|
||||
String parameterValue = request.getParameter(this.parameterName);
|
||||
MediaType mediaType = getMediaTypeFromParameter(parameterValue);
|
||||
if (mediaType != null) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Requested media type is '" + mediaType + "' (based on parameter '" +
|
||||
this.parameterName + "'='" + parameterValue + "')");
|
||||
try {
|
||||
ServletWebRequest webRequest = new ServletWebRequest(request);
|
||||
List<MediaType> acceptableMediaTypes = this.contentNegotiationManager.resolveMediaTypes(webRequest);
|
||||
List<MediaType> producibleMediaTypes = getProducibleMediaTypes(request);
|
||||
Set<MediaType> compatibleMediaTypes = new LinkedHashSet<MediaType>();
|
||||
for (MediaType acceptable : acceptableMediaTypes) {
|
||||
for (MediaType producible : producibleMediaTypes) {
|
||||
if (acceptable.isCompatibleWith(producible)) {
|
||||
compatibleMediaTypes.add(getMostSpecificMediaType(acceptable, producible));
|
||||
}
|
||||
return Collections.singletonList(mediaType);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!this.ignoreAcceptHeader) {
|
||||
String acceptHeader = request.getHeader(ACCEPT_HEADER);
|
||||
if (StringUtils.hasText(acceptHeader)) {
|
||||
try {
|
||||
List<MediaType> acceptableMediaTypes = MediaType.parseMediaTypes(acceptHeader);
|
||||
List<MediaType> producibleMediaTypes = getProducibleMediaTypes(request);
|
||||
Set<MediaType> compatibleMediaTypes = new LinkedHashSet<MediaType>();
|
||||
for (MediaType acceptable : acceptableMediaTypes) {
|
||||
for (MediaType producible : producibleMediaTypes) {
|
||||
if (acceptable.isCompatibleWith(producible)) {
|
||||
compatibleMediaTypes.add(getMostSpecificMediaType(acceptable, producible));
|
||||
}
|
||||
}
|
||||
}
|
||||
List<MediaType> selectedMediaTypes = new ArrayList<MediaType>(compatibleMediaTypes);
|
||||
MediaType.sortBySpecificityAndQuality(selectedMediaTypes);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Requested media types are " + selectedMediaTypes + " based on Accept header types " +
|
||||
"and producible media types " + producibleMediaTypes + ")");
|
||||
}
|
||||
return selectedMediaTypes;
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Could not parse accept header [" + acceptHeader + "]: " + ex.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.defaultContentType != null) {
|
||||
List<MediaType> selectedMediaTypes = new ArrayList<MediaType>(compatibleMediaTypes);
|
||||
MediaType.sortBySpecificityAndQuality(selectedMediaTypes);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Requested media types is " + this.defaultContentType +
|
||||
" (based on defaultContentType property)");
|
||||
logger.debug("Requested media types are " + selectedMediaTypes + " based on Accept header types " +
|
||||
"and producible media types " + producibleMediaTypes + ")");
|
||||
}
|
||||
return Collections.singletonList(this.defaultContentType);
|
||||
return selectedMediaTypes;
|
||||
}
|
||||
else {
|
||||
return Collections.emptyList();
|
||||
catch (HttpMediaTypeNotAcceptableException ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -404,52 +379,6 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
|
|||
return MediaType.SPECIFICITY_COMPARATOR.compare(acceptType, produceType) < 0 ? acceptType : produceType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the {@link MediaType} for the given filename.
|
||||
* <p>The default implementation will check the {@linkplain #setMediaTypes(Map) media types}
|
||||
* property first for a defined mapping. If not present, and if the Java Activation Framework
|
||||
* can be found on the classpath, it will call {@link FileTypeMap#getContentType(String)}
|
||||
* <p>This method can be overridden to provide a different algorithm.
|
||||
* @param filename the current request file name (i.e. {@code hotels.html})
|
||||
* @return the media type, if any
|
||||
*/
|
||||
protected MediaType getMediaTypeFromFilename(String filename) {
|
||||
String extension = StringUtils.getFilenameExtension(filename);
|
||||
if (!StringUtils.hasText(extension)) {
|
||||
return null;
|
||||
}
|
||||
extension = extension.toLowerCase(Locale.ENGLISH);
|
||||
MediaType mediaType = this.mediaTypes.get(extension);
|
||||
if (mediaType == null) {
|
||||
String mimeType = getServletContext().getMimeType(filename);
|
||||
if (StringUtils.hasText(mimeType)) {
|
||||
mediaType = MediaType.parseMediaType(mimeType);
|
||||
}
|
||||
if (this.useJaf && (mediaType == null || MediaType.APPLICATION_OCTET_STREAM.equals(mediaType))) {
|
||||
MediaType jafMediaType = ActivationMediaTypeFactory.getMediaType(filename);
|
||||
if (jafMediaType != null && !MediaType.APPLICATION_OCTET_STREAM.equals(jafMediaType)) {
|
||||
mediaType = jafMediaType;
|
||||
}
|
||||
}
|
||||
if (mediaType != null) {
|
||||
this.mediaTypes.putIfAbsent(extension, mediaType);
|
||||
}
|
||||
}
|
||||
return mediaType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the {@link MediaType} for the given parameter value.
|
||||
* <p>The default implementation will check the {@linkplain #setMediaTypes(Map) media types}
|
||||
* property for a defined mapping.
|
||||
* <p>This method can be overriden to provide a different algorithm.
|
||||
* @param parameterValue the parameter value (i.e. {@code pdf}).
|
||||
* @return the media type, if any
|
||||
*/
|
||||
protected MediaType getMediaTypeFromParameter(String parameterValue) {
|
||||
return this.mediaTypes.get(parameterValue.toLowerCase(Locale.ENGLISH));
|
||||
}
|
||||
|
||||
private List<View> getCandidateViews(String viewName, Locale locale, List<MediaType> requestedMediaTypes)
|
||||
throws Exception {
|
||||
|
||||
|
|
@ -460,7 +389,7 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
|
|||
candidateViews.add(view);
|
||||
}
|
||||
for (MediaType requestedMediaType : requestedMediaTypes) {
|
||||
List<String> extensions = getExtensionsForMediaType(requestedMediaType);
|
||||
List<String> extensions = this.contentNegotiationManager.resolveExtensions(requestedMediaType);
|
||||
for (String extension : extensions) {
|
||||
String viewNameWithExtension = viewName + "." + extension;
|
||||
view = viewResolver.resolveViewName(viewNameWithExtension, locale);
|
||||
|
|
@ -468,7 +397,6 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
|
|||
candidateViews.add(view);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if (!CollectionUtils.isEmpty(this.defaultViews)) {
|
||||
|
|
@ -477,16 +405,6 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
|
|||
return candidateViews;
|
||||
}
|
||||
|
||||
private List<String> getExtensionsForMediaType(MediaType requestedMediaType) {
|
||||
List<String> result = new ArrayList<String>();
|
||||
for (Entry<String, MediaType> entry : this.mediaTypes.entrySet()) {
|
||||
if (requestedMediaType.includes(entry.getValue())) {
|
||||
result.add(entry.getKey());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private View getBestView(List<View> candidateViews, List<MediaType> requestedMediaTypes) {
|
||||
for (View candidateView : candidateViews) {
|
||||
if (candidateView instanceof SmartView) {
|
||||
|
|
@ -528,54 +446,4 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Inner class to avoid hard-coded JAF dependency.
|
||||
*/
|
||||
private static class ActivationMediaTypeFactory {
|
||||
|
||||
private static final FileTypeMap fileTypeMap;
|
||||
|
||||
static {
|
||||
fileTypeMap = loadFileTypeMapFromContextSupportModule();
|
||||
}
|
||||
|
||||
private static FileTypeMap loadFileTypeMapFromContextSupportModule() {
|
||||
// see if we can find the extended mime.types from the context-support module
|
||||
Resource mappingLocation = new ClassPathResource("org/springframework/mail/javamail/mime.types");
|
||||
if (mappingLocation.exists()) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Loading Java Activation Framework FileTypeMap from " + mappingLocation);
|
||||
}
|
||||
InputStream inputStream = null;
|
||||
try {
|
||||
inputStream = mappingLocation.getInputStream();
|
||||
return new MimetypesFileTypeMap(inputStream);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
// ignore
|
||||
}
|
||||
finally {
|
||||
if (inputStream != null) {
|
||||
try {
|
||||
inputStream.close();
|
||||
}
|
||||
catch (IOException ex) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Loading default Java Activation Framework FileTypeMap");
|
||||
}
|
||||
return FileTypeMap.getDefaultFileTypeMap();
|
||||
}
|
||||
|
||||
public static MediaType getMediaType(String filename) {
|
||||
String mediaType = fileTypeMap.getContentType(filename);
|
||||
return (StringUtils.hasText(mediaType) ? MediaType.parseMediaType(mediaType) : null);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ public class ParamsRequestConditionTests {
|
|||
new ParamsRequestCondition("foo=bar").equals(new ParamsRequestCondition("FOO=bar")));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test
|
||||
public void paramPresent() {
|
||||
ParamsRequestCondition condition = new ParamsRequestCondition("foo");
|
||||
|
||||
|
|
@ -96,7 +96,7 @@ public class ParamsRequestConditionTests {
|
|||
@Test
|
||||
public void compareTo() {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
|
||||
|
||||
ParamsRequestCondition condition1 = new ParamsRequestCondition("foo", "bar", "baz");
|
||||
ParamsRequestCondition condition2 = new ParamsRequestCondition("foo", "bar");
|
||||
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ import org.springframework.web.servlet.mvc.condition.ProducesRequestCondition.Pr
|
|||
public class ProducesRequestConditionTests {
|
||||
|
||||
@Test
|
||||
public void producesMatch() {
|
||||
public void match() {
|
||||
ProducesRequestCondition condition = new ProducesRequestCondition("text/plain");
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
|
|
@ -46,7 +46,7 @@ public class ProducesRequestConditionTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void negatedProducesMatch() {
|
||||
public void matchNegated() {
|
||||
ProducesRequestCondition condition = new ProducesRequestCondition("!text/plain");
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
|
|
@ -56,13 +56,13 @@ public class ProducesRequestConditionTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void getProducibleMediaTypesNegatedExpression() {
|
||||
public void getProducibleMediaTypes() {
|
||||
ProducesRequestCondition condition = new ProducesRequestCondition("!application/xml");
|
||||
assertEquals(Collections.emptySet(), condition.getProducibleMediaTypes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void producesWildcardMatch() {
|
||||
public void matchWildcard() {
|
||||
ProducesRequestCondition condition = new ProducesRequestCondition("text/*");
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
|
|
@ -72,7 +72,7 @@ public class ProducesRequestConditionTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void producesMultipleMatch() {
|
||||
public void matchMultiple() {
|
||||
ProducesRequestCondition condition = new ProducesRequestCondition("text/plain", "application/xml");
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
|
|
@ -82,7 +82,7 @@ public class ProducesRequestConditionTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void producesSingleNoMatch() {
|
||||
public void matchSingle() {
|
||||
ProducesRequestCondition condition = new ProducesRequestCondition("text/plain");
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
|
|
@ -92,7 +92,7 @@ public class ProducesRequestConditionTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void producesParseError() {
|
||||
public void matchParseError() {
|
||||
ProducesRequestCondition condition = new ProducesRequestCondition("text/plain");
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
|
|
@ -102,7 +102,7 @@ public class ProducesRequestConditionTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void producesParseErrorWithNegation() {
|
||||
public void matchParseErrorWithNegation() {
|
||||
ProducesRequestCondition condition = new ProducesRequestCondition("!text/plain");
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
|
|
@ -111,6 +111,15 @@ public class ProducesRequestConditionTests {
|
|||
assertNull(condition.getMatchingCondition(request));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchByRequestParameter() {
|
||||
ProducesRequestCondition condition = new ProducesRequestCondition(new String[] {"text/plain"}, new String[] {});
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo.txt");
|
||||
|
||||
assertNotNull(condition.getMatchingCondition(request));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void compareTo() {
|
||||
ProducesRequestCondition html = new ProducesRequestCondition("text/html");
|
||||
|
|
@ -286,7 +295,7 @@ public class ProducesRequestConditionTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void parseProducesAndHeaders() {
|
||||
public void instantiateWithProducesAndHeaderConditions() {
|
||||
String[] produces = new String[] {"text/plain"};
|
||||
String[] headers = new String[]{"foo=bar", "accept=application/xml,application/pdf"};
|
||||
ProducesRequestCondition condition = new ProducesRequestCondition(produces, headers);
|
||||
|
|
@ -312,7 +321,7 @@ public class ProducesRequestConditionTests {
|
|||
|
||||
private void assertConditions(ProducesRequestCondition condition, String... expected) {
|
||||
Collection<ProduceMediaTypeExpression> expressions = condition.getContent();
|
||||
assertEquals("Invalid amount of conditions", expressions.size(), expected.length);
|
||||
assertEquals("Invalid number of conditions", expressions.size(), expected.length);
|
||||
for (String s : expected) {
|
||||
boolean found = false;
|
||||
for (ProduceMediaTypeExpression expr : expressions) {
|
||||
|
|
|
|||
|
|
@ -24,12 +24,10 @@ import static org.junit.Assert.assertEquals;
|
|||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
|
@ -42,6 +40,12 @@ import org.springframework.http.MediaType;
|
|||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.mock.web.MockServletContext;
|
||||
import org.springframework.web.accept.ContentNegotiationManager;
|
||||
import org.springframework.web.accept.FixedContentNegotiationStrategy;
|
||||
import org.springframework.web.accept.HeaderContentNegotiationStrategy;
|
||||
import org.springframework.web.accept.MappingMediaTypeExtensionsResolver;
|
||||
import org.springframework.web.accept.ParameterContentNegotiationStrategy;
|
||||
import org.springframework.web.accept.PathExtensionContentNegotiationStrategy;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
import org.springframework.web.context.support.StaticWebApplicationContext;
|
||||
|
|
@ -75,104 +79,22 @@ public class ContentNegotiatingViewResolverTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void getMediaTypeFromFilenameMediaTypes() {
|
||||
viewResolver.setMediaTypes(Collections.singletonMap("HTML", "application/xhtml+xml"));
|
||||
assertEquals("Invalid content type", new MediaType("application", "xhtml+xml"),
|
||||
viewResolver.getMediaTypeFromFilename("test.html"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getMediaTypeFromFilenameJaf() {
|
||||
assertEquals("Invalid content type", new MediaType("application", "vnd.ms-excel"),
|
||||
viewResolver.getMediaTypeFromFilename("test.xls"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getMediaTypeFromFilenameNoJaf() {
|
||||
viewResolver.setUseJaf(false);
|
||||
assertEquals("Invalid content type", MediaType.APPLICATION_OCTET_STREAM,
|
||||
viewResolver.getMediaTypeFromFilename("test.xls"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getMediaTypeFilename() {
|
||||
request.setRequestURI("/test.html?foo=bar");
|
||||
List<MediaType> result = viewResolver.getMediaTypes(request);
|
||||
assertEquals("Invalid content type", Collections.singletonList(new MediaType("text", "html")), result);
|
||||
viewResolver.setMediaTypes(Collections.singletonMap("html", "application/xhtml+xml"));
|
||||
result = viewResolver.getMediaTypes(request);
|
||||
assertEquals("Invalid content type", Collections.singletonList(new MediaType("application", "xhtml+xml")),
|
||||
result);
|
||||
}
|
||||
|
||||
// SPR-8678
|
||||
|
||||
@Test
|
||||
public void getMediaTypeFilenameWithContextPath() {
|
||||
request.setContextPath("/project-1.0.0.M3");
|
||||
request.setRequestURI("/project-1.0.0.M3/");
|
||||
assertTrue("Context path should be excluded", viewResolver.getMediaTypes(request).isEmpty());
|
||||
request.setRequestURI("/project-1.0.0.M3");
|
||||
assertTrue("Context path should be excluded", viewResolver.getMediaTypes(request).isEmpty());
|
||||
}
|
||||
|
||||
// SPR-9390
|
||||
|
||||
@Test
|
||||
public void getMediaTypeFilenameWithEncodedURI() {
|
||||
request.setRequestURI("/quo%20vadis%3f.html");
|
||||
List<MediaType> result = viewResolver.getMediaTypes(request);
|
||||
assertEquals("Invalid content type", Collections.singletonList(new MediaType("text", "html")), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getMediaTypeParameter() {
|
||||
viewResolver.setFavorParameter(true);
|
||||
viewResolver.setMediaTypes(Collections.singletonMap("html", "application/xhtml+xml"));
|
||||
request.addParameter("format", "html");
|
||||
List<MediaType> result = viewResolver.getMediaTypes(request);
|
||||
assertEquals("Invalid content type", Collections.singletonList(new MediaType("application", "xhtml+xml")),
|
||||
result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getMediaTypeAcceptHeader() {
|
||||
request.addHeader("Accept", "text/html,application/xml;q=0.9,application/xhtml+xml,*/*;q=0.8");
|
||||
List<MediaType> result = viewResolver.getMediaTypes(request);
|
||||
assertEquals("Invalid amount of media types", 4, result.size());
|
||||
assertEquals("Invalid content type", new MediaType("text", "html"), result.get(0));
|
||||
assertEquals("Invalid content type", new MediaType("application", "xhtml+xml"), result.get(1));
|
||||
assertEquals("Invalid content type", new MediaType("application", "xml", Collections.singletonMap("q", "0.9")),
|
||||
result.get(2));
|
||||
assertEquals("Invalid content type", new MediaType("*", "*", Collections.singletonMap("q", "0.8")),
|
||||
result.get(3));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getMediaTypeAcceptHeaderWithProduces() {
|
||||
public void getMediaTypeAcceptHeaderWithProduces() throws Exception {
|
||||
Set<MediaType> producibleTypes = Collections.singleton(MediaType.APPLICATION_XHTML_XML);
|
||||
request.setAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, producibleTypes);
|
||||
request.addHeader("Accept", "text/html,application/xml;q=0.9,application/xhtml+xml,*/*;q=0.8");
|
||||
viewResolver.afterPropertiesSet();
|
||||
List<MediaType> result = viewResolver.getMediaTypes(request);
|
||||
assertEquals("Invalid content type", new MediaType("application", "xhtml+xml"), result.get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getDefaultContentType() {
|
||||
request.addHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
|
||||
viewResolver.setIgnoreAcceptHeader(true);
|
||||
viewResolver.setDefaultContentType(new MediaType("application", "pdf"));
|
||||
List<MediaType> result = viewResolver.getMediaTypes(request);
|
||||
assertEquals("Invalid amount of media types", 1, result.size());
|
||||
assertEquals("Invalid content type", new MediaType("application", "pdf"), result.get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveViewNameWithPathExtension() throws Exception {
|
||||
request.setRequestURI("/test.xls");
|
||||
|
||||
ViewResolver viewResolverMock = createMock(ViewResolver.class);
|
||||
viewResolver.setViewResolvers(Collections.singletonList(viewResolverMock));
|
||||
viewResolver.afterPropertiesSet();
|
||||
|
||||
View viewMock = createMock("application_xls", View.class);
|
||||
|
||||
|
|
@ -195,7 +117,11 @@ public class ContentNegotiatingViewResolverTests {
|
|||
public void resolveViewNameWithAcceptHeader() throws Exception {
|
||||
request.addHeader("Accept", "application/vnd.ms-excel");
|
||||
|
||||
viewResolver.setMediaTypes(Collections.singletonMap("xls", "application/vnd.ms-excel"));
|
||||
Map<String, String> mapping = Collections.singletonMap("xls", "application/vnd.ms-excel");
|
||||
MappingMediaTypeExtensionsResolver extensionsResolver = new MappingMediaTypeExtensionsResolver(mapping);
|
||||
ContentNegotiationManager manager = new ContentNegotiationManager(new HeaderContentNegotiationStrategy());
|
||||
manager.addExtensionsResolver(extensionsResolver);
|
||||
viewResolver.setContentNegotiationManager(manager);
|
||||
|
||||
ViewResolver viewResolverMock = createMock(ViewResolver.class);
|
||||
viewResolver.setViewResolvers(Collections.singletonList(viewResolverMock));
|
||||
|
|
@ -220,6 +146,7 @@ public class ContentNegotiatingViewResolverTests {
|
|||
public void resolveViewNameWithInvalidAcceptHeader() throws Exception {
|
||||
request.addHeader("Accept", "application");
|
||||
|
||||
viewResolver.afterPropertiesSet();
|
||||
View result = viewResolver.resolveViewName("test", Locale.ENGLISH);
|
||||
assertNull(result);
|
||||
}
|
||||
|
|
@ -228,12 +155,15 @@ public class ContentNegotiatingViewResolverTests {
|
|||
public void resolveViewNameWithRequestParameter() throws Exception {
|
||||
request.addParameter("format", "xls");
|
||||
|
||||
viewResolver.setFavorParameter(true);
|
||||
viewResolver.setMediaTypes(Collections.singletonMap("xls", "application/vnd.ms-excel"));
|
||||
Map<String, String> mapping = Collections.singletonMap("xls", "application/vnd.ms-excel");
|
||||
ParameterContentNegotiationStrategy paramStrategy = new ParameterContentNegotiationStrategy(mapping);
|
||||
viewResolver.setContentNegotiationManager(new ContentNegotiationManager(paramStrategy));
|
||||
|
||||
ViewResolver viewResolverMock = createMock(ViewResolver.class);
|
||||
viewResolver.setViewResolvers(Collections.singletonList(viewResolverMock));
|
||||
|
||||
viewResolver.afterPropertiesSet();
|
||||
|
||||
View viewMock = createMock("application_xls", View.class);
|
||||
|
||||
String viewName = "view";
|
||||
|
|
@ -255,13 +185,16 @@ public class ContentNegotiatingViewResolverTests {
|
|||
public void resolveViewNameWithDefaultContentType() throws Exception {
|
||||
request.addHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
|
||||
|
||||
viewResolver.setIgnoreAcceptHeader(true);
|
||||
viewResolver.setDefaultContentType(new MediaType("application", "xml"));
|
||||
MediaType mediaType = new MediaType("application", "xml");
|
||||
FixedContentNegotiationStrategy fixedStrategy = new FixedContentNegotiationStrategy(mediaType);
|
||||
viewResolver.setContentNegotiationManager(new ContentNegotiationManager(fixedStrategy));
|
||||
|
||||
ViewResolver viewResolverMock1 = createMock("viewResolver1", ViewResolver.class);
|
||||
ViewResolver viewResolverMock2 = createMock("viewResolver2", ViewResolver.class);
|
||||
viewResolver.setViewResolvers(Arrays.asList(viewResolverMock1, viewResolverMock2));
|
||||
|
||||
viewResolver.afterPropertiesSet();
|
||||
|
||||
View viewMock1 = createMock("application_xml", View.class);
|
||||
View viewMock2 = createMock("text_html", View.class);
|
||||
|
||||
|
|
@ -289,6 +222,8 @@ public class ContentNegotiatingViewResolverTests {
|
|||
ViewResolver viewResolverMock2 = createMock(ViewResolver.class);
|
||||
viewResolver.setViewResolvers(Arrays.asList(viewResolverMock1, viewResolverMock2));
|
||||
|
||||
viewResolver.afterPropertiesSet();
|
||||
|
||||
View viewMock1 = createMock("application_xml", View.class);
|
||||
View viewMock2 = createMock("text_html", View.class);
|
||||
|
||||
|
|
@ -314,6 +249,8 @@ public class ContentNegotiatingViewResolverTests {
|
|||
public void resolveViewNameAcceptHeaderSortByQuality() throws Exception {
|
||||
request.addHeader("Accept", "text/plain;q=0.5, application/json");
|
||||
|
||||
viewResolver.setContentNegotiationManager(new ContentNegotiationManager(new HeaderContentNegotiationStrategy()));
|
||||
|
||||
ViewResolver htmlViewResolver = createMock(ViewResolver.class);
|
||||
ViewResolver jsonViewResolver = createMock(ViewResolver.class);
|
||||
viewResolver.setViewResolvers(Arrays.asList(htmlViewResolver, jsonViewResolver));
|
||||
|
|
@ -330,7 +267,6 @@ public class ContentNegotiatingViewResolverTests {
|
|||
expect(jsonViewMock.getContentType()).andReturn("application/json").anyTimes();
|
||||
replay(htmlViewResolver, jsonViewResolver, htmlView, jsonViewMock);
|
||||
|
||||
viewResolver.setFavorPathExtension(false);
|
||||
View result = viewResolver.resolveViewName(viewName, locale);
|
||||
assertSame("Invalid view", jsonViewMock, result);
|
||||
|
||||
|
|
@ -353,6 +289,8 @@ public class ContentNegotiatingViewResolverTests {
|
|||
defaultViews.add(viewMock3);
|
||||
viewResolver.setDefaultViews(defaultViews);
|
||||
|
||||
viewResolver.afterPropertiesSet();
|
||||
|
||||
String viewName = "view";
|
||||
Locale locale = Locale.ENGLISH;
|
||||
|
||||
|
|
@ -378,6 +316,8 @@ public class ContentNegotiatingViewResolverTests {
|
|||
ViewResolver viewResolverMock2 = createMock("viewResolver2", ViewResolver.class);
|
||||
viewResolver.setViewResolvers(Arrays.asList(viewResolverMock1, viewResolverMock2));
|
||||
|
||||
viewResolver.afterPropertiesSet();
|
||||
|
||||
View viewMock1 = createMock("application_xml", View.class);
|
||||
View viewMock2 = createMock("text_html", View.class);
|
||||
|
||||
|
|
@ -403,9 +343,10 @@ public class ContentNegotiatingViewResolverTests {
|
|||
public void resolveViewNameFilenameDefaultView() throws Exception {
|
||||
request.setRequestURI("/test.json");
|
||||
|
||||
Map<String, String> mediaTypes = new HashMap<String, String>();
|
||||
mediaTypes.put("json", "application/json");
|
||||
viewResolver.setMediaTypes(mediaTypes);
|
||||
|
||||
Map<String, String> mapping = Collections.singletonMap("json", "application/json");
|
||||
PathExtensionContentNegotiationStrategy pathStrategy = new PathExtensionContentNegotiationStrategy(mapping);
|
||||
viewResolver.setContentNegotiationManager(new ContentNegotiationManager(pathStrategy));
|
||||
|
||||
ViewResolver viewResolverMock1 = createMock(ViewResolver.class);
|
||||
ViewResolver viewResolverMock2 = createMock(ViewResolver.class);
|
||||
|
|
@ -419,6 +360,8 @@ public class ContentNegotiatingViewResolverTests {
|
|||
defaultViews.add(viewMock3);
|
||||
viewResolver.setDefaultViews(defaultViews);
|
||||
|
||||
viewResolver.afterPropertiesSet();
|
||||
|
||||
String viewName = "view";
|
||||
Locale locale = Locale.ENGLISH;
|
||||
|
||||
|
|
@ -445,6 +388,8 @@ public class ContentNegotiatingViewResolverTests {
|
|||
ViewResolver viewResolverMock = createMock(ViewResolver.class);
|
||||
viewResolver.setViewResolvers(Collections.singletonList(viewResolverMock));
|
||||
|
||||
viewResolver.afterPropertiesSet();
|
||||
|
||||
View viewMock = createMock("application_xml", View.class);
|
||||
|
||||
String viewName = "view";
|
||||
|
|
@ -479,6 +424,8 @@ public class ContentNegotiatingViewResolverTests {
|
|||
View jsonView = createMock("application_json", View.class);
|
||||
viewResolver.setDefaultViews(Arrays.asList(jsonView));
|
||||
|
||||
viewResolver.afterPropertiesSet();
|
||||
|
||||
String viewName = "redirect:anotherTest";
|
||||
Locale locale = Locale.ENGLISH;
|
||||
|
||||
|
|
@ -500,6 +447,8 @@ public class ContentNegotiatingViewResolverTests {
|
|||
ViewResolver viewResolverMock = createMock(ViewResolver.class);
|
||||
viewResolver.setViewResolvers(Collections.singletonList(viewResolverMock));
|
||||
|
||||
viewResolver.afterPropertiesSet();
|
||||
|
||||
View viewMock = createMock("application_xml", View.class);
|
||||
|
||||
String viewName = "view";
|
||||
|
|
@ -524,6 +473,8 @@ public class ContentNegotiatingViewResolverTests {
|
|||
ViewResolver viewResolverMock = createMock(ViewResolver.class);
|
||||
viewResolver.setViewResolvers(Collections.singletonList(viewResolverMock));
|
||||
|
||||
viewResolver.afterPropertiesSet();
|
||||
|
||||
View viewMock = createMock("application_xml", View.class);
|
||||
|
||||
String viewName = "view";
|
||||
|
|
@ -553,7 +504,11 @@ public class ContentNegotiatingViewResolverTests {
|
|||
nestedResolver.setApplicationContext(webAppContext);
|
||||
nestedResolver.setViewClass(InternalResourceView.class);
|
||||
viewResolver.setViewResolvers(new ArrayList<ViewResolver>(Arrays.asList(nestedResolver)));
|
||||
viewResolver.setDefaultContentType(MediaType.TEXT_HTML);
|
||||
|
||||
FixedContentNegotiationStrategy fixedStrategy = new FixedContentNegotiationStrategy(MediaType.TEXT_HTML);
|
||||
viewResolver.setContentNegotiationManager(new ContentNegotiationManager(fixedStrategy));
|
||||
|
||||
viewResolver.afterPropertiesSet();
|
||||
|
||||
String viewName = "view";
|
||||
Locale locale = Locale.ENGLISH;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ Changes in version 3.2 M2
|
|||
* raise RestClientException instead of IllegalArgumentException for unknown status codes
|
||||
* add JacksonObjectMapperFactoryBean for configuring a Jackson ObjectMapper in XML
|
||||
* infer return type of parameterized factory methods (SPR-9493)
|
||||
|
||||
* add ContentNegotiationManager/ContentNegotiationStrategy to resolve requested media types
|
||||
|
||||
Changes in version 3.2 M1 (2012-05-28)
|
||||
--------------------------------------
|
||||
|
|
|
|||
Loading…
Reference in New Issue