Web application components made easy with Composite View
Implement Web applications featuring pluggable components with the Composite View design pattern
In Core J2EE Patterns: Best Practices and Design Strategies, Deepak Alur, John Crupi, and Dan Malks identify 15 J2EE (Java 2 Platform, Enterprise Edition) design patterns, of which the Composite View pattern proves to be one of the most compelling. The Composite View pattern builds composite views from multiple subviews, with the potential to greatly simplify application development by allowing content and layout to be plugged into a framework.
Before the Composite View pattern was widely known, developers knew it as JSP (Java Server Pages) templates; a term that originated in Sun’s J2EE Blueprints. Templates combine the Composite and Strategy patterns (both espoused by the Gang of Four (GOF) in Design Patterns) to separate both content and layout from JSPs. Such separation defines a JSP programming style that lets page authors and software developers work, for the most part, independently.
The templates description in J2EE Blueprints motivated me to write a templates custom tag library, which I contributed to the Apache Struts project in fall 2000. Not long after that, I wrote about that tag library for JavaWorld in “JSP Templates” (September 2000). Consequently, feedback from both JavaWorld readers and Struts users prompted me to rewrite the templates tag library from scratch for my book Advanced JavaServer Pages.
In spring 2001, I contributed some code from the rewritten templates tag library — now known as the regions tag library — to illustrate the Composite View J2EE design pattern in the aforementioned Core J2EE Patterns: Best Practices and Design Strategies.
This article discusses the Composite View pattern by exploring the regions tag library in more detail. Also, because the tag library examined in this article is a second revision of the library discussed in JavaWorld‘s “JSP Templates,” you can view this article as somewhat of a sequel.
The Composite View pattern
Nearly all modern object-oriented development environments support three key objects for flexible and extensible graphical user interface (GUI) development: components, containers, and layout managers. Components represent graphic objects such as text, buttons, or lists. Containers maintain component lists, while layout managers position and size (or lay out) a container’s components.
Developers typically implement components and containers with the GOF Composite design pattern, which lets you compose graphical objects into tree hierarchies. Meanwhile, layout managers are typically implemented with the GOF Strategy design pattern, so that you can change a container’s layout strategy at runtime without changing the container itself. For its part, the J2EE Composite View design pattern acts as a recipe for components, containers, and layout managers for JSP-based Web applications.
At the low end, the Composite View pattern resembles the GOF Composite pattern. In describing the Composite View pattern in Core J2EE Design Patterns, Alur, Crupi, and Malks write, “Use composite views that are composed of multiple atomic subviews…,” which means your views should be composites that can contain other views — an endorsement of the GOF Composite pattern. Those views can be composites also, so you can arrange views in a tree hierarchy.
At the high end, the Composite View pattern details the steps necessary to implement components, containers, and layout managers. The authors continue:
Each component of the template may be included dynamically into the whole, and the layout of the page may be managed independently of the content.
Thus, the authors recommend pluggable views and layout management. Dynamic content and encapsulated layout go a long way toward simplifying and maintaining JSP-based Web applications. Let’s see how.
Note: The complete source code for this article can be downloaded from Resources.
Sections, regions, and templates
Because the Composite View pattern targets Web applications, its components, containers, and layout managers differ from those found in a traditional GUI application. To reflect that difference, in the regions tag library discussed in Advanced JavaServer Pages, I renamed them:
- Section: (component) An object that renders HTML or a JSP
- Region: (container) An object that contains sections
- Template: (layout manager) A JSP that lays out regions and sections
In Figure 1 you’ll see a typical login screen for a Web application implemented with the J2EE Composite View pattern.
In the Webpage shown in Figure 1, one region, representing the entire page, contains four sections:
- Sidebar
- Header
- Content
- Footer
Regions contain sections, but they can also contain other regions, so you can nest regions and sections in a tree hierarchy. The application shown in Figure 1 also employs the Composite View pattern to encapsulate layout, specified in a JSP called a template.
Webpages, like the one shown in Figure 1, comprise two aspects: content and layout. The Composite View pattern makes those aspects pluggable.
Pluggable content
You can easily make content pluggable with either <jsp:include>
or <%@ include %>
. The former dynamically includes content at runtime, whereas the latter includes content at translation time. The JSP for the application shown in Figure 1 uses <jsp:include>
to dynamically include content for each page’s region, as seen below:
Example 1. Pluggable content
<html><head>
<title>Fruitstand.com</title>
</head>
<body background='graphics/blueAndWhiteBackground.gif'>
<table class="legacyTable">
<tr valign='top'><td><jsp:include file="sidebar.jsp"/></td>
<td><table class="legacyTable">
<tr><td><jsp:include file="header.jsp"/></td></tr>
<tr><td><jsp:include file="introduction.jsp"/></td></tr>
<tr><td><jsp:include file="footer.jsp"/></td></tr>
</table>
</td>
</tr>
</table>
</body>
</html>
Pluggable content is preferable to embedded content for two reasons: First, the content — in this case sidbar.jsp
, header.jsp
, and so on — can vary without modifying the displaying JSP file. Second, the extra level of indirection promotes reuse; for example, the preceding code can display any JSP conforming to the same layout and using the same filenames, such as sidebar.jsp
. In contrast, if you embedded the content of sidebar.jsp
, header.jsp
, introduction.jsp
, and footer.jsp
in the file, the code would not be reusable.
Pluggable layout
Pluggable content is great, but typically content and layout are tightly coupled because developers specify them in the same file, as is the case for Example 1. With one more level of indirection, you also can make layout pluggable. Let’s revisit Example 1 and split it into two JSP files. The first, known as a JSP template, encapsulates layout, as seen in Example 2.a:
Example 2.a. A JSP template
<html><head>
<%@ taglib uri='regions' prefix='region' %>
<title>Fruitstand.com</title>
</head>
<body background='graphics/blueAndWhiteBackground.gif'>
<table class="legacyTable">
<tr valign='top'><td><region:render section='sidebar'/></td>
<td><table class="legacyTable">
<tr><td><region:render section='header'/></td></tr>
<tr><td><region:render section='content'/></td></tr>
<tr><td><region:render section='footer'/></td></tr>
</table>
</td>
</tr>
</table>
</body>
</html>
Notice the similarities between Example 2.a and Example 1. The only difference: the template (Example 2.a) uses a custom tag to include content, whereas Example 1 uses the standard JSP include action. Because the template does not directly refer to the files it includes, any JSP conforming to the sidebar/header/content/footer format can reuse the template.
Templates use only the region:render
tag from the regions tag library. That tag extracts the content for each section from a region stored in application scope; for example, in the preceding code, the region:render
tag extracts the content for the sidebar section. Section content, always a string, most often represents a file. By default, the region:render
tag dynamically includes the file represented by a section’s content. In the preceding code, for example, region:render
will include the file /sidebar.jsp
when it renders the sidebar section.
Other JSP files that directly specify content employ templates; for example, Example 2.b lists a JSP that uses the template listed in Example 2.a:
Example 2.b. Render a region
<%@ taglib uri='regions' prefix='region' %>
<region:render template="/MyTemplate.jsp">
<region:put section='header' content="/header.jsp" />
<region:put section='sidebar'content="/sidebar.jsp" />
<region:put section='content'content="/introduction.jsp"/>
<region:put section='footer' content="/footer.jsp" />
</region:render>
Example 2.b’s code demonstrates how to use a template. The region:render
start tag stores the template name and creates a new region. The region:put
tags create new sections in that region. Finally, the region:render
end tag dynamically includes the template specified in the region:render
start tag. Subsequently, the template renders the aforementioned regions.
So, how do you benefit from this indirection? You can use a single template, which encapsulates layout, for all Webpages with similar formats. As a consequence, you can change the layout for many Webpages simply by modifying a single template. Also, because you reuse the layout for many pages, that layout does not have to be repeated in JSP files that use it.
Direct content
Sometimes you want to render section content directly, rather than treating the content as an included file; for example, the JSP shown in Figure 2 sets the window title to Direct Content.
The JSP shown in Figure 2 is listed in Example 3.a:
Example 3.a. Specify direct content
<%@ taglib uri='regions' prefix='region' %>
<table class="legacyTable">
<tr>
<td>
<region:render template="template.jsp">
<region:put section='title'
content="Direct Content" direct="true"/>
<region:put section='header' content="/header.jsp"/>
<region:put section='sidebar' content="/sidebar.jsp"/>
<region:put section='content' content="/content.jsp"/>
<region:put section='footer' content="/footer.jsp"/>
</region:render>
</td>
</tr>
</table>
The JSP in Example 3.a specifies a section for the window title. But unlike the header, sidebar, content, and footer sections, you don’t want the template to regard the title section’s content as a filename; instead, you want that content to resolve to text. To accomplish that task, you specify true
for the direct attribute of the region:put
tag.
You don’t need to change a template to support direct rendering of a section’s content. Example 3.b lists the template used by the JSP listed in Example 3.a:
Example 3.b. Render direct content with a template
<html><head>
<%@ taglib uri='regions' prefix='region' %>
<title><region:render section='title'/></title>
</head>
<table class="legacyTable" border="1" height="250" width="450">
<tr> <%-- Sidebar --%>
<td valign='top' width="25%">
<region:render section='sidebar'/>
</td>
<td valign='top' align='center' width="*">
<table class="legacyTable" height="250">
<tr> <%-- Header --%>
<td align='center' height="20%">
<region:render section='header'/>
</td>
</tr> <%-- Main Content --%>
<td align='center' height="*">
<region:render section='content'/>
</td>
</tr> <%-- Footer --%>
<td align='center' height="15%">
<region:render section='footer'/>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body></html>
The JSP files for the sidebar, header, content, and footer sections are nothing more than simple strings with a specific font. I’ve listed all four files in Example 3.c:
Example 3.c. Placeholder JSP files
<font size="5">SIDEBAR</font> <!--sidebar.jsp-->
<font size="5">HEADER</font> <!--header.jsp-->
<font size="5">CONTENT</font> <!--content.jsp-->
<font size="5">FOOTER</font> <!--footer.jsp-->
I employ both the template listed in Example 3.b and the placeholder files listed in Example 3.c throughout the remainder of this article.
Optional content
All content displayed in a region is optional, which increases a single template’s reuse because more JSPs can use the same template. Figure 3 shows an application with two regions that use the same template. The region on the left in Figure 3 specifies the content displayed by the template, whereas the region on the right omits the content section.
The application shown in Figure 3 is listed in Example 4:
Example 4. Optional content
<%@ taglib uri='regions' prefix='region' %>
<table class="legacyTable">
<tr>
<td valign='top'>
<font size="5">Specifying All Content:</font>
<table class="legacyTable" cellspacing='20'>
<tr>
<td> <%--all content is specified for this region--%>
<region:render template="hscf.jsp">
<region:put section='header'
content="/header.jsp"/>
<region:put section='sidebar'
content="/sidebar.jsp"/>
<region:put section='content'
content="/content.jsp"/>
<region:put section='footer'
content="/footer.jsp"/>
</region:render>
</td>
</table>
</td>
<td valign='top'>
<font size="5">Omitting Content:</font>
<table class="legacyTable" cellspacing='20'>
<tr>
<td> <%--content is omitted for this region--%>
<region:render template="hscf.jsp">
<region:put section='header'
content="/header.jsp"/>
<region:put section='sidebar'
content="/sidebar.jsp"/>
<region:put section='footer'
content="/footer.jsp"/>
</region:render>
</td>
</tr>
</table>
</td>
</tr>
</table>
The preceding application uses the template hscf.jsp
listed in Example 3.b.
Role-based content
Many Web applications display role-based content; for example the application shown in Figure 4 includes a hidden region displayed only if the user is authorized as a curator. The inset shown in Figure 4 represents the same JSP when the user’s role is curator.
You can specify a section as role dependent with the region:put
tag’s role
attribute. For example, the JSP in Figure 4 makes the edit panel section dependent upon the curator user role, like this:
<region:render template="hscf.jsp">
...
<region:put section='editPanel' content="/header.jsp"
role="curator"/>
...
</region:render>
In the preceding code, the region:put
tag only creates the editPanel
section if a user’s role is curator.
The region:render
tag has an analogous role attribute:
<%@ taglib uri='regions.tld' prefix='region' %>
...
<table class="legacyTable">
...
<td><region:render section='editPanel' role="curator"/></td>
...
</table>
...
In the code above, region:render
will only render the editPanel
section if the user’s role is curator.
Define regions separately
As you’ve seen so far in this article, you can create and render a region all at once (see Example 3.a and Example 4). However, the region tag library discussed in this article lets you define a region in one place, and render it in another. For example, Example 5.a shows how to define a region:
Example 5.a. Define regions
<%@ taglib uri='regions' prefix='region' %>
<region:define id='SIDEBAR_REGION' scope="application"
template="hscf.jsp">
<region:put section='header' content="/header.jsp"/>
<region:put section='sidebar' content="/sidebar.jsp"/>
<region:put section='content' content="/content.jsp"/>
<region:put section='footer' content="/footer.jsp"/>
</region:define>
Once a region has been defined with region:put
, it can be rendered with the region:render
tag, as listed in Example 5.b:
Example 5.b. Render predefined regions
<%@ taglib uri='regions' prefix='regions' %>
<%@ include file="/regionDefinitions.jsp" %>
<region:render region='SIDEBAR_REGION'/>
The JSP listed in Example 5.b includes a JSP file — regionDefinitions.jsp
— that defines regions. One of those regions, SIDEBAR_REGION
as defined in Example 5.a, is subsequently rendered by the region:render
tag.
Why would you want to define and use regions separately, as illustrated above? First, the JSP code that renders the region is significantly simplified. Second, it is convenient to store an application’s region definitions in a single place because it’s easy to find a region’s definition when you need to change it.
Composite regions
The Composite View pattern’s central motivation is to compose views into tree structures. The regions tag library implements that feature with the GOF Composite design pattern. Figure 5 shows an example of how you can create composite views.
The application shown in Figure 5 defines a region, SIDEBAR_REGION
, that contains another region, BORDER_REGION
. The application displays the SIDEBAR_REGION
. Both are listed in Example 6:
Example 6. Composite regions
<%@ taglib uri='regions' prefix='region' %>
<region:define id='SIDEBAR_REGION' scope="application"
template="hscf.jsp">
<region:put section='header' content="/header.jsp"/>
<region:put section='sidebar' content="/sidebar.jsp"/>
<region:put section='content' content="BORDER_REGION"/>
<region:put section='footer' content="/footer.jsp"/>
</region:define>
<region:define id='BORDER_REGION' scope="application"
template="tlbr.jsp">
<region:put section='top' content="/top.jsp"/>
<region:put section='left' content="/left.jsp"/>
<region:put section='right' content="/right.jsp"/>
<region:put section='bottom' content="/bottom.jsp"/>
</region:define>
Because regions can contain both sections and other regions, you can nest regions as deeply as you wish. (Note: The template used by the BORDER_REGION
is not listed in this article, but is available for download.)
Extend regions
Just as Java classes can inherit methods from their superclass, regions can inherit sections (and contained regions) from another region. Figure 6 shows a region that inherits from a SIDEBAR_REGION
and overrides the header and footer.
The application shown in Figure 6 renders an EXTENDED_SIDEBAR_REGION
that extends SIDEBAR_REGION
. Both regions are listed in Example 7:
Example 7. Extended regions
<%@ taglib uri='regions' prefix='region' %>
<region:define id='SIDEBAR_REGION' scope="application"
template="hscf.jsp">
<region:put section='header' content="/header.jsp"/>
<region:put section='sidebar' content="/sidebar.jsp"/>
<region:put section='content' content="/content.jsp"/>
<region:put section='footer' content="/footer.jsp"/>
</region:define>
<region:define id='EXTENDED_SIDEBAR_REGION' scope="application"
region='SIDEBAR_REGION'>
<region:put section='header' content="/overridden-header.jsp"/>
<region:put section='footer' content="/overridden-footer.jsp"/>
</region:define>
In the preceding code, I’ve defined EXTENDED_SIDEBAR_REGION
as a region, not as a template. When you define a region in terms of another region (which is known as the base region), it inherits the sections and regions contained in the base region. You can override inherited sections and regions in the extended region.
EXTENDED_SIDEBAR_REGION
inherits the header, sidebar, content, and footer sections, and overrides the header and footer. I’ve listed both overridden-header.jsp
and overridden-footer.jsp
below:
<font size="5" color="gray">OVERRIDDEN HEADER</font> <!-- overridden-header.jsp -->
<font size="5" color="gray">OVERRIDDEN FOOTER</font> <!-- overridden-footer.jsp -->
Just as inheritance is a powerful feature of object-oriented languages, the ability to extend regions is one of the most compelling features of the regions tag library.
Combine features
You can combine the features of the regions tag library. As such, the application shown in Figure 7 illustrates the power of the Composite View design pattern and the regions tag library.
The application shown in Figure 7 is listed in Example 8:
Example 8. Combine features
<%@ taglib uri='regions' prefix='region' %>
<region:define id='SIDEBAR_REGION' scope="request"
template="hscf.jsp">
<region:put section='header' content="/header.jsp"/>
<region:put section='sidebar' content="EXTENDED_BORDER_REGION"/>
<region:put section='content' content="/content.jsp"/>
<region:put section='footer' content="/footer.jsp"/>
</region:define>
<region:define id='BORDER_REGION' scope="request"
template="tlbr.jsp">
<region:put section='top' content="/top.jsp"/>
<region:put section='bottom' content="/bottom.jsp"/>
<region:put section='left' content="/left.jsp"/>
<region:put section='right' content="/right.jsp"/>
</region:define>
<region:define id='EXTENDED_SIDEBAR_REGION' scope="request"
region='SIDEBAR_REGION'>
<region:put section='header' content="/overridden-header.jsp"/>
<region:put section='sidebar'content="BORDER_REGION"/>
<region:put section='content'content="SIDEBAR_REGION"/>
</region:define>
<region:define id='EXTENDED_BORDER_REGION' scope="request"
region='BORDER_REGION'>
<region:put section='top' content="/overridden-top.jsp"/>
<region:put section='bottom' content="/overridden-bottom.jsp"/>
<region:put section='right'>
<font size="4">Direct Content</font>
</region:put>
</region:define>
I’ll let you figure out which region is displayed in Figure 7’s application, and how those regions fit together. If you take the time, you might reconsider whether it’s necessary for such a complicated configuration. Nevertheless, the regions tag library can render any combination of nesting and overriding.
Master the possibilities
Numerous ways exist to implement the Composite View design pattern. As one example, I’ve shown how the regions tag library implements Composite View. With the regions tag library’s three tags — region:define
, region:put
, and region:render
— you can implement modularized, JSP-based Web applications that are robust, maintainable, and extensible.