001/* 002 * Copyright 2002-2017 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.web.servlet.config.annotation; 018 019import java.util.ArrayList; 020import java.util.Arrays; 021import java.util.Collections; 022import java.util.List; 023 024import org.springframework.beans.factory.BeanFactoryUtils; 025import org.springframework.beans.factory.BeanInitializationException; 026import org.springframework.context.ApplicationContext; 027import org.springframework.core.Ordered; 028import org.springframework.util.CollectionUtils; 029import org.springframework.util.ObjectUtils; 030import org.springframework.web.accept.ContentNegotiationManager; 031import org.springframework.web.servlet.View; 032import org.springframework.web.servlet.ViewResolver; 033import org.springframework.web.servlet.view.BeanNameViewResolver; 034import org.springframework.web.servlet.view.ContentNegotiatingViewResolver; 035import org.springframework.web.servlet.view.InternalResourceViewResolver; 036import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer; 037import org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver; 038import org.springframework.web.servlet.view.groovy.GroovyMarkupConfigurer; 039import org.springframework.web.servlet.view.groovy.GroovyMarkupViewResolver; 040import org.springframework.web.servlet.view.script.ScriptTemplateConfigurer; 041import org.springframework.web.servlet.view.script.ScriptTemplateViewResolver; 042import org.springframework.web.servlet.view.tiles3.TilesConfigurer; 043import org.springframework.web.servlet.view.tiles3.TilesViewResolver; 044 045/** 046 * Assist with the configuration of a chain of 047 * {@link org.springframework.web.servlet.ViewResolver ViewResolver} instances. 048 * This class is expected to be used via {@link WebMvcConfigurer#configureViewResolvers}. 049 * 050 * @author Sebastien Deleuze 051 * @author Rossen Stoyanchev 052 * @since 4.1 053 */ 054public class ViewResolverRegistry { 055 056 private ContentNegotiationManager contentNegotiationManager; 057 058 private ApplicationContext applicationContext; 059 060 private ContentNegotiatingViewResolver contentNegotiatingResolver; 061 062 private final List<ViewResolver> viewResolvers = new ArrayList<ViewResolver>(4); 063 064 private Integer order; 065 066 067 /** 068 * Class constructor with {@link ContentNegotiationManager} and {@link ApplicationContext}. 069 * @since 4.3.12 070 */ 071 public ViewResolverRegistry(ContentNegotiationManager contentNegotiationManager, ApplicationContext context) { 072 this.contentNegotiationManager = contentNegotiationManager; 073 this.applicationContext = context; 074 } 075 076 @Deprecated 077 public ViewResolverRegistry() { 078 } 079 080 081 /** 082 * Whether any view resolvers have been registered. 083 */ 084 public boolean hasRegistrations() { 085 return (this.contentNegotiatingResolver != null || !this.viewResolvers.isEmpty()); 086 } 087 088 /** 089 * Enable use of a {@link ContentNegotiatingViewResolver} to front all other 090 * configured view resolvers and select among all selected Views based on 091 * media types requested by the client (e.g. in the Accept header). 092 * <p>If invoked multiple times the provided default views will be added to 093 * any other default views that may have been configured already. 094 * @see ContentNegotiatingViewResolver#setDefaultViews 095 */ 096 public void enableContentNegotiation(View... defaultViews) { 097 initContentNegotiatingViewResolver(defaultViews); 098 } 099 100 /** 101 * Enable use of a {@link ContentNegotiatingViewResolver} to front all other 102 * configured view resolvers and select among all selected Views based on 103 * media types requested by the client (e.g. in the Accept header). 104 * <p>If invoked multiple times the provided default views will be added to 105 * any other default views that may have been configured already. 106 * @see ContentNegotiatingViewResolver#setDefaultViews 107 */ 108 public void enableContentNegotiation(boolean useNotAcceptableStatus, View... defaultViews) { 109 initContentNegotiatingViewResolver(defaultViews); 110 this.contentNegotiatingResolver.setUseNotAcceptableStatusCode(useNotAcceptableStatus); 111 } 112 113 private void initContentNegotiatingViewResolver(View[] defaultViews) { 114 // ContentNegotiatingResolver in the registry: elevate its precedence! 115 this.order = (this.order != null ? this.order : Ordered.HIGHEST_PRECEDENCE); 116 117 if (this.contentNegotiatingResolver != null) { 118 if (!ObjectUtils.isEmpty(defaultViews)) { 119 if (!CollectionUtils.isEmpty(this.contentNegotiatingResolver.getDefaultViews())) { 120 List<View> views = new ArrayList<View>(this.contentNegotiatingResolver.getDefaultViews()); 121 views.addAll(Arrays.asList(defaultViews)); 122 this.contentNegotiatingResolver.setDefaultViews(views); 123 } 124 } 125 } 126 else { 127 this.contentNegotiatingResolver = new ContentNegotiatingViewResolver(); 128 this.contentNegotiatingResolver.setDefaultViews(Arrays.asList(defaultViews)); 129 this.contentNegotiatingResolver.setViewResolvers(this.viewResolvers); 130 this.contentNegotiatingResolver.setContentNegotiationManager(this.contentNegotiationManager); 131 } 132 } 133 134 /** 135 * Register JSP view resolver using a default view name prefix of "/WEB-INF/" 136 * and a default suffix of ".jsp". 137 * <p>When this method is invoked more than once, each call will register a 138 * new ViewResolver instance. Note that since it's not easy to determine 139 * if a JSP exists without forwarding to it, using multiple JSP-based view 140 * resolvers only makes sense in combination with the "viewNames" property 141 * on the resolver indicating which view names are handled by which resolver. 142 */ 143 public UrlBasedViewResolverRegistration jsp() { 144 return jsp("/WEB-INF/", ".jsp"); 145 } 146 147 /** 148 * Register JSP view resolver with the specified prefix and suffix. 149 * <p>When this method is invoked more than once, each call will register a 150 * new ViewResolver instance. Note that since it's not easy to determine 151 * if a JSP exists without forwarding to it, using multiple JSP-based view 152 * resolvers only makes sense in combination with the "viewNames" property 153 * on the resolver indicating which view names are handled by which resolver. 154 */ 155 public UrlBasedViewResolverRegistration jsp(String prefix, String suffix) { 156 InternalResourceViewResolver resolver = new InternalResourceViewResolver(); 157 resolver.setPrefix(prefix); 158 resolver.setSuffix(suffix); 159 this.viewResolvers.add(resolver); 160 return new UrlBasedViewResolverRegistration(resolver); 161 } 162 163 /** 164 * Register Tiles 3.x view resolver. 165 * <p><strong>Note</strong> that you must also configure Tiles by adding a 166 * {@link org.springframework.web.servlet.view.tiles3.TilesConfigurer} bean. 167 */ 168 public UrlBasedViewResolverRegistration tiles() { 169 if (!checkBeanOfType(TilesConfigurer.class)) { 170 throw new BeanInitializationException("In addition to a Tiles view resolver " + 171 "there must also be a single TilesConfigurer bean in this web application context " + 172 "(or its parent)."); 173 } 174 TilesRegistration registration = new TilesRegistration(); 175 this.viewResolvers.add(registration.getViewResolver()); 176 return registration; 177 } 178 179 /** 180 * Register a FreeMarker view resolver with an empty default view name 181 * prefix and a default suffix of ".ftl". 182 * <p><strong>Note</strong> that you must also configure FreeMarker by adding a 183 * {@link org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer} bean. 184 */ 185 public UrlBasedViewResolverRegistration freeMarker() { 186 if (!checkBeanOfType(FreeMarkerConfigurer.class)) { 187 throw new BeanInitializationException("In addition to a FreeMarker view resolver " + 188 "there must also be a single FreeMarkerConfig bean in this web application context " + 189 "(or its parent): FreeMarkerConfigurer is the usual implementation. " + 190 "This bean may be given any name."); 191 } 192 FreeMarkerRegistration registration = new FreeMarkerRegistration(); 193 this.viewResolvers.add(registration.getViewResolver()); 194 return registration; 195 } 196 197 /** 198 * Register Velocity view resolver with an empty default view name 199 * prefix and a default suffix of ".vm". 200 * <p><strong>Note</strong> that you must also configure Velocity by adding a 201 * {@link org.springframework.web.servlet.view.velocity.VelocityConfigurer} bean. 202 * @deprecated as of Spring 4.3, in favor of FreeMarker 203 */ 204 @Deprecated 205 public UrlBasedViewResolverRegistration velocity() { 206 if (!checkBeanOfType(org.springframework.web.servlet.view.velocity.VelocityConfigurer.class)) { 207 throw new BeanInitializationException("In addition to a Velocity view resolver " + 208 "there must also be a single VelocityConfig bean in this web application context " + 209 "(or its parent): VelocityConfigurer is the usual implementation. " + 210 "This bean may be given any name."); 211 } 212 VelocityRegistration registration = new VelocityRegistration(); 213 this.viewResolvers.add(registration.getViewResolver()); 214 return registration; 215 } 216 217 /** 218 * Register a Groovy markup view resolver with an empty default view name 219 * prefix and a default suffix of ".tpl". 220 */ 221 public UrlBasedViewResolverRegistration groovy() { 222 if (!checkBeanOfType(GroovyMarkupConfigurer.class)) { 223 throw new BeanInitializationException("In addition to a Groovy markup view resolver " + 224 "there must also be a single GroovyMarkupConfig bean in this web application context " + 225 "(or its parent): GroovyMarkupConfigurer is the usual implementation. " + 226 "This bean may be given any name."); 227 } 228 GroovyMarkupRegistration registration = new GroovyMarkupRegistration(); 229 this.viewResolvers.add(registration.getViewResolver()); 230 return registration; 231 } 232 233 /** 234 * Register a script template view resolver with an empty default view name prefix and suffix. 235 * @since 4.2 236 */ 237 public UrlBasedViewResolverRegistration scriptTemplate() { 238 if (!checkBeanOfType(ScriptTemplateConfigurer.class)) { 239 throw new BeanInitializationException("In addition to a script template view resolver " + 240 "there must also be a single ScriptTemplateConfig bean in this web application context " + 241 "(or its parent): ScriptTemplateConfigurer is the usual implementation. " + 242 "This bean may be given any name."); 243 } 244 ScriptRegistration registration = new ScriptRegistration(); 245 this.viewResolvers.add(registration.getViewResolver()); 246 return registration; 247 } 248 249 /** 250 * Register a bean name view resolver that interprets view names as the names 251 * of {@link org.springframework.web.servlet.View} beans. 252 */ 253 public void beanName() { 254 BeanNameViewResolver resolver = new BeanNameViewResolver(); 255 this.viewResolvers.add(resolver); 256 } 257 258 /** 259 * Register a {@link ViewResolver} bean instance. This may be useful to 260 * configure a custom (or 3rd party) resolver implementation. It may also be 261 * used as an alternative to other registration methods in this class when 262 * they don't expose some more advanced property that needs to be set. 263 */ 264 public void viewResolver(ViewResolver viewResolver) { 265 if (viewResolver instanceof ContentNegotiatingViewResolver) { 266 throw new BeanInitializationException( 267 "addViewResolver cannot be used to configure a ContentNegotiatingViewResolver. " + 268 "Please use the method enableContentNegotiation instead."); 269 } 270 this.viewResolvers.add(viewResolver); 271 } 272 273 /** 274 * ViewResolver's registered through this registry are encapsulated in an 275 * instance of {@link org.springframework.web.servlet.view.ViewResolverComposite 276 * ViewResolverComposite} and follow the order of registration. 277 * This property determines the order of the ViewResolverComposite itself 278 * relative to any additional ViewResolver's (not registered here) present in 279 * the Spring configuration 280 * <p>By default this property is not set, which means the resolver is ordered 281 * at {@link Ordered#LOWEST_PRECEDENCE} unless content negotiation is enabled 282 * in which case the order (if not set explicitly) is changed to 283 * {@link Ordered#HIGHEST_PRECEDENCE}. 284 */ 285 public void order(int order) { 286 this.order =urceLineNo">287 } 288 289 290 protected int getOrder() { 291 return (this.order != null ? this.order : Ordered.LOWEST_PRECEDENCE); 292 } 293 294 protected List<ViewResolver> getViewResolvers() { 295 if (this.contentNegotiatingResolver != null) { 296 return Collections.<ViewResolver>singletonList(this.contentNegotiatingResolver); 297 } 298 else { 299 return this.viewResolvers; 300 } 301 } 302 303 private boolean checkBeanOfType(Class<?> beanType) { 304 return (this.applicationContext == null || 305 !ObjectUtils.isEmpty(BeanFactoryUtils.beanNamesForTypeIncludingAncestors( 306 this.applicationContext, beanType, false, false))); 307 } 308 309 @Deprecated 310 protected boolean hasBeanOfType(Class<?> beanType) { 311 return !ObjectUtils.isEmpty(BeanFactoryUtils.beanNamesForTypeIncludingAncestors( 312 this.applicationContext, beanType, false, false)); 313 } 314 315 @Deprecated 316 protected void setContentNegotiationManager(ContentNegotiationManager contentNegotiationManager) { 317 this.contentNegotiationManager = contentNegotiationManager; 318 } 319 320 @Deprecated 321 protected void setApplicationContext(ApplicationContext applicationContext) { 322 this.applicationContext = applicationContext; 323 } 324 325 326 private static class TilesRegistration extends UrlBasedViewResolverRegistration { 327 328 public TilesRegistration() { 329 super(new TilesViewResolver()); 330 } 331 } 332 333 334 private static class VelocityRegistration extends UrlBasedViewResolverRegistration { 335 336 @SuppressWarnings("deprecation") 337 public VelocityRegistration() { 338 super(new org.springframework.web.servlet.view.velocity.VelocityViewResolver()); 339 getViewResolver().setSuffix(".vm"); 340 } 341 } 342 343 344 private static class FreeMarkerRegistration extends UrlBasedViewResolverRegistration { 345 346 public FreeMarkerRegistration() { 347 super(new FreeMarkerViewResolver()); 348 getViewResolver().setSuffix(".ftl"); 349 } 350 } 351 352 353 private static class GroovyMarkupRegistration extends UrlBasedViewResolverRegistration { 354 355 public GroovyMarkupRegistration() { 356 super(new GroovyMarkupViewResolver()); 357 getViewResolver().setSuffix(".tpl"); 358 } 359 } 360 361 362 private static class ScriptRegistration extends UrlBasedViewResolverRegistration { 363 364 public ScriptRegistration() { 365 super(new ScriptTemplateViewResolver()); 366 getViewResolver(); 367 } 368 } 369 370}