ACS 3.4 Java Design Document

by Bill Schneider

Supported Functionality: What's there?

ACS/Java 3.4 is is built on the same data model and provides many of the same modules and functions.  Use of the same data model allows cross-compatibility with ACS 3.4/Tcl, so that functions not provided in ACS/Java (e.g., a missing admin page) may be performed by an ACS/Tcl install operating on the same database.

The following modules are supported in ACS/Java:

Location of Files and Deployment

Let $ACS_JAVA be the ACS/Java base directory; under Tomcat, this is probably $TOMCAT_HOME/webapps/acs-java. The ACS/Java tree consists of several different types of files: The distribution of ACS/Java is currently set up for deployment under Tomcat Release 3.1. Tomcat is available for free at http://jakarta.apache.org. The ACS/Java tree should be checked out into the webapps directory.

The web.xml indicates some servlet classes to be loaded at server start-up. The base URL of the acs-java webapp context is mapped to the RequestProcessor servlet, so that all HTTP requests are filtered and processed by the RequestProcessor. The *.jsp pattern is also explicitly mapped to the RequestProcessor, unmapping the default mapping. TPL template files, which are really just JSPs in disguise, are mapped directly to the JSP handler servlet.

The web.xml file contains a servlet initialization parameter for locating the acs-parameters.xml file. This file contains the application-level configuration information for your ACS web service and component modules.

Third-party libraries

We use the following third-party libraries in the ACS/Java code:

Important servlets and other low-level stuff

The Request Processor

All HTTP requests to a properly-configured ACS/Java server, with the acs-java context mapped to the root URL in $TOMCAT_HOME/conf/server.xml (or corresponding config file), go through the RequestProcessor servlet. The request processor handles:

Initialization Parameters

The Parameters servlet runs on startup and loads the acs-parameters.xml file (or whatever file is listed as its initialization parameter in web.xml). This is a structured XML file with sections for each module.

We elected to make a separate XML file for application-level configuration, rather than listing all init parameters as servlet paramters in web.xml because we can then better structure the parameters by module, and list multiple values for a single parameter. We could have used a Java properties file, but that format does not provide the desired level of data structuring. Also, XML is a standard input-validation mechanism with DTDs.

Parameter values are obtained through static methods of the Parameters class.

Scheduled Procedures

Procedures are scheduled with the Scheduler class. First, your procedure should extend the ScheduledProcedure class; then, call the appropriate scheduler method. Procedures can be scheduled daily, weekly, or at some other fixed interval.

Logging

We handle logging using the org.apache.turbine.util.Log class.  It supports three different methods corresponding to different error levels: note, warn, and error.

Sending e-mail to users

You can send e-mail to users with the classes in the org.apache.turbine.util.mail package, which are wrappers to the JavaMail API. There is also a sendMail() method in the com.arsdigita.acs.Utilities class, which mimics the AOLserver ns_sendmail procedure.

The Database API

The Java/ACS database API wraps the JDBC API, i.e., the classes and methods in the java.sql.* package, so that JSP pages should not have to call these methods directly. The database API also includes a series of JSP custom tags for performing queries in JSP pages and setting up datasources with the query results for use in templates.

The heart of the database API is the com.arsdigita.acs.db.Database class, which handles getting connections, performing queries and updates, and transaction management. Methods to wrap updates and queries on LOBs are also provided.

The DataSource interface and the Selection class, which implements the DataSource interface, are also part of the database API. These interfaces are wrappers to the ResultSet interface, and hide some of the ugliness of ResultSet from page authors. For example, raw dates (e.g., "SELECT sysdate FROM dual") are converted into Java strings with the ANSI date (ResultSet.getString("sysdate") would also print the time); and CLOB contents are transparently obtained and returned as strings.

You should also use Selection as the base class for any class that represents a row or set of rows from a specific database table, to provide methods specific to that data type. For example, com.arsdigita.acs.bboard.TopicsDataSource represents rows from the BBOARD_TOPICS table. The Selection class provides the basic functionality for initialization, iteration over rows, and getting column values, while the TopicsDataSource subclass provides methods specific to a bboard topic, like the authorization tests adminAuthorize and authorizeUser.

Since we're working in an OOP language with encapsulation, polymorphism, etc., why don't we put a stronger abstraction barrier between the database, with a Java class corresponding with each table in our data model, instead of using a generic datasource interface?

There are several reasons why. First, if you put a lot of thought into building your data model in your RDBMS, why should you have to double that effort by building it a second time in Java with a class for each table, an accessor method for each column in each table, etc.? 

Second, while the Java object system is great for structuring data, it does not take performance into account. In any database-backed web service, it is likely that some table columns will be accessed very frequently; some less frequently; and some very infrequently. In a table like BBOARD, there will be many pages that display the ONE_LINE summary column, and MSG_ID primary key, but only a few that display the MESSAGE body CLOB column. It doesn't make sense to request the message body unless it's requested; and special-casing a SELECT to omit the message body in a hypothetical Java Message class is effectively a tunnel under the abstraction barrier anyway. 

Third, if we had a rigid one-to-one mapping between database tables and Java classes, it wouldn't make sense how to handle JOINs, which are effectively new object types built on the fly. GROUP BY complicates things further, since that's something you should only do when needed. 

Fourth, SQL code within Java classes is ugly, because there are no multiline string literals.  In JSPs, we can build our own custom tags for defining SQL statements that take up multiple lines.

Fifth, and finally, being close to the database so that page authors have the full expressive power of SQL at their disposal is a great feature of ACS/Tcl; and we want to keep it that way in ACS/Java. 

Though it is good not to take all SQL out of the JSPs, it is still a good idea to move re-usable logic that calls the database (e.g., access tests, group and role membership tests) into Java classes to enhance readability and maintainability. Some good examples are the GeneralPermissions class, and the isGroupMember and isGroupAdmin methods in UserInfo class.

The JSP tag library and Templating

The ACS/Java toolkit includes a series of JSP custom tags for parsing user input from URL and form variables; defining SQL statements and loading datasources with results from queries; and calling templates.  All ACS/Java JSP tags start with an "ad:" prefix.

The generalized control flow of a page within ACS/Java is:

Getting URL/Form Variables with the ad:page_contract tag

Form and URL variables are processed with the ad:page_contract JSP tag. This tag performs some type checking and validation. The page_contract tag automagically declares local variables corresponding to each declared form/URL variable in the vars parameter. The page_contract tag also sets attributes in the pageContext object corresonding to each declared form/URL variable.

The syntax for the ad:page_contract tag is:

<ad:page_contract [id="pc"] vars="var_name(:flags)...">

Available flags for each variable include:

If the id attribute is set as in the above example (id="pc"), you may call pc.addComplaint("you must enter ...") within the page_contract tag body for indicating problems with user input. When the page_contract tag is closed, a message will be displayed to the user and the page will not continue executing.

Warning: Because of a quirk of the JSP spec (Section 5.5 of the JSP 1.1 Specification document), any variable that you set within the body of a page_contract tag will revert back to its corresponding attribute in the pageContext; so, if you want the new value to stick, you should call pageContext.setAttribute(varname, value). For example:

<ad:page>
<ad:page_contract vars="v:optional"/>
 <%
  if (v == null) {
   pageContext.setAttribute("v", "default_v_value");
  }
 %>
</ad:page_contract>

<%--
  the translation of the closing tag includes the statement
  v = pageContext.getAttribute("v");
  so the following line should print out "default_v_value".
--%>
<%= v %>

Getting the currently-logged-in user

The current user is indicated by an attribute in the current HttpSession object, which is maintained by the servlet engine and identifies users with a browser cookie.  The Security class handles the management of the session, and the creation/validation of persistent login cookies.

The <ad:get_user_info id="user" [redirect="true|false"] /> tag determines the currently-logged-in user and sets up a UserInfo variable, perhaps redirecting to the registration page if the user is not logged in.  If the user is not logged in and the redirect is false, then the variable is null.

Defining SQL statements and setting up datasources with the ad:query tag

Database queries are performed with the <ad:db_query>, <ad:db_1row>, and <ad:db_0or1row> tags depending on whether the query is for multiple rows, one row, or zero or one row respectively.

All of the database query tags instantiate objects of type Selection, or one of its subclasses, and make them immediately available both as local variables in the JSP page, and as request attributes for use in templates with the <ad:sub> and <ad:loop> tags.

All query tags allow you to specify the exact subtype of DataSource to be created; by default, db_query creates an object of type Selection, and db_1row/db_0or1row create an object of type SingleSelection.

Each database query tag also creates a variable of type SqlStatement that can be referenced for setting bind variable values. The bind() method binds values to variables in the order in which they occur within the SQL statement.  You can also bind variables by name, rather than positionally.

Example:

<ad:db_query id="get_bboard_topics" obj="topics" type="com.arsdigita.acs.bboard.TopicsDataSource">
 select * from bboard_topics
 where group_id = ?
<%
if (topic_stub != null) {
%>
  and topic like :topic_stub || '%'
<%
get_bboard_topics.bind("topic_stub", topic_stub);
}
get_bboard_topics.bind(group_id);
%>
</ad:db_query>
<ad:db_1row id="get_date" obj="date">
 select sysdate from dual
</ad:db_1row>
<%--
at this point date is declared and is type SingleSelection;
topics is declared and is type TopicsDataSource, which is a 
subclass of Selection with methods specific to operating on bboard
topics
--%>

<%= date.get("sysdate") %>
<ul>
<ad:loop ds="topics">
 <li><ad:sub var="topic"/>
</ad:loop>
</ul>
SqlStatement objects can also be passed to the Database.dml() and Database.plsql() methods for performing inserts/updates and calling stored procedures/functions.  Note that with the plsql() method, you can specify an output variable type, and the first bind variable in your query.  (You should not bind an input value to the output bind variable.)

Templating and the template_contract tag

A JSP page may pass control to a corresponding template with the <ad:use_template/> tag.  When invoked from a JSP page /path/to/page.jsp, the request will be forwarded to the corresponding TPL template, /templates/path/to/page.tpl.

The TPL template should indicate the attributes it uses via the template_contract tag.  Usage of this tag is:

<ad:template_contract vars="var1[:flag1,flag2,...] var2[:flags] ..."/>

The "vars" parameter to the template_contract tag is very similar to that for the page_contract tag; each declared variable becomes a scripting variable available for display in the template. There are two major differences, though:

Most of the body of the template should be regular HTML, with interpolated values from datasources or variables set up by template_contract.  Datasources can be iterated over with the <ad:loop> tag, and individual columns from a row are interpolated into the HTML output with the <ad:sub> tag.  A one-row datasource can also be interpolated with <ad:scope>, which also brings a datasource into scope for <ad:sub> without iteration.

Note: The ad:sub tag will try to introspect a corresponding method for the column name requested, before calling the generic get() method on the datasource.  So <ad:sub var="property_name"/> will first try to output the value of the datasource's getPropertyName() method, or the result of get("property_name") if no such method exists.

Example:

<ad:template_contract vars="users:datasource daterow:datasource navbar"/>

<h2>Test</h2>

<%-- directly interpolate a single variable with JSP syntax --%>
<%= navbar %>
<hr>

<%-- interpolate a one-row datasource using ad:scope --%>
<ad:scope sources="daterow">
Today's date is <ad:sub var="sysdate"/>.
</ad:scope>

<%-- now loop over a multi-row datasource --%>
<p>List of users:
<ul>
<ad:loop ds="users">
 <li><ad:sub var="first_names"/> <ad:sub var="last_name"/> (<ad:sub var="email"/>)
</ad:loop>
</ul>

Future Improvements

There are several areas that are open for future work.
bschneid@arsdigita.com