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.view.tiles3; 018 019import java.util.Collection; 020import java.util.LinkedList; 021import java.util.List; 022 023import javax.el.ArrayELResolver; 024import javax.el.BeanELResolver; 025import javax.el.CompositeELResolver; 026import javax.el.ListELResolver; 027import javax.el.MapELResolver; 028import javax.el.ResourceBundleELResolver; 029import javax.servlet.ServletContext; 030import javax.servlet.jsp.JspFactory; 031 032import org.apache.commons.logging.Log; 033import org.apache.commons.logging.LogFactory; 034import org.apache.tiles.TilesContainer; 035import org.apache.tiles.TilesException; 036import org.apache.tiles.definition.DefinitionsFactory; 037import org.apache.tiles.definition.DefinitionsReader; 038import org.apache.tiles.definition.dao.BaseLocaleUrlDefinitionDAO; 039import org.apache.tiles.definition.dao.CachingLocaleUrlDefinitionDAO; 040import org.apache.tiles.definition.digester.DigesterDefinitionsReader; 041import org.apache.tiles.el.ELAttributeEvaluator; 042import org.apache.tiles.el.ScopeELResolver; 043import org.apache.tiles.el.TilesContextBeanELResolver; 044import org.apache.tiles.el.TilesContextELResolver; 045import org.apache.tiles.evaluator.AttributeEvaluator; 046import org.apache.tiles.evaluator.AttributeEvaluatorFactory; 047import org.apache.tiles.evaluator.BasicAttributeEvaluatorFactory; 048import org.apache.tiles.evaluator.impl.DirectAttributeEvaluator; 049import org.apache.tiles.extras.complete.CompleteAutoloadTilesContainerFactory; 050import org.apache.tiles.extras.complete.CompleteAutoloadTilesInitializer; 051import org.apache.tiles.factory.AbstractTilesContainerFactory; 052import org.apache.tiles.factory.BasicTilesContainerFactory; 053import org.apache.tiles.impl.mgmt.CachingTilesContainer; 054import org.apache.tiles.locale.LocaleResolver; 055import org.apache.tiles.preparer.factory.PreparerFactory; 056import org.apache.tiles.request.ApplicationContext; 057import org.apache.tiles.request.ApplicationContextAware; 058import org.apache.tiles.request.ApplicationResource; 059import org.apache.tiles.startup.DefaultTilesInitializer; 060import org.apache.tiles.startup.TilesInitializer; 061 062import org.springframework.beans.BeanUtils; 063import org.springframework.beans.BeanWrapper; 064import org.springframework.beans.PropertyAccessorFactory; 065import org.springframework.beans.factory.DisposableBean; 066import org.springframework.beans.factory.InitializingBean; 067import org.springframework.lang.Nullable; 068import org.springframework.util.Assert; 069import org.springframework.util.ClassUtils; 070import org.springframework.web.context.ServletContextAware; 071 072/** 073 * Helper class to configure Tiles 3.x for the Spring Framework. See 074 * <a href="https://tiles.apache.org">https://tiles.apache.org</a> 075 * for more information about Tiles, which basically is a templating mechanism 076 * for web applications using JSPs and other template engines. 077 * 078 * <p>The TilesConfigurer simply configures a TilesContainer using a set of files 079 * containing definitions, to be accessed by {@link TilesView} instances. This is a 080 * Spring-based alternative (for usage in Spring configuration) to the Tiles-provided 081 * {@code ServletContextListener} 082 * (e.g. {@link org.apache.tiles.extras.complete.CompleteAutoloadTilesListener} 083 * for usage in {@code web.xml}. 084 * 085 * <p>TilesViews can be managed by any {@link org.springframework.web.servlet.ViewResolver}. 086 * For simple convention-based view resolution, consider using {@link TilesViewResolver}. 087 * 088 * <p>A typical TilesConfigurer bean definition looks as follows: 089 * 090 * <pre class="code"> 091 * <bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer"> 092 * <property name="definitions"> 093 * <list> 094 * <value>/WEB-INF/defs/general.xml</value> 095 * <value>/WEB-INF/defs/widgets.xml</value> 096 * <value>/WEB-INF/defs/administrator.xml</value> 097 * <value>/WEB-INF/defs/customer.xml</value> 098 * <value>/WEB-INF/defs/templates.xml</value> 099 * </list> 100 * </property> 101 * </bean> 102 * </pre> 103 * 104 * The values in the list are the actual Tiles XML files containing the definitions. 105 * If the list is not specified, the default is {@code "/WEB-INF/tiles.xml"}. 106 * 107 * <p>Note that in Tiles 3 an underscore in the name of a file containing Tiles 108 * definitions is used to indicate locale information, for example: 109 * 110 * <pre class="code"> 111 * <bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer"> 112 * <property name="definitions"> 113 * <list> 114 * <value>/WEB-INF/defs/tiles.xml</value> 115 * <value>/WEB-INF/defs/tiles_fr_FR.xml</value> 116 * </list> 117 * </property> 118 * </bean> 119 * </pre> 120 * 121 * @author mick semb wever 122 * @author Rossen Stoyanchev 123 * @author Juergen Hoeller 124 * @since 3.2 125 * @see TilesView 126 * @see TilesViewResolver 127 */ 128public class TilesConfigurer implements ServletContextAware, InitializingBean, DisposableBean { 129 130 private static final boolean tilesElPresent = 131 ClassUtils.isPresent("org.apache.tiles.el.ELAttributeEvaluator", TilesConfigurer.class.getClassLoader()); 132 133 134 protected final Log logger = LogFactory.getLog(getClass()); 135 136 @Nullable 137 private TilesInitializer tilesInitializer; 138 139 @Nullable 140 private String[] definitions; 141 142 private boolean checkRefresh = false; 143 144 private boolean validateDefinitions = true; 145 146 @Nullable 147 private Class<? extends DefinitionsFactory> definitionsFactoryClass; 148 149 @Nullable 150 private Class<? extends PreparerFactory> preparerFactoryClass; 151 152 private boolean useMutableTilesContainer = false; 153 154 @Nullable 155 private ServletContext servletContext; 156 157 158 /** 159 * Configure Tiles using a custom TilesInitializer, typically specified as an inner bean. 160 * <p>Default is a variant of {@link org.apache.tiles.startup.DefaultTilesInitializer}, 161 * respecting the "definitions", "preparerFactoryClass" etc properties on this configurer. 162 * <p><b>NOTE: Specifying a custom TilesInitializer effectively disables all other bean 163 * properties on this configurer.</b> The entire initialization procedure is then left 164 * to the TilesInitializer as specified. 165 */ 166 public void setTilesInitializer(TilesInitializer tilesInitializer) { 167 this.tilesInitializer = tilesInitializer; 168 } 169 170 /** 171 * Specify whether to apply Tiles 3.0's "complete-autoload" configuration. 172 * <p>See {@link org.apache.tiles.extras.complete.CompleteAutoloadTilesContainerFactory} 173 * for details on the complete-autoload mode. 174 * <p><b>NOTE: Specifying the complete-autoload mode effectively disables all other bean 175 * properties on this configurer.</b> The entire initialization procedure is then left 176 * to {@link org.apache.tiles.extras.complete.CompleteAutoloadTilesInitializer}. 177 * @see org.apache.tiles.extras.complete.CompleteAutoloadTilesContainerFactory 178 * @see org.apache.tiles.extras.complete.CompleteAutoloadTilesInitializer 179 */ 180 public void setCompleteAutoload(boolean completeAutoload) { 181 if (completeAutoload) { 182 try { 183 this.tilesInitializer = new SpringCompleteAutoloadTilesInitializer(); 184 } 185 catch (Throwable ex) { 186 throw new IllegalStateException("Tiles-Extras 3.0 not available", ex); 187 } 188 } 189 else { 190 this.tilesInitializer = null; 191 } 192 } 193 194 /** 195 * Set the Tiles definitions, i.e. the list of files containing the definitions. 196 * Default is "/WEB-INF/tiles.xml". 197 */ 198 public void setDefinitions(String... definitions) { 199 this.definitions = definitions; 200 } 201 202 /** 203 * Set whether to check Tiles definition files for a refresh at runtime. 204 * Default is "false". 205 */ 206 public void setCheckRefresh(boolean checkRefresh) { 207 this.checkRefresh = checkRefresh; 208 } 209 210 /** 211 * Set whether to validate the Tiles XML definitions. Default is "true". 212 */ 213 public void setValidateDefinitions(boolean validateDefinitions) { 214 this.validateDefinitions = validateDefinitions; 215 } 216 217 /** 218 * Set the {@link org.apache.tiles.definition.DefinitionsFactory} implementation to use. 219 * Default is {@link org.apache.tiles.definition.UnresolvingLocaleDefinitionsFactory}, 220 * operating on definition resource URLs. 221 * <p>Specify a custom DefinitionsFactory, e.g. a UrlDefinitionsFactory subclass, 222 * to customize the creation of Tiles Definition objects. Note that such a 223 * DefinitionsFactory has to be able to handle {@link java.net.URL} source objects, 224 * unless you configure a different TilesContainerFactory. 225 */ 226 public void setDefinitionsFactoryClass(Class<? extends DefinitionsFactory> definitionsFactoryClass) { 227 this.definitionsFactoryClass = definitionsFactoryClass; 228 } 229 230 /** 231 * Set the {@link org.apache.tiles.preparer.factory.PreparerFactory} implementation to use. 232 * Default is {@link org.apache.tiles.preparer.factory.BasicPreparerFactory}, creating 233 * shared instances for specified preparer classes. 234 * <p>Specify {@link SimpleSpringPreparerFactory} to autowire 235 * {@link org.apache.tiles.preparer.ViewPreparer} instances based on specified 236 * preparer classes, applying Spring's container callbacks as well as applying 237 * configured Spring BeanPostProcessors. If Spring's context-wide annotation-config 238 * has been activated, annotations in ViewPreparer classes will be automatically 239 * detected and applied. 240 * <p>Specify {@link SpringBeanPreparerFactory} to operate on specified preparer 241 * <i>names</i> instead of classes, obtaining the corresponding Spring bean from 242 * the DispatcherServlet's application context. The full bean creation process 243 * will be in the control of the Spring application context in this case, 244 * allowing for the use of scoped beans etc. Note that you need to define one 245 * Spring bean definition per preparer name (as used in your Tiles definitions). 246 * @see SimpleSpringPreparerFactory 247 * @see SpringBeanPreparerFactory 248 */ 249 public void setPreparerFactoryClass(Class<? extends PreparerFactory> preparerFactoryClass) { 250 this.preparerFactoryClass = preparerFactoryClass; 251 } 252 253 /** 254 * Set whether to use a MutableTilesContainer (typically the CachingTilesContainer 255 * implementation) for this application. Default is "false". 256 * @see org.apache.tiles.mgmt.MutableTilesContainer 257 * @see org.apache.tiles.impl.mgmt.CachingTilesContainer 258 */ 259 public void setUseMutableTilesContainer(boolean useMutableTilesContainer) { 260 this.useMutableTilesContainer = useMutableTilesContainer; 261 } 262 263 @Override 264 public void setServletContext(ServletContext servletContext) { 265 this.servletContext = servletContext; 266 } 267 268 /** 269 * Creates and exposes a TilesContainer for this web application, 270 * delegating to the TilesInitializer. 271 * @throws TilesException in case of setup failure 272 */ 273 @Override 274 public void afterPropertiesSet() throws TilesException { 275 Assert.state(this.servletContext != null, "No ServletContext available"); 276 ApplicationContext preliminaryContext = new SpringWildcardServletTilesApplicationContext(this.servletContext); 277 if (this.tilesInitializer == null) { 278 this.tilesInitializer = new SpringTilesInitializer(); 279 } 280 this.tilesInitializer.initialize(preliminaryContext); 281 } 282 283 /** 284 * Removes the TilesContainer from this web application. 285 * @throws TilesException in case of cleanup failure 286 */ 287 @Override 288 public void destroy() throws TilesException { 289 if (this.tilesInitializer != null) { 290 this.tilesInitializer.destroy(); 291 } 292 } 293 294 295 private class SpringTilesInitializer extends DefaultTilesInitializer { 296 297 @Override 298 protected AbstractTilesContainerFactory createContainerFactory(ApplicationContext context) { 299 return new SpringTilesContainerFactory(); 300 } 301 } 302 303 304 private class SpringTilesContainerFactory extends BasicTilesContainerFactory { 305 306 @Override 307 protected TilesContainer createDecoratedContainer(TilesContainer originalContainer, ApplicationContext context) { 308 return (useMutableTilesContainer ? new CachingTilesContainer(originalContainer) : originalContainer); 309 } 310 311 @Override 312 protected List<ApplicationResource> getSources(ApplicationContext applicationContext) { 313 if (definitions != null) { 314 List<ApplicationResource> result = new LinkedList<>(); 315 for (String definition : definitions) { 316 Collection<ApplicationResource> resources = applicationContext.getResources(definition); 317 if (resources != null) { 318 result.addAll(resources); 319 } 320 } 321 return result; 322 } 323 else { 324 return super.getSources(applicationContext); 325 } 326 } 327 328 @Override 329 protected BaseLocaleUrlDefinitionDAO instantiateLocaleDefinitionDao(ApplicationContext applicationContext, 330 LocaleResolver resolver) { 331 BaseLocaleUrlDefinitionDAO dao = super.instantiateLocaleDefinitionDao(applicationContext, resolver); 332 if (checkRefresh && dao instanceof CachingLocaleUrlDefinitionDAO) { 333 ((CachingLocaleUrlDefinitionDAO) dao).setCheckRefresh(true); 334 } 335 return dao; 336 } 337 338 @Override 339 protected DefinitionsReader createDefinitionsReader(ApplicationContext context) { 340 DigesterDefinitionsReader reader = (DigesterDefinitionsReader) super.createDefinitionsReader(context); 341 reader.setValidating(validateDefinitions); 342 return reader; 343 } 344 345 @Override 346 protected DefinitionsFactory createDefinitionsFactory(ApplicationContext applicationContext, 347 LocaleResolver resolver) { 348 349 if (definitionsFactoryClass != null) { 350 DefinitionsFactory factory = BeanUtils.instantiateClass(definitionsFactoryClass); 351 if (factory instanceof ApplicationContextAware) { 352 ((ApplicationContextAware) factory).setApplicationContext(applicationContext); 353 } 354 BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(factory); 355 if (bw.isWritableProperty("localeResolver")) { 356 bw.setPropertyValue("localeResolver", resolver); 357 } 358 if (bw.isWritableProperty("definitionDAO")) { 359 bw.setPropertyValue("definitionDAO", createLocaleDefinitionDao(applicationContext, resolver)); 360 } 361 return factory; 362 } 363 else { 364 return super.createDefinitionsFactory(applicationContext, resolver); 365 } 366 } 367 368 @Override 369 protected PreparerFactory createPreparerFactory(ApplicationContext context) { 370 if (preparerFactoryClass != null) { 371 return BeanUtils.instantiateClass(preparerFactoryClass); 372 } 373 else { 374 return super.createPreparerFactory(context); 375 } 376 } 377 378 @Override 379 protected LocaleResolver createLocaleResolver(ApplicationContext context) { 380 return new SpringLocaleResolver(); 381 } 382 383 @Override 384 protected AttributeEvaluatorFactory createAttributeEvaluatorFactory(ApplicationContext context, 385 LocaleResolver resolver) { 386 AttributeEvaluator evaluator; 387 if (tilesElPresent && JspFactory.getDefaultFactory() != null) { 388 evaluator = new TilesElActivator().createEvaluator(); 389 } 390 else { 391 evaluator = new DirectAttributeEvaluator(); 392 } 393 return new BasicAttributeEvaluatorFactory(evaluator); 394 } 395 } 396 397 398 private static class SpringCompleteAutoloadTilesInitializer extends CompleteAutoloadTilesInitializer { 399 400 @Override 401 protected AbstractTilesContainerFactory createContainerFactory(ApplicationContext context) { 402 return new SpringCompleteAutoloadTilesContainerFactory(); 403 } 404 } 405 406 407 private static class SpringCompleteAutoloadTilesContainerFactory extends CompleteAutoloadTilesContainerFactory { 408 409 @Override 410 protected LocaleResolver createLocaleResolver(ApplicationContext applicationContext) { 411 return new SpringLocaleResolver(); 412 } 413 } 414 415 416 private class TilesElActivator { 417 418 public AttributeEvaluator createEvaluator() { 419 ELAttributeEvaluator evaluator = new ELAttributeEvaluator(); 420 evaluator.setExpressionFactory( 421 JspFactory.getDefaultFactory().getJspApplicationContext(servletContext).getExpressionFactory()); 422 evaluator.setResolver(new CompositeELResolverImpl()); 423 return evaluator; 424 } 425 } 426 427 428 private static class CompositeELResolverImpl extends CompositeELResolver { 429 430 public CompositeELResolverImpl() { 431 add(new ScopeELResolver()); 432 add(new TilesContextELResolver(new TilesContextBeanELResolver())); 433 add(new TilesContextBeanELResolver()); 434 add(new ArrayELResolver(false)); 435 add(new ListELResolver(false)); 436 add(new MapELResolver(false)); 437 add(new ResourceBundleELResolver()); 438 add(new BeanELResolver(false)); 439 } 440 } 441 442}