ACS Java 4.0 Templating System Developers' Guide

by Bill Schneider

The big picture

The ACS Java 4.0 templating system uses the same .adp templates as the ACS 4.0 classic (Tcl) system. In ACS Java, data sources are populated in JSP pages, and passed to template pages for display. Templates in ACS Java are translated into JSP pages with embedded Java code, which can be directly served by the underlying servlet/JSP engine. This parallels the flow of the original ACS templating system, where templates are translated to Tcl pages served directly by the underlying AOLserver.

The mechanics of serving templated pages in Java is a little different than in Tcl. Because Java code cannot directly read or write variables in a caller's stack frame, as Tcl can with uplevel and upvar, it is necessary to explicitly pass objects to the template through a mutually-accessible structure. We construct our own call stack object passed around through request attributes, which is like a Hashtable attached to the ServletRequest object created by the servlet engine.

Likewise, the Java servlet specification has a more complex mechanism for passing control from one JSP page or servlet to another:

Tcl codeJava/JSP code
source $file
RequestDispatcher rd = request.getRequestDispatcher(file);
rd.include(request, response);

There are two different ways to pass control to another resource (servlet, JSP, static file): including and forwarding. Forwarding is also known as "chaining", and is generally used by resources that don't write output to the browser directly; they do some processing and forward to another resource for displaying output. Including is used to nest pages, so that servlets or JSP pages that write output to the browser can include a sub-page. It is not always legal to forward or chain to another resource; forwarding from within an include()'d resource will generally produce an error.

To help insulate developers from some of the complexities that come with Java, ACS includes a macro pre-processor (class com.arsdigita.db.MacroTranslator) for keeping JSP pages clean and simple. Instead of performing database operations and business logic within a JSP page, the author would write an .ajp (ACS Java Page), and this .ajp file is macro-expanded on demand into a regular JSP page. The macro pre-processor includes several macros for facilitating integration with the templating system.

A simple example

Let's consider a simple example page that displays some information and lists some users from the persons table. Using the pre-processor, first we make an .ajp page to set up datasources:
<%@ include file="/acs.jspi" %>

<%
ad_page_contract {
	A simple example page
	
	@cvs-id java-developers-guide.html,v 1.9 2001/01/21 01:54:06 bquinn Exp
	@author Bill Schneider [bschneid@arsdigita.com]
} -properties {
	x:onevalue
	people:multirow
}

Integer x = new Integer(25);

db_multirow people {
	select 
		first_names, last_name 
	from 
		persons
}

ad_return_template
%>
Next we write an .adp template in the same directory to display output to the user:
<html>
 <body>
  the value of x is @x@
 <br>
 The following users exist:
 <ul>
  <multiple name="people">
   <li>@people.first_names@ @people.last_name@
  </multiple>
 </ul>
 </body>
</html>

What's going on here?

The first line of the .ajp page statically includes some boilerplate code to set up appropriate properties for the JSP page: an error page to forward to on an uncaught Exception, importing various Java packages, etc.

The .ajp page consists solely of inline Java code, but there are three macros that will first be translated into Java code by the pre-processor:

Once control is passed to the template, the .adp page will be translated into JSP code on demand (the translated JSP is kept in the file system for future requests, and the template is only translated when it changes). The template is a JSP page that consists mostly of HTML with some mark-up to indicate the interpolation of dynamic values. The @x@ symbol interpolates the value of x, and the <multiple> tag iterates over the list of people for display.

Under the hood

Passing datasources to templates

The JSP code that the preprocessor generates for the ad_return_template macro exports all the datasources defined by ad_page_contract to the template. Variables are passed through a call stack object of type com.arsdigita.acs.CallStack; this call stack object is placed in the "com.arsdigita.acs.CallStack" request attribute, where it can be accessed by other JSP pages handling the request.

New levels are created on the call stack each time a template passes control to another template with a <master> tag or an <include> tag. Using a stack insulates the datasources in each level of templating from each other.

The type of each datasource placed in the stack frame depends on what kind of datasource it is. Any "onevalue" or "onelist" property is passed as whatever object type the corresponding local variable in the JSP page is. Both "onerow" and "multirow" properties are also directly placed in the stack frame; they are later wrapped in some class that implements the DataSource interface in the translated template code. A "onerow" datasource is wrapped in a SingleSelection object, and a "multirow" property is wrapped either as a Selection or a SeekableSelection depending on its usage pattern, determined at translation time.

So the generated code for the above ad_return_template macro would look like this:

CallStack __stack = (CallStack) request.getAttribute("com.arsdigita.acs.CallStack");
if (__stack == null) { 
	__stack = new CallStack();
	request.setAttribute("com.arsdigita.acs.CallStack", __stack);
} 
__stack.put("x", x);
// people is an instanceof ListResultSet, will be wrapped as a 
// Selection in the translated template 
__stack.put("people", people);
// TemplateTranslator.getTemplate() is a static method that returns the 
// URL of the template to serve for the current JSP page
RequestDispatcher rd = 
	request.getRequestDispatcher(TemplateTranslator.getTemplate(pageContext));
rd.forward(request, response);

The life cycle of a request

When an abstract URL first comes into the system, it is processed by the RequestProcessor servlet that handles all incoming requests. From there, the request goes through a series of handlers to handle the translation and serving of the requested object. A request for the URL /foo/bar, which is a request for the templated page bar.ajp/bar.adp in a nounted instance of package foo, is handled by the following steps:
  1. RequestProcessor handles incoming request for /foo/bar

  2. ACSRequest.getConcreteUrl() resolves /foo/bar to either an object in a mounted site node (/packages/foo/www/bar.*, or to a page in the global pageroot (/www/foo/bar.*); .ajp pages get highest precedence if they exist.

  3. ACSRequest.getConcreteUrl() runs any TranslatorHandler registered for the resolved file (for .ajp, runs the MacroTranslatorHandler, which does an up-to-date check and translates if necessary) and returns the URL for the generated file to serve (/packages/foo/www/bar.generated.jsp)

  4. The RequestProcessor runs ExtensionHandlers on the URL returned by ACSRequest.getConcreteUrl(), which forward the request to the appropriate handler for file type. JSP files (*.jsp, *.generated.jsp) are handled by the underlying JSP engine.

  5. bar.generated.jsp runs, and when ready to display, forwards to /packages/foo/www/bar.adp

  6. RequestProcessor is invoked again on concrete URL, because there is no handler registered in the underlying servlet engine for the *.adp extension

  7. ACSRequest.getConcreteUrl() runs the registered TranslatorHandler (TemplateTranslatorHandler) for the *.adp extension, which does an up-to-date check and translates bar.adp to /templates/packages/foo/www/bar__template_.jsp if necessary; returns the name of the translated file.

  8. The RequestProcessor calls the ExtensionHandler registered for *.jsp again, on the translated template file; the request is forwarded to the JSP handler in the underlying JSP engine on /templates/packages/foo/www/bar__template.jsp.

  9. The template JSP is served.

figure 1: control flow diagram for a requested page

The above process repeats recursively for included templates; for master templates, the slave template forwards the request to the master template, which then re-includes the slave template.

Important differences between templating system in Java and Tcl

The template translator in Java is feature-complete and supports all of the tags (except the form builder) supported in Tcl. There are a few important differences to keep in mind when writing the controlling .ajp page, though:
bschneid@arsdigita.com