FINN tech blog

tech blog

the ultimate view

A story of getting the View layer up and running quickly in Spring, using Tiles with wildcards, fallbacks, and definition includes, to use the Composite pattern and Convention over Configuration providing a minimal ongoing xml changes.

Summary

From the architect’s perspective you see Apache Tiles as rare exotic Italian marble sheets laid out exquisitely, while the same architect will see SiteMesh as the steel wiring stuck inside the concrete slab. The beauty of the tiles is always admired and a key component in creating an eye-catching surrounding.

Background

At FINN.no we were redesigning our control and view layers. We, being the architectural team of six, had already decided on Spring-Web as a framework for the control layer due to its flexibility and a design for us providing better, simpler, migration path. For the front end we were a little unclear. In a department of ~60 developers we knew that the popular vote would lead us towards SiteMesh. And we knew why for practical purposes sitemesh gives the front end developer more flexibility and less xml editing. But we knew sitemesh has some serious shortcomings…

SiteMesh shortcomings:

  • from a design perspective the Decorator pattern doesn’t combine with MVC as elegantly as the Composite pattern does
  • requires to hold all possible html for a request in buffer requiring large amounts of memory
  • unable to flush the response before the response is complete
  • requires more overall processing due to the processing of all the potentially included fragments
  • does not guaranteed thread safety
  • does not provide any structure or organisation amongst jsps, making refactorings and other tricks awkward

One of the alternatives we looked at was Apache Tiles. It follows the Composite Pattern, but within that allows one to take advantage of the Decorator pattern using a ViewPreparer. This meant it provided by default what we considered a superior design but if necessary could also do what SiteMesh was good at. It already had integration with Spring, and the way it worked it meant that once the Spring-Web controller code was executed, the Spring’s view resolver would pass the ball onto Tiles letting it do the rest. This gave us a clear MVC separation and an encapsulation ensuring single thread safety within the view domain.

Yet the most valuable benefit Tiles was going to offer wasn’t realised until we started experimenting a little more…

3 comments

  1. Girish says:

    In step3, The given sample code is wrong. It would not compile as all the methods have the same name, though the intent is clear by looking at the @RequestMapping annotation.

    Excellent article!! I would definitely use this in my next project

    EDIT: Thanks for pointing out the error. It has been corrected. ~mck

  2. Joe says:

    How did you override createPatternDefinitionResolver(Class) in SpringTilesContainerFactory? According to http://tiles.apache.org/framework/tutorial/advanced/wildcard.html that must be done before REGEXP: definition names will be understood by Tiles.

    I could just subclass BasicTilesContainerFactory instead, but I assume the stuff in SpringTilesContainerFactory is required.

    I could make another class, then copy-paste SpringTilesContainerFactory and add the extra method, but that doesn’t seem very nice.

    I could make another class, then use declare parents in a privileged aspect to force in that private class as the superclass, but that doesn’t seem very nice, either.

    Thanks for the great article!

  3. mck says:

    Joe,
     FinnTilesContainerFactory.java extends BasicTilesContainerFactory.
    The code for it is:

    public final class FinnTilesContainerFactory extends BasicTilesContainerFactory {
        @Override
        protected BasicTilesContainer instantiateContainer(final TilesApplicationContext applicationContext) {
            return new CachingTilesContainer();
        }
        /** Adds support for including definitions as defined in Step3 of this article (on page 5). */
        @Override
        protected UnresolvingLocaleDefinitionsFactory instantiateDefinitionsFactory(
                final TilesApplicationContext applicationContext,
                final TilesRequestContextFactory contextFactory,
                final LocaleResolver resolver) {
            return new FinnUnresolvingLocaleDefinitionsFactoryImpl();
        }
        @Override
        protected LocaleResolver createLocaleResolver(
                final TilesApplicationContext applicationContext,
                final TilesRequestContextFactory contextFactory) {
            return new SpringLocaleResolver();
        }
        @Override
        protected AttributeEvaluatorFactory createAttributeEvaluatorFactory(
                final TilesApplicationContext applicationContext,
                final TilesRequestContextFactory contextFactory,
                final LocaleResolver resolver) {
            return new BasicAttributeEvaluatorFactory(createELEvaluator(applicationContext));
        }
        @Override
        protected <T> PatternDefinitionResolver<T> createPatternDefinitionResolver(final Class<T> customizationKeyClass) {
            DefinitionPatternMatcherFactory wildcardFactory = new WildcardDefinitionPatternMatcherFactory();
            DefinitionPatternMatcherFactory regexpFactory = new RegexpDefinitionPatternMatcherFactory();
            PrefixedPatternDefinitionResolver<T> resolver = new PrefixedPatternDefinitionResolver<T>();
            resolver.registerDefinitionPatternMatcherFactory("WILDCARD", wildcardFactory);
            resolver.registerDefinitionPatternMatcherFactory("REGEXP", regexpFactory);
            return resolver;
        }
        /** Uses the custom TemplateAttributeRenderer to implement "fallback" from Step2 of this article (on page 4). */
        @Override
        protected AttributeRenderer createTemplateAttributeRenderer(
                final BasicRendererFactory rendererFactory,
                final TilesApplicationContext applicationContext,
                final TilesRequestContextFactory contextFactory,
                final TilesContainer container,
                final AttributeEvaluatorFactory attributeEvaluatorFactory) {
            TemplateAttributeRenderer templateRenderer = new TemplateAttributeRenderer();
            templateRenderer.setApplicationContext(applicationContext);
            templateRenderer.setRequestContextFactory(contextFactory);
            templateRenderer.setAttributeEvaluatorFactory(attributeEvaluatorFactory);
            return templateRenderer;
        }
        @Override
        protected List<URL> getSourceURLs(
                final TilesApplicationContext applicationContext,
                final TilesRequestContextFactory contextFactory) {
            try {
                Set<URL> finalSet = new HashSet<URL>();
                Set<URL> webINFSet = applicationContext.getResources("/WEB-INF/**/tiles*.xml");
                Set<URL> metaINFSet = applicationContext.getResources("classpath*:META-INF/**/tiles*.xml");
                if (webINFSet != null) { finalSet.addAll(webINFSet); }
                if (metaINFSet != null) { finalSet.addAll(metaINFSet); }
                return URLUtil.getBaseTilesDefinitionURLs(finalSet);
            } catch (IOException e) {
                throw new DefinitionsFactoryException("Cannot load definition URLs", e);
            }
        }
        @Override
        protected DefinitionsReader createDefinitionsReader(
                final TilesApplicationContext applicationContext,
                final TilesRequestContextFactory contextFactory) {
            return new CompatibilityDigesterDefinitionsReader();
        }
        private ELAttributeEvaluator createELEvaluator(final TilesApplicationContext applicationContext) {
            ELAttributeEvaluator evaluator = new ELAttributeEvaluator();
            evaluator.setApplicationContext(applicationContext);
            JspExpressionFactoryFactory efFactory = new JspExpressionFactoryFactory();
            efFactory.setApplicationContext(applicationContext);
            evaluator.setExpressionFactory(efFactory.getExpressionFactory());
            ELResolver elResolver = new CompositeELResolverImpl();
            evaluator.setResolver(elResolver);
            return evaluator;
        }
    }

Leave a Reply

Fork me on GitHub