For the collected modules to appear as one application, it is crucial that they have the same looks. This includes a common header, footer, menus, and so on. A very common solution to this is problem is to create a header template and a footer tem- plate, and have the page content templates include the header and footer templates at the top and bottom of the page, respectively. In WebWork, the end status of an
Figure 6.6: Timeline of Maven 2 releases and DHIS 2 events. Note that the distances between the dates do not correspond to the time differences. The two vertical lines symbolize greater jumps in time where uninteresting events have been left out.
action class execution is mapped to a result, for example a Velocity result with a path to the template which should be processed, similar to figure 6.7.
1 < action name = " r e l a t i v e U R L" action = " a c t i o n C l a s s " >
2 < result name = " s u c c e s s" type = " v e l o c i t y" > t e m p l a t e. vm </ result > 3 </ action >
Figure 6.7: How to define a Velocity result in an action mapping.
So by specifying the page template in the action mapping, and making the page template include a header and footer template, the problem should be solved. But having all these includes can be seen as every template knowing and controlling its own environment, which might not be a good thing if the environment would change in some way or another. With the first web portal assembly attempt, I created a similar solution in which, instead of specifying the header and footer templates in the page content template, all template paths were to be specified in the action mappings as a pipe-separated string of paths, similar to figure 6.8.
1 < action name = " r e l a t i v e U R L" action = " a c t i o n C l a s s " > 2 < result name = " s u c c e s s" type = " v e l o c i t y" >
3 header . vm | t e m p l a t e. vm | footer . vm 4 </ result >
5 </ action >
Figure 6.8: Action mapping with multiple templates in the result, which has to be supported by a custom Velocity resource loader.
Upon an action result, a custom Velocity resource loader would receive the tem- plate paths, load them in the order specified, and return a concatenated stream back to Velocity. Velocity would thus regard the loaded templates as one large template. A positive consequence of this was that any set of templates could be combined as specified in the action mappings, and Velocity would also automatically cache the composite template, because caching takes place after loading. While this worked just fine, it would be very unfortunate if the header and footer templates, for some reason, were to change location or name. This holds for all variants of solutions hav- ing separate header and footer templates. The custom Velocity resource loader also had a technical issue which should have been addressed if it were to be used in a production system. There were no restrictions on which files could be concatenated, so any path to any file on the classpath would be accepted by the resource loader.
Shortly after, a safer solution was found. This was a resource loader based on key information at the top of each template that were asked for, which put the template
in a category for adding specific header and footer templates around it. The header and footer templates were predefined in a properties file, thus collecting the path specifications in one place. If the key information did not match any of the predefined templates, it was returned as is and an error message was sent to the system log. A small inefficiency with the solution was that the requested template had to be parsed in order for the key information to be extracted, but the great benefit of this way of doing things—and also of the previous solution—was that it did not require much code, and no web framework would really need to know about the solution. The Velocity resource loader just had to be registered as the one to be used by Velocity, everything else was transparent. Here too, Velocity would automatically cache the composite templates as single complete templates.
One of the core developers was sceptical to having custom resource loaders, and the resource loaders would have become more complex when the web portal would have been deployed unwrapped, as then the templates are not on the classpath (I will get back to the location of the templates in the next chapter). The core devel- oper had a wish for something different, and thus a month later a new solution for putting together web pages was created. The opposite of having header and footer templates is to have one “background template“, or main template, containing the header and footer, which includes the content and other elements where they belong in the document. In DHIS 2, we needed a page content template and a page menu template, but because a result in the action mappings can only contain one template reference when using the Velocity and WebWork provided resource loaders, the main template had to be specified here as it had to be the first template to be loaded. So, by defining that the same main template was to be displayed for all actions, and then give separate parameters deciding which templates would be included by the main template, the desired result was met. Figure 6.9 shows how this was achieved in the action mapping. Figure 6.10 shows the basic concept of the main template in Velocity including the menu and page templates.
1 < action name = " r e l a t i v e U R L" class = " a c t i o n C l a s s " >
2 < result name = " s u c c e s s" type = " v e l o c i t y" > main . vm </ result > 3 < param name = " menu " > some / menu . vm </ param >
4 < param name = " page " > some / page . vm </ param > 5 </ action >
Figure 6.9: How to specify a main template with dynamic includes of specified menu and page templates in DHIS 2. For different web pages with same or different menus, only the two static parameters need to be changed. The main template assures a common look to all the web pages.
1 < html >
2 < head > < title > DHIS 2 </ title > </ head > 3 < body > 4 5 < div id = " header " > 6 ... 7 </ div > 8 9 < div id = " menu " >
10 # parse ( $menu ) ## R e f e r e n c i n g the menu p a r a m e t e r 11 </ div >
12
13 < div id = " c o n t e n t" >
14 # parse ( $page ) ## R e f e r e n c i n g the page p a r a m e t e r 15 </ div > 16 17 < div id = " footer " > 18 ... 19 </ div > 20 21 </ body > 22 </ html >
Figure 6.10: The conceptual idea of how a main template written in Velocity can dynamically include a menu and page template based on outside configuration. Line 10 and 14 contain the include statements.
By itself, this is not enough. WebWork does not automatically make static pa- rameters from the action mappings available to Velocity, WebWork only makes the parameters available to the corresponding Java action classes. So it is by default the action classes’ responsibility to have two setter methods and two getter methods matching the parameter names, in order to transfer the incomming parameter values from the action mappings to the Velocity template engine. This is a concern cutting across all action classes, and thus is best not implemented by every single action class. A small change in the names or the addition of new parameters would mean that all action classes in the system would need to be updated. There are two ways of solving this: Create a superclass which all action classes must extend, or create a WebWork interceptor which makes the parameters bypass the action classes. The latter solution was chosen, making the action classes completely unaware of what is going on and not forcing them into unnecessary class hierarchies. The interceptor was added to the default interceptor stack, which is used upon every action execution. In addition, the interceptor was made general in the way that the parameters that it should recog- nize and pass on were configurable in the bean mapping of the interceptor. This has
turned out useful, as more properties for including page specific JavaScript files and style sheets have been added without touching any Java code. This template system is still being used in the spring of 2008.
There are a few benefits of having a main template. For instance, the template is viewable in a web browser during design, without needing to include the contents, so it is a more attractive solution to web graphic designers. All included templates are standalone—they do not contain any knowledge of their surroundings, and can thus be reused much more easily. And control is centralized in the main template. On the negative side, the current technical implementation requires the main template to be specified in every action mapping that ends with a Velocity result. If the main template was to be moved or renamed, all action mappings need to be updated. On the other hand, the current solution allows for specifying different main templates if they exist. Another issue is that static parameters in an action mapping are local to the action mapping and not the result mapping. Thus, if two different Velocity results were mapped up for the same action, they would necessarily get the same page and menu. A workaround is to let one of the result mappings chain to another action mapping, which then returns the correct web page, but this is a hack.
A slightly different implementation of the same concept is to create different Ve- locity result types, one for each “background” template. This would move the main template paths from the action mappings to the result types, centralizing the paths, and then the page template paths could be set in the result mappings instead of static parameters. But it would tie the solution to the template engine being used, in our case Velocity, while the current solution is template engine independent. It is a trade-off, and I would go for the solution which is being used today, because it only requires one custom class, a simple, generic interceptor, which is completely transparent and initially just a helper class for forwarding static parameters to the result.
Yet another alternative is to use a decoration framework such as OpenSymphony SiteMesh [18]. My personal opinion is that SiteMesh is overkill when templates in- cluding other templates is an option. SiteMesh works by parsing the web pages returned from the application to remove the meta information and outer HTML ele- ments from the document, so that it can be surrounded by new, common HTML. The meta information extracted is also partially merged into the header section of the new document. For complex enterprise web sites where the web pages are produced by multiple independent subsystems, a SiteMesh type of framework is a good alternative for creating a common look to the subsystems. For applications and sites with a sin- gle web page source, I would try hard to incorporate the common look functionality into the application itself, in order to avoid the processing overhead of parsing and generating new web pages which already have been generated by the application.