SiteMesh

Contents
      1. Hello World
      2. In- and Exclusion Patterns
      3. Other decorator tags
        1. The Page Object
      4. Including a third pages' content
      5. Decoration-Aware Decorees
        1. Tagged Content
      6. sitemesh.xml
      7. Conditional Decoration
        1. AgentDecoratorMapper and LanguageDecoratorMapper
        2. Other decorator Mappers
        3. Custom Mappers
      8. Parsing Rules
        1. Replacement Rules
          1. Replacing Tags
          2. Replacing Text
        2. Custom Rules
          1. Custom TextFilter
          2. Custom Tag Rules
          3. Content Extraction Rules
      9. Struts2 and Decorators
      10. References
SiteMesh allows you to merge web pages (using JSP, Velocity or Freemarker) with decorators, e. g. to add header, footer or navigation to all your pages. It does so by replacing each page by the decorator, which then in turn includes some elements of the original page, such as the body.
The following is based on version 2.4.2 of SiteMesh. SiteMesh is distributed under terms compatible to the Apace License, which the Free Software Foundation considers compatible with the GPLv3.

Hello World

 <filter>
    <filter-name>sitemesh</filter-name>
    <filter-class>com.opensymphony.sitemesh.webapp.SiteMeshFilter</filter-class>
 </filter>
 <filter-mapping>
    <filter-name>sitemesh</filter-name>
    <url-pattern>/*</url-pattern>
 </filter-mapping>
 <decorators defaultdir="/decorators">
    <decorator name="main" page="main.jsp">
          <pattern>/*</pattern>
    </decorator>
 </decorators>
The following example adds a "Hello World - " prefix to the title of the page. The original title is appended using the <decorator:title />-Tag. A default value is given, for the case that the title is empty. The body of the original document is included after the decorator provided the navigation.
 <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
 <%@ taglib uri="http://www.opensymphony.com/sitemesh/decorator" prefix="decorator" %>
 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
 <html>
 <head>
 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
 <title>Hello World - <decorator:title default="Unnamed Page" /></title>
 </head>
 <body>
     <div id="navigation"><ul><li><a href="<%=request.getContextPath()%>/">Home</a></li></ul></div>
     <decorator:body />
 </body>
 </html>
Specifying the charset is required if it is not ISO-8859-1. After these steps, SiteMesh should start decorating your pages.

In- and Exclusion Patterns

In decorators.xml, you can specify locations that shall not be decorated, and those which shall get a certain decorator. For example, we exclude /oldpage.jsp and everything under the direction /plain. Everything else gets decorated by main.jsp. Note that these patterns refer to the URL, and not the file name, though these may be identical. The asterisk wildcard matches any string. I. e. It is like the url-pattern in web.xml. There may only be one decorator per page (chaining is planned for SiteMesh 3).
 <decorators defaultdir="/decorators">
    <excludes>
        <pattern>/oldpage.jsp</pattern>
        <pattern>/plain/*</pattern>
    </excludes>
    <decorator name="main" page="main.jsp">
          <pattern>/*</pattern>
    </decorator>
    <decorator name="printable" page="printable.jsp"/>
 </decorators>
The decorator "printable" doesn't need any patterns. A decorator with that name will automatically be used if the request parameter printable=true is present. SiteMesh only decorates pages of the text/html content type.

Other decorator tags

Other SiteMesh-decorator-tags are: Includes the decorated page's head, similarly to the body and title inclusion in the example above. Includes the property value (or, if writeEntireProperty is set, the entire property like ' foo="bar"') of a property. For example, if the decorator contained the tag:
 <body id="<decorator:getProperty property="body.id">">
would result in <body id="orderForm"> if the original document contained <body id="orderForm" ...>. The same would result from a decorator containing:
 <body<decorator:getProperty property="body.id" writeEntireProperty="true" />>
If the original document's body didn't contain a id-attribute the results would be different: <body id=""> or <body> respectively. As with decorator:title, a default value may be specified. Properties available to <decorator:getProperty> include:

The Page Object

You can use SiteMesh's representation of the pre-decoration page:
 <decorator:usePage id="origPage" />
Scriptlets can now access it:
 <%= origPage.isPropertySet("meta.author") ? "by " + origPage.getProperty("meta.author") : "anonymous" %>
The tag <decorator:useHtmlPage> does the same, but declares the page object as being of the subclass HTMLPage, which has the getHead() and isFrameSet() methods. For debugging, we can use the Page Object to list all available page properties:
	<decorator:usePage id="origPage" />
	<ul>
 	<% for(String key : origPage.getPropertyKeys()) { %>
 		<li><%=key %>=<%=origPage.getProperty(key) %></li>
 	<% }%>
 	</ul>

Including a third pages' content

Imagine we had a menu.html which is a proper HTML page, showing a menu, which can be viewed on its own. We can now use a SiteMesh decorator menuDecorator.jsp to extract the body of menu.html and include it from our main.jsp decorator. First we declare the new decorator in decorators.xml under <decorators>:
    <decorator name="menudecorator" page="menuDecorator.jsp"/>
Now we make a decorator menuDecorator.jsp to extract the body of a HTML page:
 <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
 <%@ taglib uri="http://www.opensymphony.com/sitemesh/decorator" prefix="decorator" %>
 <div>
	<h2>Menu for <decorator:getProperty property="parentTitle"/></h2>
	<decorator:body/>
 </div>
it also fits it into a div and displays a property "parentTitle" which we will set from the main.jsp decorator, to which we add the following lines:
 <page:applyDecorator name="menudecorator" page="/menu.html" encoding="utf-8">
     <page:param name="parentTitle"><decorator:title/></page:param>
 </page:applyDecorator>
Now menudecorator is applied to menu.html, passing our title property as a new property "parentTitle", available to our menudecorator. The result is inserted into the page. We could have used the name "title" instead of "parentTitle", because the values generated from parsing menu.html would be overridden. At the top of the main.jsp we need the taglib declaration:
 <%@ taglib uri="http://www.opensymphony.com/sitemesh/page" prefix="page" %>
If the applyDecorator-Tag is left without a page-attribute, the tag's contents are decorated.

Decoration-Aware Decorees

SiteMesh allows you to decorate pages without them "knowing" what is happening, that is, without SiteMesh-related Tags in them, and without coupling them to specific decorators or even SiteMesh itself. If you are willing to waive that advantage, you can make decorator and decoree play along.
In the simplest form, a page may specify which decorator (as named in decorators.xml) it wants, using a meta-tag:
 <meta name="decorator" content="someSpecialDecorator">
This overrides all other rules that map a page to a decorator. Another possibility is explicitly setting properties:
 <parameter name="tagline" value="Not New But Improved"/>
will set a page property named page.tagline which can be accessed by the decorator using:
 <decorator:getProperty property="page.tagline"/>

Tagged Content

A page can offer content blocks for the decorator to chose from. If a page's body contained:
 <content tag="error">Invalid input!</content>
for the decorator, this would not be part of the body. Instead, this content would be available as a property named after the tag, prefixed by "page.". Using the page object as described above, we could display a error div only if tagged error content is present:
 <% if(origPage.isPropertySet("page.error")) { %>
 <div class="error">
	<decorator:getProperty property="page.error"/>
 </div>
 <% } %>

sitemesh.xml

In the SiteMesh JAR file we find com/opensymphony/module/sitemesh/factory/sitemesh-default.xml. This file configures the behaviour of SiteMesh. We can copy the sitemesh-default.xml to WEB-INF/sitemesh.xml to customize it. If you want to put the file somewhere else, you need to specify the location in web.xml:
 <web-app ...>
   <context-param>
     <param-name>sitemesh.configfile</param-name>
     <param-value>/WEB-INF/foo/sitemesh.xml</param-value>
   </context-param>
   ...
We can also alter the location of the file which contains the exclusion patterns (i. e. the locations not to be decorated) as described above. As we have seen, the default is decorators.xml.
 <excludes file="/WEB-INF/xml/decorators.xml"/>
The excludes file should contain one <excludes>-Block with <url-pattern>-Entries. When you look at the sitemesh-default.xml you'll see that the excludes-declaration contains a place-holder ${decorators-file} instead of the file name:
 <excludes file="${decorators-file}"/>
You can define the values of place-holders like that:
 <property name="decorators-file" value="/WEB-INF/decorators.xml"/>
The place-holder ${decorators-file} is also used elsewhere in the default sitemesh.xml. That way, we have to edit a shared value in only one place when it changes. In this case we can specify an alternative location for decorators.xml. In sitemesh.xml you can also order SiteMesh to re-load the configuration after a given number of seconds have passed. This also causes the decorators.xml to be reloaded. That way, you can change your SiteMesh behaviour without having to restart the entire application:
 <sitemesh>
     <config-refresh seconds="3600"/>
     ...

Conditional Decoration

Base your decoration on user-agent, request, cookie etc. using Decorator Mappers. sitemesh.xml contains the default mapper configuration. The first mapper configured is asked to name a decorator responsible for the file. It may poll the mapper next in order (chain of responsibility pattern) and modify its response. The default configuration consists of:
  1. PageDecoratorMapper: Does the page specify a decorator using a meta tag?
  2. FrameSetDecoratorMapper: No decorator for frame sets!
  3. PrintableDecoratorMapper: is the request parameter printable=true set? If So, use the "printable" decorator. (decorator, parameter name and value may be changed, see below)
  4. FileDecoratorMapper: Is a decorator searched by name, and is the decorator name a file name?
  5. ConfigDecoratorMapper: Consult decorators.xml

AgentDecoratorMapper and LanguageDecoratorMapper

One way we can extend the mapper chain is by activating the AgentDecoratorMapper. It can detect browsers by their User-Agent string. If we add to sitemesh.xml, above the other entries of that kind:
		<mapper class="com.opensymphony.module.sitemesh.mapper.AgentDecoratorMapper">
			<param name="match.Galeon" value="gally" />
		</mapper>
Then SiteMesh will, if the User-agent contains the string "Galeon", modify the decorator file name ("default.jsp" will become "default-gally.jsp") to contain the configured parameter value. If a file of that name exists, it's used as decorator.
A similar mapper is the LanguageDecoratorMapper. It will perform the same match and file name replace operation using the Accept-Language header.

Other decorator Mappers

All mappers are configured in sitemesh.xml using
 <mapper class="com.opensymphony.module.sitemesh.mapper.XXX">
	<param name="YYY" value="ZZZ" />
 </mapper>
where XXX is the mapper name, YYY a parameter name, and ZZZ the parameter value. (Multiple param tags are possible.)

Custom Mappers

You can write your own mapper. Here is a mapper that changes the decorator at a certain time of day. That way, it could switch to a more lightweight layout in times of high server load.
 package net.pratinas.sitmesh.mapper;
 import java.util.Calendar;
 import java.util.GregorianCalendar;
 import java.util.Properties;
 import javax.servlet.http.HttpServletRequest;
 import com.opensymphony.module.sitemesh.Config;
 import com.opensymphony.module.sitemesh.Decorator;
 import com.opensymphony.module.sitemesh.DecoratorMapper;
 import com.opensymphony.module.sitemesh.Page;
 import com.opensymphony.module.sitemesh.mapper.AbstractDecoratorMapper;

 public class TimeOfDayDecoratorMapper extends AbstractDecoratorMapper {
    private String decorator;
    private int from, to;
    
	public void init(Config config, Properties properties, DecoratorMapper parent) throws InstantiationException {
        super.init(config, properties, parent);
        decorator  = properties.getProperty("decorator");
        from = Integer.parseInt(properties.getProperty("time.from"));
        to = Integer.parseInt(properties.getProperty("time.to"));
    }
    public Decorator getDecorator(HttpServletRequest request, Page page) {
        final Calendar now = new GregorianCalendar();
        final int hour = now.get(Calendar.HOUR_OF_DAY);
        
    	if ((from < to && hour >= from && hour < to)
    			|| (from > to && (hour >= from || hour < to))) {
            return getNamedDecorator(request, decorator);
        }
        return super.getDecorator(request, page);
    }
 }
The init() method is given the sitemesh.xml parameters as "properties". In this case, we need "decorator" (the name of the decorator to be switched to), "time.from" and "time.to" the hours between the decorator shall be used). The getDecorator() is invoked every time the app looks for a suitable decorator. We extend AbstractDecoratorMapper, which allows us to call super.getDecorator() to delegate the search to the parent mappers, if our time condition is not met. The decorator fits well after the PrintableDecoratorMapper in sitemesh.xml:
		<mapper class="net.pratinas.sitmesh.mapper.TimeOfDayDecoratorMapper">
			<param name="decorator" value="miniDecorator" />
			<param name="time.from" value="23" />
			<param name="time.to" value="1" />
		</mapper>
With this entry, our mapper is active, assuming a decorator is named "miniDecorator" in decorators.xml.

Parsing Rules

The SiteMesh parser is responsible for extracting all the information (title, head, body, etc.) from the original HTML page to be included by the decorator. We can extend the default parser by mandating which rules it shall use. Our parser will have to extend the class HTMLPageParser and override the addUserDefinedRules-Method.
 package net.pratinas.sitemesh.parser;
 import com.opensymphony.module.sitemesh.html.State;
 import com.opensymphony.module.sitemesh.html.rules.PageBuilder;
 import com.opensymphony.module.sitemesh.parser.HTMLPageParser;
 public class ExtendedHtmlPageParser extends HTMLPageParser {
    @Override
    protected void addUserDefinedRules(State html, PageBuilder page) {
        super.addUserDefinedRules(html, page);
    }
 }
We see here that five rules (creating values accessible to <decorator:getProperty>) are instantiated and added: The rules for head, body, title and frameset detection are always present. The full name our new parser class needs to be registered in sitemesh.xml:
	<page-parsers>
		<parser content-type="text/html" class="net.pratinas.sitemesh.parser.ExtendedHtmlPageParser" />
	</page-parsers>
We are now ready to customize the parsing rules. Your parser is instantiated only once, so it could have some state shared between requests. Thread safety measures are advised in this case.

Replacement Rules

Replacing Tags

Imagine we used <blink>-Tags in our markup, but came to the conclusion that <em> would have been the better choice. The TagReplaceRule for the SiteMesh parser can do automatic replacement, while preserving the tag attributes. All we got to do is to instantiate the TagReplaceRule with the two tag names as parameters and add it using addRule():
 ...
 import com.opensymphony.module.sitemesh.html.rules.TagReplaceRule;
 ...
    protected void addUserDefinedRules(State html, PageBuilder page) {
		super.addUserDefinedRules(html, page);
		
		html.addRule(new TagReplaceRule("blink", "em"));
    }

Replacing Text

Now we want to replace text on our site. For that we add instances of RegexReplacementTextFilter, constructed with a search pattern and a replacement string, using the addTextFilter()-Medthod:
 ... 
 import com.opensymphony.module.sitemesh.html.rules.RegexReplacementTextFilter;
 ...
    protected void addUserDefinedRules(State html, PageBuilder page) {
		super.addUserDefinedRules(html, page);
		
		html.addRule(new TagReplaceRule("blink", "em"));
		html.addTextFilter(new RegexReplacementTextFilter("Eurasia", "Eastasia")); 
		html.addTextFilter(new RegexReplacementTextFilter("\\[\\[(.+?)\\]\\]", 
				"<a href="http://en.wikipedia.org/wiki/$1">$1</a>")); 
	}
Rule one simply replaces alls instances of the string "Eurasia" with "Eastasia". The second looks for text in double square brackets (such as [[Hippocrates]]) and replaces it with a link to the Wikipedia article with the name in the brackets. The rule uses Pattern und Matcher from java.util.regex, so their syntax is to be used here.

Custom Rules

Please note that custom rules are not described in the official documentation. This section is based on following the example of the TagReplaceRule's RegexReplacementTextFilter's source code. The custom rules are coupled to SiteMesh internals and are more likely to break when SiteMesh is updated. Also note that adding rules increases processing time. In a small benchmark I found these increases to be non-dramatic: a Struts2 application took at average 54 ms to complete a request without SiteMesh, 60 ms with it, and 62 ms with all the additional rules described below active. I doubt, however, that these values are reliable indicators for real world behaviour.

Custom TextFilter

SiteMesh passes all plain Text between Tags to all TextFilters. The Filters return the text modified according to their purpose. A filter simply needs to implement the TextFilter interface, and with that the filter-Method. The following filter changes all letters to lower case:
 package net.pratinas.sitemesh.html.rules;
 import com.opensymphony.module.sitemesh.html.TextFilter;
 public class ToLowerCaseTextFilter implements TextFilter {
	@Override
	public String filter(String text) {
		return text.toLowerCase();
	}
 }
At the end of addUserDefinedRules we add the Filter with that line:
 html.addTextFilter(new ToLowerCaseTextFilter());
The filters are applied in the same order in which they are added.

Custom Tag Rules

In contrast to text filters, parsing rules listen for HTML tags. A rule takes responsibility for one tag by name. Only one rule may listen to a certain tag name such as <div> (unless you write a composite rule). To the rule's responsibilities belongs the writing of the tag HTML to the output buffer. As an example, we will create a rule that looks for <td>-Tags, and removes their bgcolor-Attribute, if they have one. Our rule extends SiteMesh's BasicRule:
 package net.pratinas.sitemesh.html.rules;
 import com.opensymphony.module.sitemesh.html.BasicRule;
 import com.opensymphony.module.sitemesh.html.CustomTag;
 import com.opensymphony.module.sitemesh.html.Tag;
 public class BgcolorRemovingRule extends BasicRule {
	public BgcolorRemovingRule() {
		super("td");
	}
	
	@Override
	public void process(Tag tag) {
		if (tag.getType() == Tag.OPEN && tag.hasAttribute("bgcolor", false)) {
			CustomTag achromaticTag = new CustomTag(tag);
			achromaticTag.removeAttribute("bgcolor", false);
			achromaticTag.writeTo(currentBuffer());
		} else {
			tag.writeTo(currentBuffer());
		}
	}
 }
The constructor calls the superclass constructor (that of BasicRule) stating to which tag name it wants to listen (<td>). Whenever the parser encounters a <td> tag it will call the process-method of our rule. The method checks whether its the opening tag (other cases are Tag.CLOSE, such as </td> and Tag.EMPTY such as <hr />) and whether the "bgcolor" attribute is present. If not, it simply lets the tag write its HTML code to the current output buffer. Tag-Objects are immutable, so to alter them - in case of a "bgcolor" attribute present - we create a custom tag constructed from the old tag. We can now remove the attribute. The parameters of the custom tag's removeAttribute method are the attribute name and a boolean value determining whether this name is case sensitive. We could also have changed or added or changed an attribute using the custom tag's setAttributeValue method:
 achromaticTag.setAttributeValue("class", false, "highlighted");
The custom tag too needs to be written to the current buffer. The rule now is added in the addUserDefinedRules-Method of our parser:
 html.addRule(new BgcolorRemovingRule());

Content Extraction Rules

Usually you want your rule to extract certain parts of the page to be available to the decorator as properties. For that, you pass the PageBuilder object to the rule, which receives these properties. The following rule extracts <div> elements and makes them available as properties named div.ID to the decorator, where id is the <div>'s document id.
 package net.pratinas.sitemesh.html.rules;
 import java.util.Stack;
 import com.opensymphony.module.sitemesh.html.BasicRule;
 import com.opensymphony.module.sitemesh.html.Tag;
 import com.opensymphony.module.sitemesh.html.rules.PageBuilder;
 import com.opensymphony.module.sitemesh.html.util.CharArray;
 public class DivExtractingRule extends BasicRule {
	private final PageBuilder page;
	private int count = 0;
	private Stack<String> ids = new Stack<String>();
	
	public DivExtractingRule(PageBuilder page) {
		super("div");
		this.page = page;
	}
	
	@Override
	public void process(Tag tag) {
			if (tag.getType() == Tag.OPEN) {
				// <div>s without ID get a sequential number
				String id = "noname" + ++count; 
				if (tag.hasAttribute("id", false)) {
					id = tag.getAttributeValue("id", false);
				}
				tag.writeTo(currentBuffer());
				context.pushBuffer(new CharArray(512));				
				ids.push(id);
			} else if (tag.getType() == Tag.CLOSE) {
				page.addProperty("div." + ids.pop(), currentBuffer().toString());
				context.mergeBuffer();
				context.popBuffer();
				tag.writeTo(currentBuffer());
			}
	}
 }
Since <div>s might be nested, the rule pushes the id values on a stack for each opening tag, and popping them back for each closing tag. For every opening tag we also start a new output buffer. The parsing context, which serves as a output buffer stack, is available as a object field in each rule. since the innermost <div> started an new buffer, this buffer contains exactly what appeared after it. When the closing tag is reached, this buffer - containing the <div>'s content - is added as a property to the page object. This content is also merged (that is, appended) to the previous buffer. That way, the content of inner <div>s remain part of outer <div>s and the whole document. As usual, we add the rule to our parser in addUserDefinedRules, this time passsing the page object:
         html.addRule(new DivExtractingRule(page));

Struts2 and Decorators

If you want to access the Struts2 action context from decorators (e. g. to read action properties), you need to make explicit that the ActionContextCleanUp filter is to be invoked only after SiteMesh did its work. For that, your filter chain declaration in web.xml might look something like this:
	<filter>
	    <filter-name>struts-cleanup</filter-name>
	    <filter-class>org.apache.struts2.dispatcher.ActionContextCleanUp</filter-class>
	</filter>
	
	<filter>
	   <filter-name>sitemesh</filter-name>
	   <filter-class>com.opensymphony.sitemesh.webapp.SiteMeshFilter</filter-class>
	</filter>
	
	<filter>
	    <filter-name>struts2</filter-name>
	    <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
	</filter>
	
	<filter-mapping>
	    <filter-name>struts-cleanup</filter-name>
	    <url-pattern>/*</url-pattern>
	</filter-mapping>
	
	<filter-mapping>
	   <filter-name>sitemesh</filter-name>
	   <url-pattern>/*</url-pattern>
	</filter-mapping>
	
	<filter-mapping>
	    <filter-name>struts2</filter-name>
	    <url-pattern>/*</url-pattern>
	</filter-mapping>

References

* The documentation can still be found in the directory 'docs' of the source distribution (choose the ZIP file). You can generate the SiteMesh 2.4.2 API documentation by running the command
    javadoc -d apidoc -classpath 'lib/*' -sourcepath src/java -subpackages com.opensymphony.module.sitemesh
from the directory where you extracted the source distribution files. The documents will be placed in a directory named "apidoc".

EditContents