91
91
* text/html} request {@code Accept} header has the same result.
92
92
*
93
93
* @author Arjen Poutsma
94
- * @author Rostislav Hristov
94
+ * @author Juergen Hoeller
95
+ * @since 3.0
95
96
* @see ViewResolver
96
97
* @see InternalResourceViewResolver
97
98
* @see BeanNameViewResolver
98
- * @since 3.0
99
99
*/
100
100
public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport implements ViewResolver , Ordered {
101
101
102
+ private static final String ACCEPT_HEADER = "Accept" ;
103
+
102
104
private static final boolean jafPresent =
103
105
ClassUtils .isPresent ("javax.activation.FileTypeMap" , ContentNegotiatingViewResolver .class .getClassLoader ());
104
106
105
- private static final String ACCEPT_HEADER = "Accept" ;
107
+ private static final UrlPathHelper urlPathHelper = new UrlPathHelper ();
108
+
106
109
107
- private UrlPathHelper urlPathHelper = new UrlPathHelper () ;
110
+ private int order = Ordered . HIGHEST_PRECEDENCE ;
108
111
109
112
private boolean favorPathExtension = true ;
110
113
111
114
private boolean favorParameter = false ;
112
115
113
- private boolean ignoreAcceptHeader = false ;
114
-
115
116
private String parameterName = "format" ;
116
117
117
- private int order = Ordered . HIGHEST_PRECEDENCE ;
118
+ private boolean ignoreAcceptHeader = false ;
118
119
119
120
private ConcurrentMap <String , MediaType > mediaTypes = new ConcurrentHashMap <String , MediaType >();
120
121
121
122
private List <View > defaultViews ;
122
123
124
+ private MediaType defaultContentType ;
125
+
123
126
private List <ViewResolver > viewResolvers ;
124
127
125
- private MediaType defaultContentType ;
126
128
127
129
public void setOrder (int order ) {
128
130
this .order = order ;
129
131
}
130
132
131
133
public int getOrder () {
132
- return order ;
134
+ return this . order ;
133
135
}
134
136
135
137
/**
136
- * Indicates whether the extension of the request path should be used to determine the requested media type, in favor
137
- * of looking at the {@code Accept} header. The default value is {@code true}.
138
- *
139
- * <p>For instance, when this flag is <code>true</code> (the default), a request for {@code /hotels.pdf} will result in
140
- * an {@code AbstractPdfView} being resolved, while the {@code Accept} header can be the browser-defined {@code
141
- * text/html,application/xhtml+xml}.
138
+ * Indicates whether the extension of the request path should be used to determine the requested media type,
139
+ * in favor of looking at the {@code Accept} header. The default value is {@code true}.
140
+ * <p>For instance, when this flag is <code>true</code> (the default), a request for {@code /hotels.pdf}
141
+ * will result in an {@code AbstractPdfView} being resolved, while the {@code Accept} header can be the
142
+ * browser-defined {@code text/html,application/xhtml+xml}.
142
143
*/
143
144
public void setFavorPathExtension (boolean favorPathExtension ) {
144
145
this .favorPathExtension = favorPathExtension ;
145
146
}
146
147
147
148
/**
148
- * Indicates whether a request parameter should be used to determine the requested media type, in favor of looking at
149
- * the {@code Accept} header. The default value is {@code false}.
150
- *
151
- * <p>For instance, when this flag is <code>true</code>, a request for {@code /hotels?format=pdf} will result in an
152
- * {@code AbstractPdfView} being resolved, while the {@code Accept} header can be the browser-defined {@code
153
- * text/html,application/xhtml+xml}.
149
+ * Indicates whether a request parameter should be used to determine the requested media type,
150
+ * in favor of looking at the {@code Accept} header. The default value is {@code false}.
151
+ * <p>For instance, when this flag is <code>true</code>, a request for {@code /hotels?format=pdf} will result
152
+ * in an {@code AbstractPdfView} being resolved, while the {@code Accept} header can be the browser-defined
153
+ * {@code text/html,application/xhtml+xml}.
154
154
*/
155
155
public void setFavorParameter (boolean favorParameter ) {
156
156
this .favorParameter = favorParameter ;
@@ -166,20 +166,18 @@ public void setParameterName(String parameterName) {
166
166
167
167
/**
168
168
* Indicates whether the HTTP {@code Accept} header should be ignored. Default is {@code false}.
169
- *
170
- * If set to {@code true}, this view resolver will only refer to the file extension and/or paramter, as indicated by
171
- * the {@link #setFavorPathExtension(boolean) favorPathExtension} and {@link #setFavorParameter(boolean)
172
- * favorParameter} properties.
169
+ * If set to {@code true}, this view resolver will only refer to the file extension and/or paramter,
170
+ * as indicated by the {@link #setFavorPathExtension(boolean) favorPathExtension} and
171
+ * {@link #setFavorParameter(boolean) favorParameter} properties.
173
172
*/
174
173
public void setIgnoreAcceptHeader (boolean ignoreAcceptHeader ) {
175
174
this .ignoreAcceptHeader = ignoreAcceptHeader ;
176
175
}
177
176
178
177
/**
179
178
* Sets the mapping from file extensions to media types.
180
- *
181
- * <p>When this mapping is not set or when an extension is not present, this view resolver will fall back to using a
182
- * {@link FileTypeMap} when the Java Action Framework is available.
179
+ * <p>When this mapping is not set or when an extension is not present, this view resolver
180
+ * will fall back to using a {@link FileTypeMap} when the Java Action Framework is available.
183
181
*/
184
182
public void setMediaTypes (Map <String , String > mediaTypes ) {
185
183
Assert .notNull (mediaTypes , "'mediaTypes' must not be null" );
@@ -190,13 +188,17 @@ public void setMediaTypes(Map<String, String> mediaTypes) {
190
188
}
191
189
}
192
190
193
- /** Sets the default views to use when a more specific view can not be obtained from the {@link ViewResolver} chain. */
191
+ /**
192
+ * Sets the default views to use when a more specific view can not be obtained
193
+ * from the {@link ViewResolver} chain.
194
+ */
194
195
public void setDefaultViews (List <View > defaultViews ) {
195
196
this .defaultViews = defaultViews ;
196
197
}
197
198
198
199
/**
199
- * Sets the default content type. This content type will be used when file extension, parameter, nor {@code Accept}
200
+ * Sets the default content type.
201
+ * <p>This content type will be used when file extension, parameter, nor {@code Accept}
200
202
* header define a content-type, either through being disabled or empty.
201
203
*/
202
204
public void setDefaultContentType (MediaType defaultContentType ) {
@@ -205,7 +207,6 @@ public void setDefaultContentType(MediaType defaultContentType) {
205
207
206
208
/**
207
209
* Sets the view resolvers to be wrapped by this view resolver.
208
- *
209
210
* <p>If this property is not set, view resolvers will be detected automatically.
210
211
*/
211
212
public void setViewResolvers (List <ViewResolver > viewResolvers ) {
@@ -214,9 +215,9 @@ public void setViewResolvers(List<ViewResolver> viewResolvers) {
214
215
215
216
@ Override
216
217
protected void initServletContext (ServletContext servletContext ) {
217
- if (viewResolvers == null ) {
218
- Map <String , ViewResolver > matchingBeans = BeanFactoryUtils
219
- .beansOfTypeIncludingAncestors (getApplicationContext (), ViewResolver .class , true , false );
218
+ if (this . viewResolvers == null ) {
219
+ Map <String , ViewResolver > matchingBeans =
220
+ BeanFactoryUtils .beansOfTypeIncludingAncestors (getApplicationContext (), ViewResolver .class );
220
221
this .viewResolvers = new ArrayList <ViewResolver >(matchingBeans .size ());
221
222
for (ViewResolver viewResolver : matchingBeans .values ()) {
222
223
if (this != viewResolver ) {
@@ -233,19 +234,16 @@ protected void initServletContext(ServletContext servletContext) {
233
234
234
235
/**
235
236
* Determines the list of {@link MediaType} for the given {@link HttpServletRequest}.
236
- *
237
237
* <p>The default implementation invokes {@link #getMediaTypeFromFilename(String)} if {@linkplain
238
238
* #setFavorPathExtension(boolean) favorPathExtension} property is <code>true</code>. If the property is
239
- * <code>false</code>, or when a media type cannot be determined from the request path, this method will inspect the
240
- * {@code Accept} header of the request.
241
- *
239
+ * <code>false</code>, or when a media type cannot be determined from the request path, this method will
240
+ * inspect the {@code Accept} header of the request.
242
241
* <p>This method can be overriden to provide a different algorithm.
243
- *
244
242
* @param request the current servlet request
245
243
* @return the list of media types requested, if any
246
244
*/
247
245
protected List <MediaType > getMediaTypes (HttpServletRequest request ) {
248
- if (favorPathExtension ) {
246
+ if (this . favorPathExtension ) {
249
247
String requestUri = urlPathHelper .getRequestUri (request );
250
248
String filename = WebUtils .extractFullFilenameFromUrlPath (requestUri );
251
249
MediaType mediaType = getMediaTypeFromFilename (filename );
@@ -258,23 +256,22 @@ protected List<MediaType> getMediaTypes(HttpServletRequest request) {
258
256
return mediaTypes ;
259
257
}
260
258
}
261
- if (favorParameter ) {
262
- if (request .getParameter (parameterName ) != null ) {
263
- String parameterValue = request .getParameter (parameterName );
259
+ if (this . favorParameter ) {
260
+ if (request .getParameter (this . parameterName ) != null ) {
261
+ String parameterValue = request .getParameter (this . parameterName );
264
262
MediaType mediaType = getMediaTypeFromParameter (parameterValue );
265
263
if (mediaType != null ) {
266
264
if (logger .isDebugEnabled ()) {
267
- logger .debug (
268
- "Requested media type is '" + mediaType + "' (based on parameter '" + parameterName +
269
- "'='" + parameterValue + "')" );
265
+ logger .debug ("Requested media type is '" + mediaType + "' (based on parameter '" +
266
+ this .parameterName + "'='" + parameterValue + "')" );
270
267
}
271
268
List <MediaType > mediaTypes = new ArrayList <MediaType >();
272
269
mediaTypes .add (mediaType );
273
270
return mediaTypes ;
274
271
}
275
272
}
276
273
}
277
- if (!ignoreAcceptHeader ) {
274
+ if (!this . ignoreAcceptHeader ) {
278
275
String acceptHeader = request .getHeader (ACCEPT_HEADER );
279
276
if (StringUtils .hasText (acceptHeader )) {
280
277
List <MediaType > mediaTypes = MediaType .parseMediaTypes (acceptHeader );
@@ -284,8 +281,8 @@ protected List<MediaType> getMediaTypes(HttpServletRequest request) {
284
281
return mediaTypes ;
285
282
}
286
283
}
287
- if (defaultContentType != null ) {
288
- return Collections .singletonList (defaultContentType );
284
+ if (this . defaultContentType != null ) {
285
+ return Collections .singletonList (this . defaultContentType );
289
286
}
290
287
else {
291
288
return Collections .emptyList ();
@@ -294,13 +291,10 @@ protected List<MediaType> getMediaTypes(HttpServletRequest request) {
294
291
295
292
/**
296
293
* Determines the {@link MediaType} for the given filename.
297
- *
298
294
* <p>The default implementation will check the {@linkplain #setMediaTypes(Map) media types} property first for a
299
295
* defined mapping. If not present, and if the Java Activation Framework can be found on the class path, it will call
300
296
* {@link FileTypeMap#getContentType(String)}
301
- *
302
297
* <p>This method can be overriden to provide a different algorithm.
303
- *
304
298
* @param filename the current request file name (i.e. {@code hotels.html})
305
299
* @return the media type, if any
306
300
*/
@@ -310,50 +304,51 @@ protected MediaType getMediaTypeFromFilename(String filename) {
310
304
return null ;
311
305
}
312
306
extension = extension .toLowerCase (Locale .ENGLISH );
313
- MediaType mediaType = mediaTypes .get (extension );
307
+ MediaType mediaType = this . mediaTypes .get (extension );
314
308
if (mediaType == null && jafPresent ) {
315
309
mediaType = ActivationMediaTypeFactory .getMediaType (filename );
316
310
if (mediaType != null ) {
317
- mediaTypes .putIfAbsent (extension , mediaType );
311
+ this . mediaTypes .putIfAbsent (extension , mediaType );
318
312
}
319
313
}
320
314
return mediaType ;
321
315
}
322
316
323
317
/**
324
318
* Determines the {@link MediaType} for the given parameter value.
325
- *
326
- * <p>The default implementation will check the {@linkplain #setMediaTypes(Map) media types} property for a defined
327
- * mapping.
328
- *
319
+ * <p>The default implementation will check the {@linkplain #setMediaTypes(Map) media types}
320
+ * property for a defined mapping.
329
321
* <p>This method can be overriden to provide a different algorithm.
330
- *
331
322
* @param parameterValue the parameter value (i.e. {@code pdf}).
332
323
* @return the media type, if any
333
324
*/
334
325
protected MediaType getMediaTypeFromParameter (String parameterValue ) {
335
- parameterValue = parameterValue .toLowerCase (Locale .ENGLISH );
336
- return mediaTypes .get (parameterValue );
326
+ return this .mediaTypes .get (parameterValue .toLowerCase (Locale .ENGLISH ));
337
327
}
338
328
339
329
public View resolveViewName (String viewName , Locale locale ) throws Exception {
340
330
RequestAttributes attrs = RequestContextHolder .getRequestAttributes ();
341
331
Assert .isInstanceOf (ServletRequestAttributes .class , attrs );
342
332
ServletRequestAttributes servletAttrs = (ServletRequestAttributes ) attrs ;
333
+
343
334
List <MediaType > requestedMediaTypes = getMediaTypes (servletAttrs .getRequest ());
344
- Collections .sort (requestedMediaTypes );
335
+ if (requestedMediaTypes .size () > 1 ) {
336
+ // avoid sorting attempt for empty list and singleton list
337
+ Collections .sort (requestedMediaTypes );
338
+ }
345
339
346
340
SortedMap <MediaType , View > views = new TreeMap <MediaType , View >();
347
341
List <View > candidateViews = new ArrayList <View >();
348
- for (ViewResolver viewResolver : viewResolvers ) {
342
+ for (ViewResolver viewResolver : this . viewResolvers ) {
349
343
View view = viewResolver .resolveViewName (viewName , locale );
350
344
if (view != null ) {
351
345
candidateViews .add (view );
352
346
}
353
347
}
354
- if (!CollectionUtils .isEmpty (defaultViews )) {
355
- candidateViews .addAll (defaultViews );
348
+ if (!CollectionUtils .isEmpty (this . defaultViews )) {
349
+ candidateViews .addAll (this . defaultViews );
356
350
}
351
+
357
352
for (View candidateView : candidateViews ) {
358
353
MediaType viewMediaType = MediaType .parseMediaType (candidateView .getContentType ());
359
354
for (MediaType requestedMediaType : requestedMediaTypes ) {
@@ -365,6 +360,7 @@ public View resolveViewName(String viewName, Locale locale) throws Exception {
365
360
}
366
361
}
367
362
}
363
+
368
364
if (!views .isEmpty ()) {
369
365
MediaType mediaType = views .firstKey ();
370
366
View view = views .get (mediaType );
@@ -378,7 +374,10 @@ public View resolveViewName(String viewName, Locale locale) throws Exception {
378
374
}
379
375
}
380
376
381
- /** Inner class to avoid hard-coded JAF dependency. */
377
+
378
+ /**
379
+ * Inner class to avoid hard-coded JAF dependency.
380
+ */
382
381
private static class ActivationMediaTypeFactory {
383
382
384
383
private static final FileTypeMap fileTypeMap ;
0 commit comments