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 code | Java/JSP code |
|---|---|
source $file
| |
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.
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>
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:
ad_page_contract, which defines x and people
as datasources to export to the template; this macro is also used to define incoming
URL or form variables.
db_multirow, which performs a database query and puts
the results into a java.sql.ResultSet object named
people
ad_return_template, which forwards to the template,
exporting the datasources defined in the
ad_page_contract. The template is included instead of
forwarded to if the page that contains the
ad_return_template macro is a subpage that was included
from an outer-level page.
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.
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);
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:
RequestProcessor handles incoming request for /foo/bar
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.
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)
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.
RequestProcessor is invoked again on concrete URL,
because there is no handler registered in the underlying servlet
engine for the *.adp extension
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.
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.

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.
In Tcl, though, all output from a template is just bundled up in a
string; if no <master> tag is encountered, this string simply gets
ns_returned. When a <master> tag is detected, control
is passed to the master template which just interpolates this string
wherever <slave> appears, and the code for the slave template is
not re-invoked.
The difference in semantics of master/slave is most dramatic when a master template includes two <slave> templates, because in Tcl, the slave template runs first and its contents are interpolated twice; in Java, though, the slave template is actually run twice.
ad_return_template macro in the controlling .ajp
page to pass control to the template (or forwarding to the template
URL explicitly, if writing a .jsp directly) is not optional
in Java.
ad_return_template with a argument
specifying the name of the template is slightly different: a literal
filename is specified with double-quotes (ad_return_template
"filename") and a variable reference without
(ad_return_template filenameString).
-properties section
of ad_page_contract because the .ajp and template do not
run in the same Java stack frame.
RequestDispatcher.include() instead.
<if @var@ nil> tag will generate an error if
@var@ is not a valid passed-in datasource; that is,
@var@ must be a key in the Map used for the
stack frame. @var@ is nil if it is valid but either a
null object, or an empty string.
<if @var@ defined> tag will execute the true branch
if @var@ is a valid passed-in datasource, regardless of its value.
ad_page_contract macro is necessary in any .ajp
files <include>'d from another template, to declare passed-in
properties that are manipulated or used in computations within the
.ajp page. This is not necessary in Tcl, but in Java a compiler error
will result. It is not necessary to declare passed-in properties for
an included subtemplate if these properties are only interpolated in
the included .adp subtemplate, and not referenced in the included .ajp
page.
<multiple name="ds">
@ds.foo@
<multiple name="ds.bar">
@ds.bar.baz@
</multiple>
</multiple>
To get around this limitation, consider crafting your query so you
could use a <group> tag instead of nested <multiple> tags; or
consider making a custom subclass of type ListResultSet
that implements a toString method to display the proper
HTML string representation of the list of items. This is sub-optimal
because it may result in HTML within Java source code; but generating
HTML in an isolated "bean" class, where it can be made infinitely
customizable through get/set-property methods, is preferable to
generating it in the .ajp file.