Skip to content

Design Doc: Configuration

sitemesh edited this page Sep 13, 2010 · 3 revisions

Requirements

  • Pure Java API: User must never be forced to write any config files if they want to configure through code.
  • Text formats: As an alternative to the Java API, users should be able to configure through text (e.g. XML/Properties files, inline as web.xml params).
  • DI Integrations: Should be easy to integrate the config loading with a dependency injection framework (e.g. Spring, Guice, PicoContainer) for wiring up the parts that make a config.
  • Hot reloading: If the config is changed, SiteMesh should immediately pick this up without users having to reload their application.
  • Simplicity: Easy things should be simple. Hard things possible.
  • Pluggability: Users should be able to easily plug into a custom configuration mechanism, allowing them to configure in the same way as the rest of their app/framework. (Example: Java DSL, Grails conventions…).
  • Decoratable: Each aspect of the configuration should be decoratable to allow the user to plug in custom ‘tweaks’.
  • Shared: Should be possible to reuse parts of the same config across web applications and offline sites.

Design overview

Low Level: Just objects

The majority of the SiteMesh code is plain OO. Classes that depend on injected interfaces, and they themselves implement other interfaces.

It is easy for any of these objects to be used or replaced by a user if they are happy to write Java code.

A class may require multiple dependencies to be instantiated. At this level, it is the user’s responsibility to supply it with these.

To simplify thread safety, these objects will be immutable.

ContentProcessor contentProcessor = new MyContentProcessor(...);
DecoratorSelector decoratorSelector = new MyDecoratorSelector(...);
// ... more deps

javax.servlet.Filter filter = new SiteMeshFilter(contentProcessore, decoratorSelector, ....);

Medium Level: Assembly

The medium level makes it easy to assemble the low level objects in typical configurations.

For embedding in Java applications, it provides Builder APIs that can be

// Builder examples...

// Building the SiteMesh Servlet Filter
javax.servlet.Filter filter = new SiteMeshFilterBuilder()
    .addDecoratorMapping("/*", "/some/decorator.html")
    .addDecoratorMapping("/foo/*", "/another/decorator.html")
    .addTagRuleBundle(new MyCustomTagRuleBundle())
    .build();

// Building the SiteMesh offline generator
SiteMeshOfflineGenerator generator = new SiteMeshOfflineGeneratorBuilder()
    .addDecoratorMapping("/*.html", "/decorators/decorator.html")
    .addExclusion("/decorators/*");
    .setSourceDirectory("/src/html")
    .setDestinationDirectory("/build/html")
    .build();

High level: Config loader

At the highest level, classes will be created that can setup a Builder from sources such as properties files, web.xml s, an XML file, a dependency injection container, etc.

// Some example usages of the Config sources.

javax.servlet.Filter filter = new SiteMeshFilterBuilder()
    .configureFrom(new XmlConfig("/some/file.xml"));
    .build();

javax.servlet.Filter filter = new SiteMeshFilterBuilder()
    .configureFrom(new XmlConfig(someDocumentObject));
    .build();

javax.servlet.Filter filter = new SiteMeshFilterBuilder()
    .configureFrom(new SpringConfig("springBeanId"));
    .build();

javax.servlet.Filter filter = new SiteMeshFilterBuilder()
    .configureFrom(new WebXmlConfig(filterConfig));
    .build();

Config options

Here are some examples of how the config would work through various mechanism (note: this is just a proposal – not the final version).

Java API

Example: Servlet Filter (Java API)

javax.servlet.Filter filter = new SiteMeshFilterBuilder()
    .addDecoratorMapping("/*", "/decorators/main.html")
    .addDecoratorMapping("/foo/*", "/decorators/2-column.html, "/decorators/main.html")
    .addTagRuleBundle(new MyCustomTagRuleBundle())
    .build();

Example: Offline Generator (Java API)

SiteMeshOfflineGenerator generator = new SiteMeshOfflineGeneratorBuilder()
    .addDecoratorMapping("/*.html", "/decorators/decorator.html")
    .addExclusion("/decorators/*");
    .setSourceDirectory("/src/html")
    .setDestinationDirectory("/build/html")
    .build();

Properties

The properties mechanism allows SiteMesh to be configured with a flattened set of key/value string pairs.
Properties aren’t the cleanest mechanism, but they have the advantage that they can easily be squeezed into many existing places without requiring code or a separate file.

Example: SiteMesh Filter (Properties embedded in web.xml init-params)

<web-app>

  ...

  <filter>
    <filter-name>sitemesh</filter-name>
    <filter-name>org.sitemesh.builder.SiteMeshFilter</filter-name>
    <init-param>
      <param-name>decoratorMappings</param-name>
      <param-value>
        /*=/decorators/main.html
        /foo/*=/decorators/2-column.html|/decorators/main.html
      </param-value>
    </init-param>
    <init-param>
      <param-name>tagRuleBundles</param-name>
      <param-value>com.foo.MyCustomTagRuleBundle</param-value>
    </init-param>
  </filter>
</web-app>

Example: Offline Generator (Properties embedded in command line arguments) =

java -jar sitemesh.jar --source="/src/html" --destination="/build/html" \
  --decoratorMappings="/*.html=/decorators/decorator.html" \
  --exclude="/decorators/*"

XML

A schema will be provided to support development tools.

Example: SiteMesh Filter (XML config in WEB-INF/sitemesh.xml)

<sitemesh xmlns="http://www.sitemesh.org/schema/sitemesh-config">

  <!-- compact version -->
  <map path="/*" decorator="/decorators.main.html"/>

  <!-- full version (allows multiple decorators for a given path) -->
  <map>
    <path>/foo/*</path>
    <decorator>/decorators/2-column.html</decorator>
    <decorator>/decorators/main.html</decorator>
  </map>

</sitemesh>

It should be possible to drive both the Filter and the offline generator from the same config file.

Spring XML

It should be possible to configure all of the SiteMesh depedencies using Spring.

Additionally, the Spring XML can be extended with a custom namespace to make certain operations more readable.

<beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:sitemesh="http://www.sitemesh.org/schema/sitemesh-config">

  ...

  <bean id='sitemeshFilter' class='org.sitemesh.builder.SiteMeshFilter'>
    <property name='tagRuleBundles'>
      <list>
        <bean class='com.foo.MyCustomTagRuleBundle'/>
      </list>
    </property>
    <property name='decoratorMappings'>

      <!-- compact version -->
      <sitemesh:map path="/*" decorator="/decorators/main.html"/>

      <!-- full version (allows multiple decorators for a given path) -->
      <sitemesh:map>
        <sitemesh:path>/foo/*</sitemesh:path>
        <sitemesh:decorator>/decorators/2-column.html</sitemesh:decorator>
        <sitemesh:decorator>/decorators/main.html</sitemesh:decorator>
      </sitemesh:map>
    </property>
  </bean>

</beans>

User convention

Some applications and frameworks (e.g. Grails) may choose to use a config-less mechanism, driven by convention. SiteMesh will allow for this by having custom implementations of the varios interfaces passed in.

Example: Selecting a decorator by naming convention

/**
 * Example decorator selector that works by convention.
 */
public class MyConventionalDecoratorSelector implements DecoratorSelector<WebAppContext> {
  @Override
  public String[] selectDecoratorPaths(Content content, WebAppContext context) throws IOException {
    // If page contains <meta name='decorator' content='/some/dec.html,/another/dec.html'>
    ContentChunk decoratorMetaTag = content.getExtractedProperties().getChild("meta.decorator");
    if (decoratorMetaTag.hasValue()) {
      return decoratorMetaTag.split(",");
    }

    // Otherwise, build decorator based on the request
    HttpServletRequest request = context.getRequest();
    return figureOutDecoratorBaseOn(request); // use your imagination :)
  }
}

Based on the generic parameter when implementing DecoratorSelector, this can be coupled to a WebAppContext or OfflineContext where it can access environmental specific objects (e.g. HttpServletRequest), or the more generic SiteMeshContext which will work in both offline and in web-apps, but with less access to objects.

Inheritance

It should be possible to configure SiteMesh from multiple configuration sources. Each will inherit from the previous config.

For example, there may be a default config bundled with SiteMesh, which does the bulk of a typical setup. Then the dependencies may be loaded from Spring. Then the decorator mappings may be loaded from XML. It is down to the user to decide how they want to split up the config (though the SM docs will make some recommendations).

More…

The mechanisms above should support a lot of users, but no doubt new frameworks and config mechanisms will be requested over time. These can be added by end users or third parties using the same mechanism that SiteMesh uses internally.

Status

  • Low level and builder Java objects done.
  • Properties and XML based loading done.
  • Currently lacks ability to reload config when the config file changes without restarting webapp.
  • No work on convention or spring based config started.