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