We want to use the same templates from ACS 4.0 in ACS Java 4.0. For those templates that do not include Tcl code, we can do this by translating ACS templates into JSP code, following similar steps as the ATS:
We also developed a series of custom JSP tags (ad:db_query, ad:db_1row, ad:db_0or1row) for setting up DataSource objects from database queries, and putting the created DataSource object in the request attributes.
We use the same DataSource objects and JSP tags in ACS Java 4.0 as we did in 3.4, although we will break the DataSource interface up into two interfaces: DataSource, which implements the basic methods to operate on a single set of name/value properties; and MultiDataSource, which extends DataSource and adds methods for iterating over multiple rows and seeking to different rows.
We will, however, need to revise the Selection class that implements
MultiDataSource. The JDBC interface has no method for determining how
many rows were returned from a query in advance, which is necessary
when either the ":rowcount" field is requested from a template, or
when the "grid" tag is used. We alos need the capability to seek to
any row in the result rows for the grid tag, and so the
same datasource can be used by more than one multiple tag.
So, rather than just wrapping a ResultSet, as it did previously, Selection will pull in all of the rows from the database. (Note: If we were guaranteed a JDBC 2.0 ResultSet that allowed both forwards and backwards scrolling cursors, we could use these methods to figure out the number of rows returned instead.)
Each main .jsp page would pass control to the template with a JSP custom tag, ad:use_template. This would look at the the request URL, and forward the request to the corresponding template file. If the request was for the URL /path/to/page.jsp, the ad:use_template tag would forward to the URL /templates/path/to/page.tpl. The .tpl templates did not require any translation, although, for convenience, the ad:template_contract tag was provided for creating local variables from passed-in request attributes.
In ACS Java 4.0, we propose to translate ATS templates into the JSP code that can then be directly served by the underlying JSP engine. The following mechanism translates standard ATS templates into JSP code:
RequestDispatcher
interface in the Servlet spec).
We use Java code within JSP files for performing business
logic and database access. We could also use Java servlets for the
same purpose; however, it is easier to dynamically change the site
layout using JSP, because all you have to do to publish a new JSP page
is put the JSP file where you want it located. The JSP engine will
handle re-compiling and re-loading JSP code without a server restart;
publishing a servlet requires editing the web.xml
deployment descriptor to register a servlet on a URL, and restarting
the server for the change to take effect.
| ATS template code | JSP code |
|---|---|
| @variable@ | <%= request.getAttribute("variable") %> |
| @datasource.variable@ |
<% DataSource datasource = request.getAttribute("datasource"); %>
...
<%= datasource.get("variable") %>
|
| ATS template code | JSP Code |
|---|---|
<multiple name="datasource"> ... body of multiple tag ... </multiple> |
<%
MultiDataSource datasource = (MultiDataSource)request.getAttribute("datasource");
boolean datasource_moreElements = datasource.next();
// datasource.next() might be called from within a group
// tag, so we need to keep track of whether or not this has
// happened yet
boolean datasource_alreadyIncremented = false;
do {
// ... body of multiple tag ...
if (!datasource_alreadyIncremented) {
datasource_moreElements = datasource.next();
}
} while (datasource_moreElements);
%>
|
(must be within multiple name="datasource" tag)
<group column="column"> ... body of group tag ... </group> |
<%
// always go through at least once
String this_column = datasource.get("column");
String last_column = this_column;
do {
// an group tag contained within multiple="datasource"
datasource_alreadyIncremented = false;
// ... body of group tag ...
if (!datasource_alreadyIncremented) {
datasource_moreElements = datasource.next();
datasource_alreadyIncremented = true;
}
last_column = this_column;
this_column = datasource.get("column");
} while (last_column.equals(this_column)
&& datasource_moreElements);
%>
|
| <list name="vec">
... body of list tag ... </list> |
<%
Enumeration vec = ((Vector)request.getAttribute("vec")).elements(); while (vec.hasMoreElements()) { Object vec_item = vec.nextElement(); ... body of list tag ... } %> |
|
|
| ATS template code | JSP code |
|---|---|
<property name="prop"> ... </property> |
<%
out = pageContext.pushBody();
// now any output will go to a string buffer rather than to the browser
// put the property in a scratch area to pass to master template later
__scratchMap.put("prop", (BodyContent)out).getString());
// restore the writer
out = pageContext.popBody();
%>
|
| <master src="filename"> and <slave> | continue translating rest of template. Translated template will forward the request to the (translated) master template where the <master> tag appeared; then the master template will include the slave template where <slave> appears. |
| <include src="subpage" foo="bar" baz="quux"> | <%
// put all attributes in a scratch map
__scratchMap.put("foo", "bar");
__scratchMap.put("baz", "quux");
// now create a new stack level and pass all the attributes
__stack.newLevel(__scratchMap);
RequestDispatcher rd = request.getRequestDispatcher("subpage");
rd.include(request, response);
// restore the stack
__stack.pop();
%>
|
| ATS template code | JSP code |
|---|---|
<if @var@ eq/ne value> or <if @ds.key@ eq/ne value> ... </if> |
<%
String var = request.getAttribute("var").toString();
(or:) String var = datasource.get("var");
if ([!]var.equals("value")) {
...
}
%>
|
<if @var@ lt value>(similar: le, gt, ge) ... </if> |
<%
Double var =
new Double(request.getAttribute("var").toString());
if (var.doubleValue() < value) {
...
}
%>
|
<if @var@ even>(similar: odd) ... </if> |
<%
Integer var =
new Integer(request.getAttribute("var").toString());
if (var.intValue() % 2 == 0) {
...
}
%>
|
<if @var@ [not] in val1 val2 val3 ...>(similar: odd) ... </if> |
Translation of <if @var@ eq val1 or @var@ eq val2 ... > |
<if @var@ [not] nil> ... </if> |
executes true branch if @var@ is a valid datasource passed in, but null; generates an error if @var@ is not a valid datasource |
<if @var@ [not] defined> ... </if> |
executes true branch if @var@ is not a valid passed-in datasource, or false branch otherwise |
An argument to <if> may be either a datasource or a
literal ("3.14159", "failure"). Since literal values
from a template file are Strings until converted to another type, and
the DataSource.get() method returns a String, it is easiest to first
convert single-value arguments to Strings first and then convert all
the arguments to whatever type is needed for the comparison
together. Although this may result in some superfluous
conversions, avoiding this conversion would require lots of run-time
type checking and would produce much more complex Java code inside the
translated template.
For eq/ne, we'll compare all arguments as Strings using the String.equals() method.
For odd and even, we'll coerce all arguments to
int using Integer.parseInt(); e.g.:
if (Integer.parseInt(datasource.get("val")) % 2 == 0) {
...
}
For inequalities, we'll have to first detect if a String argument contains a number, and then compare it numerically. We could further refine testing numbers to see if we have an integer vs. a floating point, but it makes for a simpler conditional expression to compare all numbers as floating-point. First, we declare a utility function isNumber():
public static boolean isNumber(String s) {Then, we would generate a floating point for numeric comparison with Double.parseDouble, if applicable; otherwise, we use the String.compareTo() method for lexical comparisons.
Perl5Util re = new Perl5Util();
// a number can start with a minus sign, and is a series of
// digits. It may contain a decimal point and a series of more digits.
// after that it might contain an optional exponent.
return (re.match("/^-?[0-9]+(\.[0-9]+)?([Ee][\+-][0-9]+)?$/", s));
}
For example:
<if @datasource.value1@ lt @datasource.value2@> ... body of if ... <if>
becomes
if (Utilities.isNumber(datasource.get("value1")) && Utilities.isNumber(datasource.get("value2"))
? (Double.parseDouble(datasource.get("value1")) < Double.parseDouble(datasource.get("value2")))
: (datasource.get("value1").compareTo(datasource.get("value2")) < 0)) {
... body of if ...
}