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