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 * &lt;bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer">
092 *   &lt;property name="definitions">
093 *     &lt;list>
094 *       &lt;value>/WEB-INF/defs/general.xml&lt;/value>
095 *       &lt;value>/WEB-INF/defs/widgets.xml&lt;/value>
096 *       &lt;value>/WEB-INF/defs/administrator.xml&lt;/value>
097 *       &lt;value>/WEB-INF/defs/customer.xml&lt;/value>
098 *       &lt;value>/WEB-INF/defs/templates.xml&lt;/value>
099 *     &lt;/list>
100 *   &lt;/property>
101 * &lt;/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 * &lt;bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer">
112 *   &lt;property name="definitions">
113 *     &lt;list>
114 *       &lt;value>/WEB-INF/defs/tiles.xml&lt;/value>
115 *       &lt;value>/WEB-INF/defs/tiles_fr_FR.xml&lt;/value>
116 *     &lt;/list>
117 *   &lt;/property>
118 * &lt;/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}