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 * &lt;bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer">
089 *   &lt;property name="definitions">
090 *     &lt;list>
091 *       &lt;value>/WEB-INF/defs/general.xml&lt;/value>
092 *       &lt;value>/WEB-INF/defs/widgets.xml&lt;/value>
093 *       &lt;value>/WEB-INF/defs/administrator.xml&lt;/value>
094 *       &lt;value>/WEB-INF/defs/customer.xml&lt;/value>
095 *       &lt;value>/WEB-INF/defs/templates.xml&lt;/value>
096 *     &lt;/list>
097 *   &lt;/property>
098 * &lt;/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 * &lt;bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer">
109 *   &lt;property name="definitions">
110 *     &lt;list>
111 *       &lt;value>/WEB-INF/defs/tiles.xml&lt;/value>
112 *       &lt;value>/WEB-INF/defs/tiles_fr_FR.xml&lt;/value>
113 *     &lt;/list>
114 *   &lt;/property>
115 * &lt;/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<ApplicationResource> result = new 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}