Skip to main content

Current CMS

August 15, 2006

{cs.r.title}






I operate a small company, Post Tool, with my partner Gigi
Obrecht. Our focus over the last nine years has been the design and
development of database driven, content managed Web sites. In 1997
the California College of the Arts (CCA, formerly the California College of Arts and Crafts)
asked us to create their Web presence. Gigi and I were both
teaching there at the time. The dotcom was at its roundest point
in the Bay Area.

I decided on a project to develop a rudimentary content
management system (CMS). It was written in Perl and used flat text
files for storage. The primary user interface listed
directory-grouped "pages." The rich text editor was developed using
Shockwave Director. While the user interface was not terribly
elegant, this system was very useful to the college. Many years
later, we designed new templates for the site. CCA was still using
the same Perl-based system.

In the meantime, I had transitioned fully to server-side Java.
"Current CMS" became the focus of my work.

Almost nine years later, I have finally arrived at something that
is worth sharing. The major components of Current CMS include a
scaffolding generator, a simple persistence mechanism, a layer for
user filtered and logged database transactions, template
management, a site map formality, as well as JSP pages and servlet
controllers for managing content. The code base was entirely
rewritten earlier this year. I will try to write clearly about it
now. Ready?

Requirements

Many Web frameworks address the validation and flow of form
pages. Current CMS does not rely on this type of framework nor does it rely on a
template engine beyond JSP and Expression Language (EL).
Current CMS does not rely on a third-party persistence mechanism either. Web
applications are often bound by memory limitations, so the code
base has few dependencies. From Apache Commons: upload, io, lang,
logging. From Apache tag libraries: standard, datetime, string. And
of course, there is the JDBC Connector. These dependencies come to
about 1.5 MB in file size.

Java 5 is required. MySQL is used by default, but this can
easily be changed to a database of your choice. Most applications
we deploy easily handle moderate traffic with a 48 MB heap.

A Quick Note About Information Architecture

All content managed systems require some form of Entity
Relationship Diagram (ERD) to be defined prior to development. There are
many methods of determining a client's requirements for the ERD.
Usually, I begin by asking them to describe key pages from the
site-to-be, questions like: What is on the home page? When you
click on that, what does the subsequent page look like? The
results generally look something like those shown in Figure 1.

A 'wireframe' (not an ERD!) for a wine distributor
Figure 1. A "wireframe" (not an ERD!) for a wine distributor
(click the image to enlarge)

From this exercise with our clients, we can produce a reasonable
ERD, which will be used as the foundation for the CMS. Current CMS
requires the ERD in a particular XML format. The entities
configuration file will be used by the scaffolding generator to
produce code templates. It will also be used by the application to
access meta data about the objects, their fields, and references. I
considered disposing of the XML configuration file in favor of Java 5
annotations but came back to the XML. In a digression, I could
elaborate on the decision. Instead, let's examine the details of
the configuration files.

A sample ERD for a wine distributor
Figure 2. A sample ERD for a wine distributor

Configuration

Current CMS requires a particular configuration of the WEB-INF
directory for each application deployment. Mostly it conforms to
Sun's established requirements for a Web Application Archive.
Following is the basic structure.

  • web.xml
  • Tag library descriptor files: dt.tld, str.tld,

    pt.tld, c.tld

  • lib - a directory that contains the required JARs
  • config - a directory of Current CMS configuration
    files

The standard deployment descriptor, web.xml, contains
many entries for Current CMS. There is a URLFilter, a
servlet that loads an initialization of the application, as well
as many controller servlets used by the CMS framework. Of note is
the initialization servlet com.posttool.cms.CMSConfig,
which loads the case configuration files that we are about to
examine, located in WEB-INF/config.

The three required configuration files are located in
WEB-INF/config: deployment.xml, entities.xml,
and templates.xml. The sample configuration files are part
of an installation for a wine distributor.

deployment.xml

In general, we find that there are at least three deployments of any
Web application in development. There may be more, depending on the
number of developers hosting a local version of the site. To track
deployment variations, we place all descriptors in a single file.
When the framework is initialized, a deployment is chosen by the
CMS. com.posttool.cms.CMSConfig compares the location of the configuration file and the servletPath
parameter of each deployment. If it cannot find a matching path, it
will log an error displaying the path it expects to see. If the
error is produced, copy the path and make sure it is listed exactly
in one deployment as servletPath. Note: It should
also make a database log in an attempt for complete choice
verification because two deployments may have the same base
path to the webapp; it doesn't at the moment.

In the following example, the first deployment will be used on
my development machine. The second deployment is chosen by the
initializing servlet on the live server. The last deployment, in
this case, will be selected when running the Ant task to generate
the scaffolding, which is why it does not require attributes
related to HTTP:

<deployments>

  <deployment name="grateful-local"
    servletHttp="http://localhost:8080/TGPI/"
    servletPath="K:\Tomcat 5.5\webapps\TGPI"
    staticResourceHttp="http://localhost:8080/TGPI/resources/"
    staticResourcePath="K:\Tomcat 5.5\webapps\TGPI\resources"
    databaseUser="xxx"
    databasePass="xxx"
    databaseName="grateful" />
 
  <deployment name="gratefulpalateimports.com"
    servletHttp="http://gratefulpalateimports.com/"
    servletPath="/webapps/ROOT/"
    staticResourceHttp="http://res.gratefulpalateimports.com/"
    staticResourcePath="/public_html/resources/"
    databaseUser="xxx"
    databasePass="xxx"
    databaseName="grateful_english" />

 
  <deployment name="grateful-generator"
    servletPath="K:\IDEROOT\TGPI\WebApp"
    databaseUser="xxx"
    databasePass="xxx"
    databaseName="grateful" />
 
</deployments>
entities.xml

The entities XML configuration file will represent the ERD. Our
case study is interested in wines, vineyards, and
their related presses. The entities root
element can contain any number of object elements.
Each object may contain any number of

references and fields. All of these
elements require a name attribute and may have a
description.

The reference element has two additional attributes. The
object attribute must make reference to the name of
another object in the schema. Note: It cannot
reference itself (yet).
The cardinality attribute can only
be "one" or "many." The name attribute should be
synchronized with a corresponding reference in the related
object. In the case of the vineyard, its press is
known as "PressVineyard," which means that in this bidirectional
relationship, a press instance knows its vineyards by the same
name.

Reference cardinality is meant to be very simple. It is not
intended to model much more than bidirectional, many-to-many
relationships. This model is appropriate 90 percent of the time. Historic versions of the Current CMS code modeled other
more complex restraints, but in this latest code base, I have not
found the time or need to add the required detail to the code. If
you are interested in making this area of the persistence mechanism
more robust, please contact me.

The field element is straightforward. The
type attribute can be one of the following values:
integer, decimal, file, date, string, html, password. By examining
the generated scaffolding, you can see how these types map to Java
and SQL.

Finally, there is the definition of a site contributor. A class
and table will be made for the people who will be maintaining the
content of the site. The fields listed are required at a minimum by
the class this object extends,
com.posttool.dbouser.AbstractUser. Of course, you can
add more fields if you want to store more information about your
contributors:

<entities name="The Grateful Palate" 
  package="com.gratefulpalateimports">

  <object name="Vineyard" description="Vineyards">
    <reference object="Press" name="PressVineyard"
                 cardinality="many" description="press" />
    <reference object="Wine" name="VineyardWine"
                 cardinality="many" description="wines" />
    <field name="name" type="string" length="128" />
    <field name="winemakers" type="string" length="128"
                 description="winemaker(s)"/>

    <field name="owners" type="string" length="128"
                 description="owner(s)"/>
    <field name="overview" type="html"
                 description="vineyard overview" />
    <field name="winemakersOverview" type="html"
                 description="winemaker's popup" />
    <field name="logo" type="file"
                 description="vineyard logo (png or gif)" />
    <field name="shield" type="file"
                 description="the shield (png)" />
  </object>

  <object name="Wine" description="Wines">
   <reference object="Press" name="PressWine"
        cardinality="many" description="press" />
   <reference object="Vineyard" name="VineyardWine"
        cardinality="one" description="from vineyard" />
    <field name="name" type="string" length="128" />
    <field name="color" type="string" length="16">
             <restrictions>

                 <option value="Red" />
                       <option value="White" />
                       <option value="Rose" />
                     </restrictions>
    </field>
    <field name="price" type="decimal" />

    <field name="description" type="html" />
    <field name="note" type="string" length="255"
                 description="tasting notes" />
    <field name="bottle" type="file"
                 description="image of bottle (png)" />
    <field name="landscape" type="file"
                 description="landscape (jpeg or png)" />
  </object>

  <object name="Press" description="Press">

    <reference object="Vineyard" name="PressVineyard"
                 cardinality="many" description="about vineyard" />
    <reference object="Wine" name="PressWine"
                 cardinality="many" description="about wine" />

    ...fields omitted...
  </object>

  <!-- cms contributor info -->

  <object name="Contributor" extends="com.posttool.dbouser.AbstractUser"
          description="CMS Contributor" icon="user_small.png">
    <field name="firstName" type="string" length="64"
                 description="first name" />
    <field name="lastName" type="string" length="64"
                 description="last name" />
    <field name="email" type="string" length="128"
                 description="email address" />
    <field name="password" type="password" length="128"
                 description="your password" />
    <field name="note" type="html" description="note" />

  </object>

</entities>
templates.xml

Templates are used to present published content. If you don't
plan on publishing any information from the content management
system, you can skip to "Generating the Scaffolding." If you do,
the Current CMS template formalities will help you.

Generally, publicly viewed page templates will be created by a
graphic designer and HTML/JavaScript developer. Our workflow allows
us to develop these pages prior to the completion of that work and
minimize integration time. The templates.xml
configuration file is used by the scaffolding generator, the CMS,
and the URLFilter. The scaffolding provides the basics of a
model/view pairing pattern. One part is the Java selection and
representation of page relevant data. The other is JSP templates
with expression language stubs dereferencing content produced by
the Java view.

The configuration file contains a list of template elements. Each has three attributes: name,
file, and class. The "name" is how the
template is known. Using factory methods, a programmer can request
a template by this name. More significantly, the name signifies a
URL pattern for this type of template. The "file" is a path for a
JSP template. The "class" is a Java program that implements the
interface com.posttool.site.TemplateView and is
responsible for providing the appropriate data for its template as
name/value pairs.

The parameter elements are not required unless a
template needs some kind of input. If this required input is a
record id, then the parameter type will refer to one of the entity
definitions. The only other valid type of parameter is a string
(for now). This list of templates is a direct representation of the
wireframe documentation shown as Figure 1:

<templates>

  <template name="home" file="/jsp/home.jsp"
      class="com.tgpimports.template.HomeView" />

  <template name="search" file="/jsp/search.jsp"
      class="com.tgpimports.template.SearchView" >
    <param name="type" type="string">The type of search</param>
    <param name="keyword" type="string">The keyword</param>

    <param name="region" type="string">The region</param>
    <param name="page" type="int">The page</param>
  </template>

  <template name="vineyard" file="/jsp/vineyard.jsp"
      class="com.tgpimports.template.VineyardView">

    <param name="vineyard" type="Vineyard" required="yes"
                 count="1">Vineyard</param>
  </template>

  <template name="press" file="/jsp/press.jsp"
      class="com.tgpimports.template.PressView" >
    <param name="press" type="Press" required="yes"
                 count="*">Press</param>

  </template>

  ... more templates ...
 
</templates>
pages.xml

The pages configuration file contains a hierarchical arrangement
of page elements. Each page in our CMS-driven site is
described by a name, a URL, a template, and possibly some parameters
(like which record to show by default). The template element must name one of the templates defined in the previous
configuration file. The URLFilter will look to this
configuration file first to see if the request URL matches one of
the page entries.

This file can be managed entirely by using an interface provided
in the CMS Web application shown in Figure 3. There is no need to edit the
XML directly, although the interface doesn't accommodate all
requirements. Using a text editor to perform global find and
replace operations is helpful, for example, if a template name
changes. Interfaces, as well as configuration files, should be created for this, but I would need to enlist some help. "Anyone?"

CMS interface for manipulating pages.xml
Figure 3. CMS interface for manipulating pages.xml
(click the image to enlarge)

events.xml

Although it is not necessary in our case study, in certain cases a programmer
may want to be notified of database operations. Entries into the
events XML file make this possible. The listener must implement
com.posttool.cms.record.RecordChangeListener.
Generally the purpose of this kind of listener is to spawn tasks
when data is changed or removed. A
RecordChangeListener may create and delete
thumbnails of uploaded images in a background thread, for example.
Another may email a notification if a certain data constraint
caused an exception:

<events>
  <event type="Vineyard" listener="com.gratefulpalateimports.events.NotifyPress" />

</events>







Generating the Scaffolding

Ant task

Finally, we can build and test the application. Make sure you
have all the CMS requirements in your classpath. The following
build fragment is all you need to generate the scaffolding. Ours
usually have other targets that rebuild the database or copy and
compile files.

Make sure the generator doesn't complain that it can't find
a configuration. Your webapp property value should
point to the location of a webapp configured with the
WEB-INF directory containing all the elements just
covered. Remember, the deployments.xml file within
WEB-INF/config must have a matching servletPath value:

<target ...>
  <taskdef name="generate" classname="com.posttool.dbo.generate.GeneratorTask" />
  <generate webapp="${webapp}" scaffolding="${scaffolding}" />
  ... move generated classes to $webapp and compile ...
  ... build/update database ...

</target>
Generated: POJOs with DBO annotation

After successfully generating the scaffolding, examine the
directory src. Within the directory you should find properly nested files, Java
files matching the package configuration, and object names from the
entities configuration file. In our case, we have
com/gratefulpalateimports/*.java. Each should look pretty
much like any POJO with the addition of one type of annotation:
com.posttool.dbo.DBO.

The DBO annotation is really simple. It has two string attributes,
a field and table, and one boolean, id. The following code samples
are snippets from generated POJOs:

@DBO (table = "Vineyard")
public class Vineyard {

  @DBO (id = true)
  private Integer id;

  @DBO (field = "name")
  private String name;
 
  @DBO (field = "press_id", table = "_PressVineyard")
  private Collection<Press> PressVineyard = new ArrayList<Press>();
  ...
Generated: SQL create and alter

Corresponding SQL create statements are generated
as create.sql. IDs are auto-increment integers. The mapping
of field types is fairly straightforward. References with a
cardinality of "one" are included as a foreign key integer in the
table. Bidirectional joins are modeled as a table with two foreign
key columns, one to each table. You can see the relationship
between the Java class annotations and the table structure if you
open both the POJOs and the generated create
statements. If the database already exists, alter
statements will be generated as alter.sql. They are meant to
help with structural changes but must be hand-edited at this time
because the differencing engine is not complete.

The following fragment shows a small sample of the
generated create including the bidirectional join table. The order
field maintains a user-specified list order, managed by the
persistence mechanism:

CREATE TABLE Vineyard (
  id INT(11) NOT NULL auto_increment,
  name VARCHAR(128),
  winemakers VARCHAR(128),
  ... more fields ...
  PRIMARY KEY (id)
);

... more tables ...

CREATE TABLE _PressVineyard (
  press_id INT(11) NOT NULL,
  vineyard_id INT(11) NOT NULL,
  _order INT(11),
  FOREIGN KEY (press_id) REFERENCES Press (id),
  FOREIGN KEY (vineyard_id) REFERENCES Vineyard (id),
  PRIMARY KEY (press_id, vineyard_id)
);

... more join tables ...

If you are uninterested in the template formalities presented by
Current CMS, skip to "Where is the CMS?" now.

Generated: Page Data Models

Following is a sample of a TemplateView. This is a
class that intends to provide data for a template in a structure
that is highly coupled with the display of that information. The
display will be handled by the JSP template, which we will look at next. In this
case, a particular wine and all its collections are populated from the
database.

We believe a line should be drawn between the preparation
of the data and its display: The Java class is responsible for
preparing structures that can dereferenced without conditions, if
possible. For example, if the designer requires two columns or some
text, instead of requiring the template to perform a modulus
operation, we prefer to make two lists, column1 and
column2. These values are then put in a
HashMap with simple names ready to become request
attributes, dereferenced in the JSP page with EL:

public class WineView implements TemplateView {
  private Map<String,Object> values;
  private AbstractUser user;
  private SitePage page;

  public WineView(AbstractUser user, SitePage page) {
    this.user = user;
    this.page = page;
  }

  public boolean isComplete() { ... }

  public String getMessage() { ... }

  public Map<String, Object> getValues() {
    if (user == null) {
      return;
    }
    int wineId = Text.toInt(page.getParameterValue("wine"));
    Wine wine = user.read(Wine.class, wineId);
    user.fill(wine);
    values = new HashMap<String, Object>();
    values.put("wine", wine);
  }
}
Generated: JSPs

The JSP page for this TemplateView as defined by the
templates.xml configuration file is /jsp/wine.jsp. As you
can see, it attempts to display the record retrieved by the
template view, as ${wine.name}. Each field type
is generated with its own special kind of dereferencing. The field
"description" was typed as "html" in entities.xml. This is
why the XML within that field is not escaped. Date fields produce
appropriate date formatting tags. The pt:img helps to
create and display sized, adjusted images.

Three standard includes come in every template: head, menu, and
footer. Some templates, like our example, reference "related"
includes. For each entity you have a related include. This makes it possible for a wine to list its vineyards as well as the press, with both using the same short view for these related entities:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<%@ page language="java" pageEncoding="UTF-8"%>
<%@ taglib uri="/WEB-INF/c.tld" prefix="c"%>
<%@ taglib uri="/WEB-INF/str.tld" prefix="str"%>
<%@ taglib uri="/WEB-INF/dt.tld" prefix="dt"%>
<%@ taglib uri="/WEB-INF/pt.tld" prefix="pt"%>

<html>

<head>
    <title>wine</title>
    <meta http-equiv="keywords" content="" />
    <base href="${baseUrl}" />
    <link rel="stylesheet" type="text/css" href="css/common.css" />
</head>

<body>

<%@ include file="include/head.jspf" %>

<div id="wine">
  <div class="item">
    ${wine.name}
    ${wine.color}
    ${wine.region}
    ${wine.price}
    <c:out escapeXml="no" value="${wine.description}" />
    <pt:img file="${wine.bottle}" />

    <pt:img file="${wine.landscape}" />
    <div class="related">
      <c:set var="__vineyard" value="${wine.vineyardWine}" scope="request"/>
      <jsp:include page="include/related/vineyard.jsp" flush="true"/>
      <c:set var="__press" value="${wine.pressWine}" scope="request"/>
      <jsp:include page="include/related/press.jsp" flush="true"/>

    </div>
  </div>
</div>

<%@ include file="include/menu.jspf" %>
<%@ include file="include/footer.jspf" %>

</body>
</html>
Where is the CMS?

The CMS itself is actually static code. It can be copied to the
Web application directory as cms. If you generate the
scaffolding and move it to the appropriate spots in your Web
application (or create Ant tasks to do this), the CMS will be
operational. The complete installation steps are:

  1. configure WEB-INF directory: web.xml,
    deployments.xml, entities.xml, templates.xml
    (optional), *.tld, lib/*.jar
  2. generate scaffolding via Ant task
  3. copy Java scaffolding to webapp/WEB-INF/classes
  4. copy cms directory to webapp/cms
  5. create the SQL database

You can download the application template source code (available
in the
Resources section) to look at the
case application configuration, complete with the generated
scaffolding and a more elaborate Ant build. You will be able to
deploy it if you make an entry in the deployments configuration
file. Check out the project page for more information.

Current CMS API

Following is a high-level walkthrough of some of the APIs used for
Current CMS.

Persistence: com.posttool.dbo

Current CMS relies primarily on a simple persistence mechanism
for all database operations: com.posttool.dbo. The
persistence mechanism is broken into three static classes that read,
write, and count. Data selection is managed using an object-oriented
approach to building the select.

The following code sample manages a list of Log
instances. All logs will meet a condition like

objectId==3
and objectName=="Car" and id != 15
. The
ConditionGroup allows us to build this flat condition.
It can contain any ICondition. Most of the classes in
the package com.posttool.dbo.sql.clause implement this
interface, including ConditionGroup.

The static method SQLRead.list requires two
arguments: the type of class to return (which must have DBO
annotations); and an ICondition. This method returns
List<Log> logs matching the condition. The list
is updated with standard setter methods, and finally the list is
saved to persistence storage:

ConditionGroup matchingLogs = new ConditionGroup();
matchingLogs.add(new Equals("objectId", log.getObjectId()));
matchingLogs.addAnd(new Equals("objectName", log.getObjectName()));
matchingLogs.addAnd(new NotEq("id", log.getId()));
List<Log> logs = SQLRead.list(Log.class, matchingLogs);
for (Log l : logs) {
       l.setStatusCode(status.toString());
       l.setCompletionDate(new Date());
}
SQLWrite.save(logs);

In the following code sample, a Select statement is
created with a SimpleCondition. This simple condition
gives the programmer direct access to the text of the where phrase
in the final SQL statement. The code should be easily read as
something like: select all artists with last name beginning with
"L", starting at the tenth and showing up to 20, ordered by ascending
last name:

Select select = new Select();
select.setWhere(new Where(new SimpleCondition("lastName","LIKE", ""L%"")));
select.setTop(10);
select.setSize(20);
select.setOrder("lastName ASC");
List<Artist> artists = SQLRead.list(Artist.class, select);

The previous two examples do not take into consideration persistence of entity
references or bidirectional links. These references are
Collection fields in the POJOs and join tables in the
database. Operations on the join tables are triggered lazily by
specifying the collection in the context of the fill and save static methods. This example selects car #32,
selects its "options," removes option 4, and saves the change. In
this and all cases, I try to keep the persistence mechanism close
to SQL so that the programmer is fully aware of each
transaction:

Car car = SQLRead.byId(Car.class, 32);
SQLRead.fill(car, car.getOptions());
car.getOptions().remove(4);
SQLWrite.save(car, car.getOptions());
The Abstract User: com.posttool.dbouser

You may have noticed the line

Wine wine =
user.read(Wine.class, id);
in the code sample from the
scaffolding WineView implements TemplateView. The
user, an AbstractUser, is passed to the
TemplateView by the URLFilter. A set of
the AbstractUser methods moderate database access.
This access is based on simple rules that come mostly from four
tables added automatically by the scaffolding generator.

For most operations, instead of using the unmoderated access
presented by the static methods of SQLRead and
SQLWrite, we use an instance of the
AbstractUser. This ensures that all reading
transactions only show the user-appropriate records. In addition, it restricts
writing and update privileges. It also logs all activity. Current
CMS contains a Web-based interface for creating and assigning
privileges to users. This information is stored in an XML file
within the WEB-INF/config/users directory, as shown in Figure
4.

CMS interface for manipulating user permissions
Figure 4. CMS interface for manipulating user permissions (click the image to enlarge)

The AbstractUser can read, save, publish,
unpublish, delete, and version records depending on their
permissions. Permissions are set by the creator of the user. In the
case study, the AbstractUser was called a Contributor. The
administrator Contributor is capable of creating another
Contributor. Once this second Contributor is created, it is
necessary for the administrator to assign permissions to each
entity. The permissions are yes, no, and request for the actions
listed above and shown in Figure 4. Fields and references can also be
masked. If the admin allows the new Contributor to create
Contributors, then a hierarchy can be built. This hierarchy allows
CMS users to send a chain of publication requestors. Each
Contributor can assign her permissions (or less) to other
Contributors she has created.

Four other classes are added to the generated
framework. These classes are required for the CMS and application
developer to maintain log files for transactions as well as version
status and assignment for all records. The classes (and tables) are,
by default: CMSLog, CMSMessage, CMSAssignment, and
CMSVersion. CMSLog records all transactions
that change the database (including everything but reading it).
CMSMessages are between users of the system as well as
automated notification of record updates.
CMSAssignment designates which users can edit which
records. CMSVersion lists revision history as well
as the publication status of every record. All of these tables are
moderated by using the data access method of the AbstractUser.

Many methods are related to using AbstractUsers and
accessing Log/Assignment/Version metadata on records. Here is
a list of the commonly used methods:

user.save(car);
user.save(car, car.getOptions());
user.publish(car);
user.unpublish(car);
Car carVersion = user.version(car);
user.delete(car); // by default, delete changes the version status
                  // to 'deleted', it does not delete the row
...
Assignment a = user.read(Assignment.class, 23);
List<T> collection = user.list(rootClass.getType(), select, VersionStatus.active);
List<LoggedObject> logged = LoggedObject.fill(collection);
...
List<Assignment> allAssignments = user.list(Assignment.class, condition);
AbstractAssignment.fillContributors(allAssignments);
...
others
The Site: com.posttool.site

In many cases, this content management is in place for
use producing a public Web site. To complete this
article, we will examine the classes related to the public site.
These classes are part of the package
com.posttool.site.

The URLFilter was mentioned earlier in the article.
The filter is set by web.xml to handle all requests with the
extension .html.

This filter tries to match the requested URL with
urls listed in the site map (pages.xml).
Each page element in the XML file is represented as an instance of
SitePage. The found page is cloned (or if no page is
found, the root is cloned). If any arguments are in the URL,
that information is added to the page clone. Often, without URL
arguments for use by the CMS interface, the page should contain all the information necessary to produce a response. This includes a view
(JSP template) and a model (the data that goes in that
template).

From the page, a Template is produced (configured in
templates.xml). The template indicates the model class that
implements TemplateView. The model class produces the
data for the template by examining the properties of the page and
making database queries. The results are placed in the request for
JSP access. Each template also indicates a JSP view file. The view
includes HTML code and expression language dereferencing,
formatting, and iteration of the data provided by the model
class.

A Tiny Tag Library: com.posttool.tag

We elected to write a few tag libs ourselves. Two key
tags are pt:drawString and pt:img. The
first, drawString, will create an image file from a text field,
based on a True Type font. We use this in almost every site we make
for headlines, artists' names, and so on. The pt:img is
useful for generating thumbnails and manipulating bitmaps in
various ways. Beside scaling, the image may turned to grayscale with
a color wash placed on top. This approach is useful for creating highlight
states for images from within JSPs. The other tags are concerned
with formatting text and dates.

Now What?

So much more detail and explanation could go into this article.
As I write about each part of the code, I think of dozens of
details that need to be documented as well as code improvements and
configuration interfaces. I hope some of you find this information useful,
and I hope those who do will have the opportunity to improve it.
Get in touch, and let's talk CMS!

Resources

width="1" height="1" border="0" alt=" " />
David Karam is usually a programmer. Sometimes he is a designer, musician or teacher.
Related Topics >> Programming   |