Skip to content

Commit bef30a8

Browse files
committed
support for Tiles 2.2.1 (preserving compatibility with Tiles 2.1.2 and above; SPR-6097)
1 parent bfd61d6 commit bef30a8

File tree

2 files changed

+224
-10
lines changed

2 files changed

+224
-10
lines changed

org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/tiles2/TilesConfigurer.java

Lines changed: 221 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,13 @@
1616

1717
package org.springframework.web.servlet.view.tiles2;
1818

19+
import java.io.IOException;
20+
import java.lang.reflect.Constructor;
21+
import java.lang.reflect.Method;
22+
import java.net.URL;
1923
import java.util.HashMap;
24+
import java.util.LinkedList;
25+
import java.util.List;
2026
import java.util.Map;
2127
import java.util.Properties;
2228
import javax.servlet.ServletContext;
@@ -26,20 +32,32 @@
2632
import org.apache.tiles.TilesApplicationContext;
2733
import org.apache.tiles.TilesException;
2834
import org.apache.tiles.context.AbstractTilesApplicationContextFactory;
35+
import org.apache.tiles.context.TilesRequestContextFactory;
2936
import org.apache.tiles.definition.DefinitionsFactory;
37+
import org.apache.tiles.definition.DefinitionsFactoryException;
38+
import org.apache.tiles.definition.DefinitionsReader;
3039
import org.apache.tiles.definition.digester.DigesterDefinitionsReader;
40+
import org.apache.tiles.evaluator.AttributeEvaluator;
3141
import org.apache.tiles.evaluator.el.ELAttributeEvaluator;
3242
import org.apache.tiles.evaluator.impl.DirectAttributeEvaluator;
43+
import org.apache.tiles.factory.AbstractTilesContainerFactory;
44+
import org.apache.tiles.factory.BasicTilesContainerFactory;
3345
import org.apache.tiles.factory.TilesContainerFactory;
46+
import org.apache.tiles.impl.BasicTilesContainer;
47+
import org.apache.tiles.impl.mgmt.CachingTilesContainer;
48+
import org.apache.tiles.locale.LocaleResolver;
3449
import org.apache.tiles.preparer.BasicPreparerFactory;
50+
import org.apache.tiles.preparer.PreparerFactory;
3551
import org.apache.tiles.servlet.context.ServletUtil;
3652
import org.apache.tiles.startup.BasicTilesInitializer;
3753
import org.apache.tiles.startup.TilesInitializer;
3854

55+
import org.springframework.beans.BeanUtils;
3956
import org.springframework.beans.factory.DisposableBean;
4057
import org.springframework.beans.factory.InitializingBean;
4158
import org.springframework.util.ClassUtils;
4259
import org.springframework.util.CollectionUtils;
60+
import org.springframework.util.ReflectionUtils;
4361
import org.springframework.util.StringUtils;
4462
import org.springframework.web.context.ServletContextAware;
4563

@@ -49,8 +67,9 @@
4967
* for more information about Tiles, which basically is a templating
5068
* mechanism for JSP-based web applications.
5169
*
52-
* <b>Note: Spring 3.0 requires Tiles 2.1.2 or above.</b>
53-
* Tiles EL support will be activated by default when running on JSP 2.1 or above.
70+
* <b>Note: Spring 3.0 requires Tiles 2.1.2 or above, with explicit support for Tiles 2.2.</b>
71+
* Tiles 2.1's EL support will be activated by default when running on JSP 2.1 or above.
72+
* Note that EL support is <i>not</> active by default when running against Tiles 2.2.
5473
*
5574
* <p>The TilesConfigurer simply configures a TilesContainer using a set of files
5675
* containing definitions, to be accessed by {@link TilesView} instances. This is a
@@ -87,8 +106,26 @@ public class TilesConfigurer implements ServletContextAware, InitializingBean, D
87106
private static final boolean jsp21Present = ClassUtils.isPresent(
88107
"javax.servlet.jsp.JspApplicationContext", TilesConfigurer.class.getClassLoader());
89108

109+
private static final boolean tiles22Present = ClassUtils.isPresent(
110+
"org.apache.tiles.evaluator.AttributeEvaluatorFactory", TilesConfigurer.class.getClassLoader());
111+
112+
90113
protected final Log logger = LogFactory.getLog(getClass());
91114

115+
private TilesInitializer tilesInitializer;
116+
117+
private boolean overrideLocaleResolver = false;
118+
119+
private String[] definitions;
120+
121+
private boolean validateDefinitions = true;
122+
123+
private Class<? extends DefinitionsFactory> definitionsFactoryClass;
124+
125+
private Class<? extends PreparerFactory> preparerFactoryClass;
126+
127+
private boolean useMutableTilesContainer = false;
128+
92129
private final Map<String, String> tilesPropertyMap = new HashMap<String, String>();
93130

94131
private ServletContext servletContext;
@@ -108,24 +145,68 @@ public TilesConfigurer() {
108145
}
109146

110147

148+
/**
149+
* Configure Tiles using a custom TilesInitializer, typically specified as an inner bean.
150+
* <p>Default is a variant of {@link org.apache.tiles.startup.DefaultTilesInitializer},
151+
* respecting the "definitions", "preparerFactoryClass" etc properties on this configurer.
152+
* <p><b>NOTE: Specifying a custom TilesInitializer effectively disables all other bean
153+
* properties on this configurer.</b> The entire initialization procedure is then left
154+
* to the TilesInitializer as specified.
155+
*/
156+
public void setTilesInitializer(TilesInitializer tilesInitializer) {
157+
this.tilesInitializer = tilesInitializer;
158+
}
159+
160+
/**
161+
* Specify whether to apply Tiles 2.2's "complete-autoload" configuration.
162+
* <p>See {@link org.apache.tiles.extras.complete.CompleteAutoloadTilesContainerFactory}
163+
* for details on the complete-autoload mode.
164+
* <p><b>NOTE: Specifying the complete-autoload mode effectively disables all other bean
165+
* properties on this configurer.</b> The entire initialization procedure is then left
166+
* to {@link org.apache.tiles.extras.complete.CompleteAutoloadTilesInitializer}.
167+
* @see org.apache.tiles.extras.complete.CompleteAutoloadTilesContainerFactory
168+
* @see org.apache.tiles.extras.complete.CompleteAutoloadTilesInitializer
169+
*/
170+
public void setCompleteAutoload(boolean completeAutoload) {
171+
if (completeAutoload) {
172+
try {
173+
Class clazz = getClass().getClassLoader().loadClass(
174+
"org.apache.tiles.extras.complete.CompleteAutoloadTilesInitializer");
175+
this.tilesInitializer = (TilesInitializer) clazz.newInstance();
176+
}
177+
catch (Exception ex) {
178+
throw new IllegalStateException("Tiles 2.2 not available", ex);
179+
}
180+
}
181+
else {
182+
this.tilesInitializer = null;
183+
}
184+
this.overrideLocaleResolver = completeAutoload;
185+
}
186+
111187
/**
112188
* Set the Tiles definitions, i.e. the list of files containing the definitions.
113189
* Default is "/WEB-INF/tiles.xml".
114190
*/
115191
public void setDefinitions(String[] definitions) {
192+
this.definitions = definitions;
116193
if (definitions != null) {
117194
String defs = StringUtils.arrayToCommaDelimitedString(definitions);
118195
if (logger.isInfoEnabled()) {
119196
logger.info("TilesConfigurer: adding definitions [" + defs + "]");
120197
}
121198
this.tilesPropertyMap.put(DefinitionsFactory.DEFINITIONS_CONFIG, defs);
122199
}
200+
else {
201+
this.tilesPropertyMap.remove(DefinitionsFactory.DEFINITIONS_CONFIG);
202+
}
123203
}
124204

125205
/**
126206
* Set whether to validate the Tiles XML definitions. Default is "true".
127207
*/
128208
public void setValidateDefinitions(boolean validateDefinitions) {
209+
this.validateDefinitions = validateDefinitions;
129210
this.tilesPropertyMap.put(DigesterDefinitionsReader.PARSER_VALIDATE_PARAMETER_NAME,
130211
Boolean.toString(validateDefinitions));
131212
}
@@ -139,7 +220,8 @@ public void setValidateDefinitions(boolean validateDefinitions) {
139220
* DefinitionsFactory has to be able to handle {@link java.net.URL} source objects,
140221
* unless you configure a different TilesContainerFactory.
141222
*/
142-
public void setDefinitionsFactoryClass(Class definitionsFactoryClass) {
223+
public void setDefinitionsFactoryClass(Class<? extends DefinitionsFactory> definitionsFactoryClass) {
224+
this.definitionsFactoryClass = definitionsFactoryClass;
143225
this.tilesPropertyMap.put(TilesContainerFactory.DEFINITIONS_FACTORY_INIT_PARAM,
144226
definitionsFactoryClass.getName());
145227
}
@@ -163,23 +245,29 @@ public void setDefinitionsFactoryClass(Class definitionsFactoryClass) {
163245
* @see SimpleSpringPreparerFactory
164246
* @see SpringBeanPreparerFactory
165247
*/
166-
public void setPreparerFactoryClass(Class preparerFactoryClass) {
248+
public void setPreparerFactoryClass(Class<? extends PreparerFactory> preparerFactoryClass) {
249+
this.preparerFactoryClass = preparerFactoryClass;
167250
this.tilesPropertyMap.put(TilesContainerFactory.PREPARER_FACTORY_INIT_PARAM,
168251
preparerFactoryClass.getName());
169252
}
170253

171254
/**
172-
* Set whether to use a MutableTilesContainer for this application.
173-
* Default is "false".
255+
* Set whether to use a MutableTilesContainer (typically the CachingTilesContainer
256+
* implementation) for this application. Default is "false".
257+
* @see org.apache.tiles.mgmt.MutableTilesContainer
258+
* @see org.apache.tiles.mgmt.CachingTilesContainer
174259
*/
175260
public void setUseMutableTilesContainer(boolean useMutableTilesContainer) {
261+
this.useMutableTilesContainer = useMutableTilesContainer;
176262
this.tilesPropertyMap.put(TilesContainerFactory.CONTAINER_FACTORY_MUTABLE_INIT_PARAM,
177263
Boolean.toString(useMutableTilesContainer));
178264
}
179265

180266
/**
181267
* Set Tiles properties (equivalent to the ServletContext init-params in
182268
* the Tiles documentation), overriding the default settings.
269+
* <p><b>NOTE: This property is only effective with Tiles 2.1.</b>
270+
* Tiles 2.2 doesn't support property-based configuration anymore.
183271
*/
184272
public void setTilesProperties(Properties tilesProperties) {
185273
CollectionUtils.mergePropertiesIntoMap(tilesProperties, this.tilesPropertyMap);
@@ -200,7 +288,46 @@ public void afterPropertiesSet() throws TilesException {
200288
SpringTilesApplicationContextFactory factory = new SpringTilesApplicationContextFactory();
201289
factory.init(this.tilesPropertyMap);
202290
TilesApplicationContext preliminaryContext = factory.createApplicationContext(this.servletContext);
203-
createTilesInitializer().initialize(preliminaryContext);
291+
if (this.tilesInitializer == null) {
292+
this.tilesInitializer = createTilesInitializer();
293+
}
294+
this.tilesInitializer.initialize(preliminaryContext);
295+
296+
if (this.overrideLocaleResolver) {
297+
// We need to do this after initialization simply because we're reusing the
298+
// original CompleteAutoloadTilesInitializer above. We cannot subclass
299+
// CompleteAutoloadTilesInitializer when compiling against Tiles 2.1...
300+
try {
301+
BasicTilesContainer container = (BasicTilesContainer) ServletUtil.getContainer(this.servletContext);
302+
DefinitionsFactory definitionsFactory = container.getDefinitionsFactory();
303+
Method setter = definitionsFactory.getClass().getMethod("setLocaleResolver", LocaleResolver.class);
304+
setter.invoke(definitionsFactory, new SpringLocaleResolver());
305+
}
306+
catch (Exception ex) {
307+
throw new IllegalStateException("Cannot override LocaleResolver with SpringLocaleResolver", ex);
308+
}
309+
}
310+
311+
if (jsp21Present && this.tilesInitializer instanceof SpringTilesInitializer) {
312+
// Again, we need to do this after initialization since SpringTilesContainerFactory
313+
// cannot override template methods that refer to Tiles 2.2 classes: in this case,
314+
// AttributeEvaluatorFactory as createAttributeEvaluatorFactory return type.
315+
try {
316+
BasicTilesContainer container = (BasicTilesContainer) ServletUtil.getContainer(this.servletContext);
317+
Class aef = getClass().getClassLoader().loadClass("org.apache.tiles.evaluator.AttributeEvaluatorFactory");
318+
Class baef = getClass().getClassLoader().loadClass("org.apache.tiles.evaluator.BasicAttributeEvaluatorFactory");
319+
Constructor baefCtor = baef.getConstructor(AttributeEvaluator.class);
320+
ELAttributeEvaluator evaluator = new ELAttributeEvaluator();
321+
evaluator.setApplicationContext(container.getApplicationContext());
322+
evaluator.init(new HashMap<String, String>());
323+
Object baefValue = baefCtor.newInstance(evaluator);
324+
Method setter = container.getClass().getMethod("setAttributeEvaluatorFactory", aef);
325+
setter.invoke(container, baefValue);
326+
}
327+
catch (Exception ex) {
328+
throw new IllegalStateException("Cannot activate ELAttributeEvaluator", ex);
329+
}
330+
}
204331
}
205332

206333
/**
@@ -209,15 +336,100 @@ public void afterPropertiesSet() throws TilesException {
209336
* @see org.apache.tiles.web.startup.TilesListener#createTilesInitializer()
210337
*/
211338
protected TilesInitializer createTilesInitializer() {
212-
return new BasicTilesInitializer();
339+
return (tiles22Present ? new SpringTilesInitializer() : new BasicTilesInitializer());
213340
}
214341

215342
/**
216343
* Removes the TilesContainer from this web application.
217344
* @throws TilesException in case of cleanup failure
218345
*/
219346
public void destroy() throws TilesException {
220-
ServletUtil.setContainer(this.servletContext, null);
347+
try {
348+
// Tiles 2.2?
349+
ReflectionUtils.invokeMethod(TilesInitializer.class.getMethod("destroy"), this.tilesInitializer);
350+
}
351+
catch (NoSuchMethodException ex) {
352+
// Tiles 2.1...
353+
ServletUtil.setContainer(this.servletContext, null);
354+
}
355+
}
356+
357+
358+
private class SpringTilesInitializer extends BasicTilesInitializer {
359+
360+
@Override
361+
protected AbstractTilesContainerFactory createContainerFactory(TilesApplicationContext context) {
362+
return new SpringTilesContainerFactory();
363+
}
364+
}
365+
366+
367+
private class SpringTilesContainerFactory extends BasicTilesContainerFactory {
368+
369+
@Override
370+
protected BasicTilesContainer instantiateContainer(TilesApplicationContext context) {
371+
return (useMutableTilesContainer ? new CachingTilesContainer() : new BasicTilesContainer());
372+
}
373+
374+
@Override
375+
protected List<URL> getSourceURLs(TilesApplicationContext applicationContext,
376+
TilesRequestContextFactory contextFactory) {
377+
if (definitions != null) {
378+
try {
379+
List<URL> result = new LinkedList<URL>();
380+
for (String definition : definitions) {
381+
result.addAll(applicationContext.getResources(definition));
382+
}
383+
return result;
384+
}
385+
catch (IOException ex) {
386+
throw new DefinitionsFactoryException("Cannot load definition URLs", ex);
387+
}
388+
}
389+
else {
390+
return super.getSourceURLs(applicationContext, contextFactory);
391+
}
392+
}
393+
394+
@Override
395+
protected DefinitionsReader createDefinitionsReader(TilesApplicationContext applicationContext,
396+
TilesRequestContextFactory contextFactory) {
397+
DigesterDefinitionsReader reader = new DigesterDefinitionsReader();
398+
if (!validateDefinitions){
399+
Map<String,String> map = new HashMap<String,String>();
400+
map.put(DigesterDefinitionsReader.PARSER_VALIDATE_PARAMETER_NAME, Boolean.FALSE.toString());
401+
reader.init(map);
402+
}
403+
return reader;
404+
}
405+
406+
@Override
407+
protected LocaleResolver createLocaleResolver(TilesApplicationContext applicationContext,
408+
TilesRequestContextFactory contextFactory) {
409+
return new SpringLocaleResolver();
410+
}
411+
412+
@Override
413+
protected DefinitionsFactory createDefinitionsFactory(TilesApplicationContext applicationContext,
414+
TilesRequestContextFactory contextFactory, LocaleResolver resolver) {
415+
if (definitionsFactoryClass != null) {
416+
return BeanUtils.instantiate(definitionsFactoryClass);
417+
}
418+
else {
419+
return super.createDefinitionsFactory(applicationContext, contextFactory, resolver);
420+
}
421+
}
422+
423+
@Override
424+
protected PreparerFactory createPreparerFactory(TilesApplicationContext applicationContext,
425+
TilesRequestContextFactory contextFactory) {
426+
if (preparerFactoryClass != null) {
427+
return BeanUtils.instantiate(preparerFactoryClass);
428+
}
429+
else {
430+
return super.createPreparerFactory(applicationContext, contextFactory);
431+
}
432+
}
221433
}
222434

223435
}

org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/view/tiles2/TilesConfigurerTests.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public class TilesConfigurerTests {
4242
public void simpleBootstrap() {
4343
MockServletContext sc = new MockServletContext();
4444
TilesConfigurer tc = new TilesConfigurer();
45-
tc.setDefinitions(new String[] {"/org/springframework/web/servlet/view/tiles2/tiles-definitions.xml"});
45+
tc.setDefinitions(new String[] {"/org/springframework/web/servlet/**/tiles-definitions.xml"});
4646
Properties props = new Properties();
4747
props.setProperty(TilesContainerFactory.ATTRIBUTE_EVALUATOR_INIT_PARAM, DirectAttributeEvaluator.class.getName());
4848
tc.setTilesProperties(props);
@@ -54,6 +54,8 @@ public void simpleBootstrap() {
5454
TilesRequestContext requestContext = new ServletTilesRequestContext(appContext,
5555
new MockHttpServletRequest(), new MockHttpServletResponse());
5656
assertNotNull(container.getDefinitionsFactory().getDefinition("test", requestContext));
57+
58+
tc.destroy();
5759
}
5860

5961
}

0 commit comments

Comments
 (0)