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

David Karam is usually a programmer. Sometimes he is a designer, musician or teacher.
Related Topics >> Programming   |