Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
爱吃血肠
spring-framework
提交
df563dfc
S
spring-framework
项目概览
爱吃血肠
/
spring-framework
通知
1
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
S
spring-framework
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
提交
df563dfc
编写于
2月 06, 2009
作者:
A
Arjen Poutsma
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
SPR-5452: Content negotiation
上级
b96a7a7a
变更
2
隐藏空白更改
内联
并排
Showing
2 changed file
with
454 addition
and
0 deletion
+454
-0
org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/ContentNegotiatingViewResolver.java
...work/web/servlet/view/ContentNegotiatingViewResolver.java
+310
-0
org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/view/ContentNegotiatingViewResolverTests.java
...web/servlet/view/ContentNegotiatingViewResolverTests.java
+144
-0
未找到文件。
org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/ContentNegotiatingViewResolver.java
0 → 100644
浏览文件 @
df563dfc
/*
* Copyright 2002-2009 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.servlet.view
;
import
java.io.IOException
;
import
java.io.InputStream
;
import
java.util.ArrayList
;
import
java.util.Collections
;
import
java.util.HashMap
;
import
java.util.List
;
import
java.util.Locale
;
import
java.util.Map
;
import
java.util.SortedMap
;
import
java.util.TreeMap
;
import
javax.activation.FileTypeMap
;
import
javax.activation.MimetypesFileTypeMap
;
import
javax.servlet.ServletContext
;
import
javax.servlet.http.HttpServletRequest
;
import
org.springframework.beans.factory.BeanFactoryUtils
;
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.util.Assert
;
import
org.springframework.util.ClassUtils
;
import
org.springframework.util.MediaType
;
import
org.springframework.util.StringUtils
;
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.support.WebApplicationObjectSupport
;
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.
*
* <p>The {@code ContentNegotiatingViewResolver} does not resolve views itself, but delegates to other {@link
* ViewResolver}s. By default, these other view resolvers are picked up automatically from the application context,
* though they can also be set explicitely by using the {@link #setViewResolvers(List) viewResolvers} property.
* <strong>Note</strong> that in order for this view resolver to work properly, the {@link #setOrder(int) order}
* 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(boolean)} property is
* <code>true</code>, the {@link #setMediaTypes(Map) mediaTypes} property is inspected for a matching media type.</li>
* <li>If there is no match and if the Java Activation Framework (JAF) is present on the class path,
* {@link FileTypeMap#getContentType(String)} is used.</li>
* <li>If the previous steps did not result in a media type, the request {@code Accept} header is used.</li>
* </ol>
* 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.
*
* <p>For example, if the request path is {@code /view.html}, this view resolver will look for a view that has the
* {@code text/html} content type (based on the {@code html} file extension). A request for {@code /view} with a {@code
* text/html} request {@code Accept} header has the same result.
*
* @author Arjen Poutsma
* @see ViewResolver
* @see InternalResourceViewResolver
* @see BeanNameViewResolver
* @since 3.0
*/
public
class
ContentNegotiatingViewResolver
extends
WebApplicationObjectSupport
implements
ViewResolver
,
Ordered
{
private
static
final
boolean
jafPresent
=
ClassUtils
.
isPresent
(
"javax.activation.FileTypeMap"
,
ContentNegotiatingViewResolver
.
class
.
getClassLoader
());
private
static
final
String
ACCEPT_HEADER
=
"Accept"
;
private
UrlPathHelper
urlPathHelper
=
new
UrlPathHelper
();
private
boolean
favorPathExtension
=
true
;
private
int
order
=
Ordered
.
HIGHEST_PRECEDENCE
;
private
Map
<
String
,
MediaType
>
mediaTypes
=
new
HashMap
<
String
,
MediaType
>();
private
List
<
ViewResolver
>
viewResolvers
;
public
void
setOrder
(
int
order
)
{
this
.
order
=
order
;
}
public
int
getOrder
()
{
return
order
;
}
/**
* Indicates 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.
*
* <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}.
*/
public
void
setFavorPathExtension
(
boolean
favorPathExtension
)
{
this
.
favorPathExtension
=
favorPathExtension
;
}
/**
* Sets 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
);
}
}
/**
* Sets the view resolvers to be wrapped by this view resolver.
*
* <p>If this property is not set, view resolvers will be detected automatically.
*/
public
void
setViewResolvers
(
List
<
ViewResolver
>
viewResolvers
)
{
this
.
viewResolvers
=
viewResolvers
;
}
@Override
protected
void
initServletContext
(
ServletContext
servletContext
)
{
if
(
viewResolvers
==
null
)
{
Map
<
String
,
ViewResolver
>
matchingBeans
=
BeanFactoryUtils
.
beansOfTypeIncludingAncestors
(
getApplicationContext
(),
ViewResolver
.
class
,
true
,
false
);
this
.
viewResolvers
=
new
ArrayList
<
ViewResolver
>(
matchingBeans
.
size
());
for
(
ViewResolver
viewResolver
:
matchingBeans
.
values
())
{
if
(
this
!=
viewResolver
)
{
this
.
viewResolvers
.
add
(
viewResolver
);
}
}
}
if
(
this
.
viewResolvers
.
isEmpty
())
{
logger
.
warn
(
"Did not find any ViewResolvers to delegate to; please configure them using the "
+
"'viewResolvers' property on the ContentNegotiatingViewResolver"
);
}
Collections
.
sort
(
this
.
viewResolvers
,
new
OrderComparator
());
}
/**
* Determines the list of {@link MediaType} for the given {@link HttpServletRequest}.
*
* <p>The default implementation invokes {@link #getMediaTypeFromFilename(String)} if {@linkplain
* #setFavorPathExtension(boolean) favorPathExtension} property is <code>true</code>. If the property is
* <code>false</code>, or when a media type cannot be determined from the request path, this method will inspect the
* {@code Accept} header of the request.
*
* <p>This method can be overriden to provide a different algorithm.
*
* @param request the current servlet request
* @return the list of media types requested, if any
*/
protected
List
<
MediaType
>
getMediaTypes
(
HttpServletRequest
request
)
{
if
(
favorPathExtension
)
{
String
requestUri
=
urlPathHelper
.
getRequestUri
(
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
+
"')"
);
}
List
<
MediaType
>
mediaTypes
=
new
ArrayList
<
MediaType
>();
mediaTypes
.
add
(
mediaType
);
return
mediaTypes
;
}
}
String
acceptHeader
=
request
.
getHeader
(
ACCEPT_HEADER
);
if
(
StringUtils
.
hasText
(
acceptHeader
))
{
List
<
MediaType
>
mediaTypes
=
MediaType
.
parseMediaTypes
(
acceptHeader
);
if
(
logger
.
isDebugEnabled
())
{
logger
.
debug
(
"Requested media types are "
+
mediaTypes
+
" (based on Accept header)"
);
}
return
mediaTypes
;
}
else
{
return
Collections
.
emptyList
();
}
}
/**
* 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 class path, it will call
* {@link FileTypeMap#getContentType(String)}
*
* <p>This method can be overriden 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
=
mediaTypes
.
get
(
extension
);
if
(
mediaType
==
null
&&
jafPresent
)
{
mediaType
=
ActivationMediaTypeFactory
.
getMediaType
(
filename
);
if
(
mediaType
!=
null
)
{
mediaTypes
.
put
(
extension
,
mediaType
);
}
}
return
mediaType
;
}
public
View
resolveViewName
(
String
viewName
,
Locale
locale
)
throws
Exception
{
RequestAttributes
attrs
=
RequestContextHolder
.
getRequestAttributes
();
Assert
.
isInstanceOf
(
ServletRequestAttributes
.
class
,
attrs
);
ServletRequestAttributes
servletAttrs
=
(
ServletRequestAttributes
)
attrs
;
List
<
MediaType
>
requestedMediaTypes
=
getMediaTypes
(
servletAttrs
.
getRequest
());
Collections
.
sort
(
requestedMediaTypes
);
SortedMap
<
MediaType
,
View
>
views
=
new
TreeMap
<
MediaType
,
View
>();
for
(
ViewResolver
viewResolver
:
viewResolvers
)
{
View
view
=
viewResolver
.
resolveViewName
(
viewName
,
locale
);
if
(
view
!=
null
)
{
MediaType
viewMediaType
=
MediaType
.
parseMediaType
(
view
.
getContentType
());
for
(
MediaType
requestedMediaType
:
requestedMediaTypes
)
{
if
(
requestedMediaType
.
includes
(
viewMediaType
))
{
if
(!
views
.
containsKey
(
requestedMediaType
))
{
views
.
put
(
requestedMediaType
,
view
);
break
;
}
}
}
}
}
if
(!
views
.
isEmpty
())
{
MediaType
mediaType
=
views
.
firstKey
();
View
view
=
views
.
get
(
mediaType
);
if
(
logger
.
isDebugEnabled
())
{
logger
.
debug
(
"Returning ["
+
view
+
"] based on requested media type '"
+
mediaType
+
"'"
);
}
return
view
;
}
else
{
return
null
;
}
}
/**
* 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
())
{
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
}
}
}
}
return
FileTypeMap
.
getDefaultFileTypeMap
();
}
public
static
MediaType
getMediaType
(
String
fileName
)
{
String
mediaType
=
fileTypeMap
.
getContentType
(
fileName
);
return
StringUtils
.
hasText
(
mediaType
)
?
MediaType
.
parseMediaType
(
mediaType
)
:
null
;
}
}
}
org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/view/ContentNegotiatingViewResolverTests.java
0 → 100644
浏览文件 @
df563dfc
/*
* Copyright 2002-2009 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.servlet.view
;
import
java.util.ArrayList
;
import
java.util.Collections
;
import
java.util.List
;
import
java.util.Locale
;
import
static
org
.
easymock
.
EasyMock
.*;
import
static
org
.
junit
.
Assert
.
assertEquals
;
import
static
org
.
junit
.
Assert
.
assertSame
;
import
org.junit.Before
;
import
org.junit.Test
;
import
org.springframework.mock.web.MockHttpServletRequest
;
import
org.springframework.util.MediaType
;
import
org.springframework.web.context.request.RequestContextHolder
;
import
org.springframework.web.context.request.ServletRequestAttributes
;
import
org.springframework.web.servlet.View
;
import
org.springframework.web.servlet.ViewResolver
;
/** @author Arjen Poutsma */
public
class
ContentNegotiatingViewResolverTests
{
private
ContentNegotiatingViewResolver
viewResolver
;
@Before
public
void
createViewResolver
()
{
viewResolver
=
new
ContentNegotiatingViewResolver
();
}
@Test
public
void
getMediaTypeFromFilename
()
{
assertEquals
(
"Invalid content type"
,
new
MediaType
(
"text"
,
"html"
),
viewResolver
.
getMediaTypeFromFilename
(
"test.html"
));
viewResolver
.
setMediaTypes
(
Collections
.
singletonMap
(
"HTML"
,
"application/xhtml+xml"
));
assertEquals
(
"Invalid content type"
,
new
MediaType
(
"application"
,
"xhtml+xml"
),
viewResolver
.
getMediaTypeFromFilename
(
"test.html"
));
}
@Test
public
void
getMediaTypeFilename
()
{
MockHttpServletRequest
request
=
new
MockHttpServletRequest
(
"GET"
,
"/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
);
}
@Test
public
void
getMediaTypeAcceptHeader
()
{
MockHttpServletRequest
request
=
new
MockHttpServletRequest
(
"GET"
,
"/test"
);
request
.
addHeader
(
"Accept"
,
"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;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
resolveViewNameAcceptHeader
()
throws
Exception
{
MockHttpServletRequest
request
=
new
MockHttpServletRequest
(
"GET"
,
"/test"
);
request
.
addHeader
(
"Accept"
,
"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
);
RequestContextHolder
.
setRequestAttributes
(
new
ServletRequestAttributes
(
request
));
ViewResolver
viewResolverMock1
=
createMock
(
ViewResolver
.
class
);
ViewResolver
viewResolverMock2
=
createMock
(
ViewResolver
.
class
);
List
<
ViewResolver
>
viewResolverMocks
=
new
ArrayList
<
ViewResolver
>();
viewResolverMocks
.
add
(
viewResolverMock1
);
viewResolverMocks
.
add
(
viewResolverMock2
);
viewResolver
.
setViewResolvers
(
viewResolverMocks
);
View
viewMock1
=
createMock
(
"application_xml"
,
View
.
class
);
View
viewMock2
=
createMock
(
"text_html"
,
View
.
class
);
String
viewName
=
"view"
;
Locale
locale
=
Locale
.
ENGLISH
;
expect
(
viewResolverMock1
.
resolveViewName
(
viewName
,
locale
)).
andReturn
(
viewMock1
);
expect
(
viewResolverMock2
.
resolveViewName
(
viewName
,
locale
)).
andReturn
(
viewMock2
);
expect
(
viewMock1
.
getContentType
()).
andReturn
(
"application/xml"
);
expect
(
viewMock2
.
getContentType
()).
andReturn
(
"text/html;charset=ISO-8859-1"
);
replay
(
viewResolverMock1
,
viewResolverMock2
,
viewMock1
,
viewMock2
);
View
result
=
viewResolver
.
resolveViewName
(
viewName
,
locale
);
assertSame
(
"Invalid view"
,
viewMock2
,
result
);
verify
(
viewResolverMock1
,
viewResolverMock2
,
viewMock1
,
viewMock2
);
}
@Test
public
void
resolveViewNameFilename
()
throws
Exception
{
MockHttpServletRequest
request
=
new
MockHttpServletRequest
(
"GET"
,
"/test.html"
);
RequestContextHolder
.
setRequestAttributes
(
new
ServletRequestAttributes
(
request
));
ViewResolver
viewResolverMock1
=
createMock
(
ViewResolver
.
class
);
ViewResolver
viewResolverMock2
=
createMock
(
ViewResolver
.
class
);
List
<
ViewResolver
>
viewResolverMocks
=
new
ArrayList
<
ViewResolver
>();
viewResolverMocks
.
add
(
viewResolverMock1
);
viewResolverMocks
.
add
(
viewResolverMock2
);
viewResolver
.
setViewResolvers
(
viewResolverMocks
);
View
viewMock1
=
createMock
(
"application_xml"
,
View
.
class
);
View
viewMock2
=
createMock
(
"text_html"
,
View
.
class
);
String
viewName
=
"view"
;
Locale
locale
=
Locale
.
ENGLISH
;
expect
(
viewResolverMock1
.
resolveViewName
(
viewName
,
locale
)).
andReturn
(
viewMock1
);
expect
(
viewResolverMock2
.
resolveViewName
(
viewName
,
locale
)).
andReturn
(
viewMock2
);
expect
(
viewMock1
.
getContentType
()).
andReturn
(
"application/xml"
);
expect
(
viewMock2
.
getContentType
()).
andReturn
(
"text/html;charset=ISO-8859-1"
);
replay
(
viewResolverMock1
,
viewResolverMock2
,
viewMock1
,
viewMock2
);
View
result
=
viewResolver
.
resolveViewName
(
viewName
,
locale
);
assertSame
(
"Invalid view"
,
viewMock2
,
result
);
verify
(
viewResolverMock1
,
viewResolverMock2
,
viewMock1
,
viewMock2
);
}
}
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录