ACS-Java "Best Practices"

what you need to know to write a good ACS-Java page


Overview

Crash Course in Java/JSP

Java Server Pages (JSPs) are the Java analog to ADPs and ASPs; they're primarily HTML, with syntax for escaping into Java code and for interpolating the values of Java variables.

Some features that are unique to JSP:

All JSPs (whether part of ACS or not) declare several variables implicitly; the most important are request, response, and out. The out variable is used implicitly every time you output HTML in a JSP.

The request object has an attribute list associated with it, which can be used for passing objects between different JSP pages, or between JSP pages and templates. This is done by calling request.setAttribute("name", object) and request.getAttribute("name"). The request object also has methods for retrieving URL/form variables and getting properties about the connection.

For most connection-level operation you can do in AOLserver, there is a corresponding method in Java. Some examples:
Tcl/AOLserverJava/JSP
ns_returnredirect $return_url response.sendRedirect(return_url);
ReturnHeaders 200 image/jpeg response.setStatus(200);
response.setContentType("image/jpeg");
set ip [ns_conn peeraddr] String ip = request.getRemoteAddr();

Getting started

Flow of a typical ACS Java page

Most ACS-Java pages will follow the same general flow pattern:

Under the hood

All ACS Java pages should include the acs.jspi file to set up imports and the ACS tag library. All ACS Java pages should also start with the ad:page tag, which sets up two variables: db, a Database handle object for use in the page; and form, an AdSet corresponding to the passed-in URL/form variables.

Each ACS Java page is split into two files: a JSP that does business logic, database access, user input validation, etc.; and a corresponding TPL template for rendering HTML to the user's web browser. (TPL templates are just JSPs with a different extension.)

The skeleton of most ACS Java JSP pages will look like this:

<%@ include file="acs.jspi" %>

<ad:page>

<ad:page_contract id="pc" vars="..."/>

... do queries, DML, etc. here ...

<ad:use_template/>

</ad:page>

Processing form and URL variables

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 scripting 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 %>

Interacting with the database

The DataSource interface and Selection classes

We define a DataSource interface for representing lists of objects to be rendered into HTML and displayed to the user via a template. DataSources all contain methods for list iteration and getting values for object properties.

The Selection class is an implementation of the DataSource interface to generically represent lists of database rows returned from queries. It is effectively a wrapper for the java.sql.ResultSet interface, which hides the distinction between VARCHAR and CLOB objects from a JSP page author; also, it ensures that raw SQL dates are displayed in standard ANSI format.

The Selection class can be extended with methods specific to a particular kind of database object, when appropriate. For instance, it is useful to have a subclass of Selection that represents a bboard topic (com.arsdigita.acs.bboard.TopicsDataSourcePerforming database queries 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 attributes for looping and interpolation with the ad:sub/ad:loop tags.

All query tags allow you to specify the type 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, though you can bind out-of-order by using named bind variables.

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.  
--%>

<%= date.get("sysdate") %>
<ul>
<ad:loop ds="topics">
 <li><ad:sub var="topic"/>
</ad:loop
</ul>

Doing DML

The db_query tag can be used without the obj and type attributes just to create a SqlStatement object; this object can be passed as an argument to the Database.dml() method for performing inserts/updates/etc. Example:
Java Tcl equivalent
<ad:db_query id="update_password">
 update users 
 set password = ?
 where user_id = ?
<% 
update_password.bind(new_pass);
update_password.bind(user_id);
%>
</ad:db_query>

<% db.dml(update_password); %>

db_dml update_password { 
 update users
  set password = :password 
  set user_id = :user_id 
}

There is an analogous plsql method as well, for calling stored functions and procedures. Note that you should not explicitly bind the return object for stored functions:
Java Tcl equivalent
<ad:db_query id="plsql_example">
begin
 ? := to_char(sysdate, ?);
end;
<% plsql_example.bind("Month dd, yyyy"); %>
</ad:db_query>

<% Object obj = db.plsql(plsql_example); %>

set date_fmt "Month dd, yyyy"
set obj [db_exec_plsql {
   begin 
    :1 := to_char(sysdate, :date_fmt);
   end;
}]

Handling LOBs

LOBs with Oracle require special handling. On queries, the Selection object and its subclasses handle CLOBs transparently; Selection.get("column_name") will return the contents of column_name as a String whether it is a CLOB or varchar.

BLOB content may be retrieved with the getBLOB() method in Selection; or the dumpBLOB method may be used to write the BLOB content directly to an output stream.

To perform DML operations with BLOB or CLOB objects, use the blobDmlFile, clobDmlFile, and clobDml methods in the Database class. All these methods are analogous to Database.dml(); they take a SqlStatement, with an additional parameter:

SQL statements used with the LOB DML methods in Database should use the returning into clause to set up the CLOB objects as the last bind variable in the statement. Also, because of the way the JDBC driver works, you have to wrap the INSERT or UPDATE statement in begin ... end;.

Example:
Java Tcl equivalent
<ad:db_query id="insert_portrait">
begin
 update users 
  set portrait_upload_date = sysdate,
      portrait = empty_clob()
  where user_id = ?
 returning portrait into ?;
end;
<% insert_portrait.bind(user_id); %>
</ad:db_query>

<% 
db.blobDmlFile(insert_portrait, new File(portrait_tmpfile));
%>

db_dml insert_portrait {
 update users 
  set portrait_upload_date = sysdate
      portrait = empty_clob()
  where user_id = :user_id
  returning portrait into :1
} -blobs [list $portrait_tmpfile]

Transactions

The database API supports transactions through the beginTransaction, abortTransaction, and endTransaction methods. Nested transactions are supported.

Example:
Java Tcl equivalent
<% 
db.beginTransaction();
db.dml("insert into greeble values('bork')");
db.dml("insert into greeble values('quaggle')");
if (!user.isSiteWideAdmin()) { 
  db.abortTransaction();
} else {
  db.endTransaction();
}
%>
db_transaction { 
 db_dml { 
  insert into greeble values('bork')
 } 
 db_dml {
  insert into greeble values('quaggle')
 } 
 if {![administrator_p $user_id]} {
  db_abort_transaction
} 

Templating

It is generally a good idea to separate content from presentation. We do this in ACS-Java by breaking up each page into two separate files: a JSP that handles business logic and sets up datasource objects with database queries, or performs some DML on the database; and a TPL template file that presents these datasources. The datasource objects are passed as request attributes.

We try to eliminate escaped Java code (that which comes inside <% ... %> blocks) as much as possible and implement our own custom tags for performing routine tasks such as looping over a multi-row datasource and interpolating columns from it.

A JSP passes control to a TPL template for display by using the <ad:use_template> custom JSP tag. In /path/to/foo.jsp, the use_template tag will pass control to the template named /templates/path/to/foo.tpl.

Note that TPL files are really just JSP files with a different extension, to prevent confusion and to bypassing repetitive request processing with the RequestProcessor servlet. The TPL extension is be directly mapped to the JSP servlet, which avoids the RequestProcessor.

The template_contract tag

The TPL template specifies the datasources and other objects to be passed in through the ad:template_contract tag, which is slightly different than the page_contract tag.

Usage of template_contract:

<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 variable available to escaped Java code in the template. There are two major differences, though:

The template_contract tag is syntactic sugar for declaring variables whose initial values come from request attributes. <ad:template_contract vars="var1:optional var2:class=Integer,multiple var3:datasource"/> is syntactic sugar for:
<%
 // var1 is optional -- will be null if not supplied
 Object var1 = request.getAttribute("var1"); 
 // var2, var3 not optional: will fail if not supplied
 Integer[] var2 = (Integer[])request.getAttribute("var2")
 if (var2 == null) 
  throw new PageValidationException("var2 not supplied");
 DataSource var3 = (DataSource)request.Attribute("var3");
 if (var3 == null) 
  throw new PageValidationException("var3 not supplied");
%>

Supported custom JSP tags

A full reference of available JSP tags may be found here. The following JSP tags are available for use in any template:

More gory details: How does ... work in ACS Java?

Logging

You should log using the Log class from the Turbine project (org.apache.turbine.util.Log). The package is imported in the acs.jspi file.
Tcl/AOLserverJava/JSP
ns_log notice "Sending email..." Log.note("Sending email...");
ns_log warning "Warning: ..." Log.warn("Warning: ...");
ns_log error "Oracle down, major system failure ..." Log.error("Oracle down, major system failure ...");

The request processor

ACS-Java includes a RequestProcessor servlet, which handles abstract URL resolution, and registered filters/procedures. All HTTP requests for URLs under ACS-Java go through it first, since it is deployed at the root URL pattern "/". (NOTE: The *.jsp URL pattern should also be mapped explicitly to RequestProcessor, to undo any previous mapping the the JSP servlet.)

The following steps take place within the RequestProcessor to serve a response to the end-user's browser:

Note that requests for abstract URLs go through the RequestProcessor twice: the first time, they are filtered, resolved, and forwarded to the concrete URL; the second time, they are just forwarded to the appropriate handler servlet. This is necessitated by the internals of the Servlet spec, specifically, because you can't call getNamedDispatcher with a URL parameter like you can with getRequestDispatcher.

Under the hood, a request for /path/to/page is handled like this:

Scheduled procedures

You can schedule procedures with the com.arsdigita.acs.Scheduler class and its static methods. Since you can't easily pass a naked method to execute as a parameter, you have to wrap your scheduled procedures in the ScheduledProcedure class, which implements java.lang.Runnable.

Procedures are scheduled by calling the schedule and scheduleOnce methods, in various forms. Example:
Java Tcl equivalent
ScheduledProcedure sp = new ScheduledProcedure () { 
  public static void run() { 
    // do something!
  }
};

Scheduler s = Scheduler.getInstance();
// run sp every hour
s.schedule(sp, 3600);
// run sp only once today at 3:45 p.m.
s.scheduleDailyOnce(sp, 15, 45);
// run sp Thursday at 10:30 a.m.
s.scheduleWeekly(sp, java.util.Calendar.THURSDAY, 10, 30);

proc sp {} {
  # do something!
} 

ns_schedule_proc 3600 sp 
ns_schedule_daily -once 15 45 sp
ns_schedule_weekly 4 10 30 sp

Initialization Parameters

Initialization parameters are stored in an XML file, and accessed through the static methods of the Parameters class. The Parameters class is a servlet loaded at start-up, and the parameters file is named from the servlet init parameters in web.xml.

In the parameters XML file, parameters are associated with modules and may also have types; also, parameters may have multiple values. For example:

<module name="bboard">
 <parameter name="ModuleName"> 
  <value>yourdomain Discussion Forums</value>
 </parameter>
 <parameter name="FileUploadingEnabledP" type="boolean"> 
  <value>true</value>
 </parameter>
</module>

<module name="foo">
 <parameter name="numberlist" type="int"> 
  <value>10</value>
  <value>20</value>
  <value>30</value>
 </parameter>
</module>

Initialization parameters are accessed through the static methods of the Parameters class:

Java Tcl equivalent
Parameters.getBoolean("FileUploadingEnabledP", "bboard") [ad_parameter FileUploadingEnabledP bboard 0]
Parameters.get("SystemName", "", "yourdomain Network") [ad_parameter SystemName "" "yourdomain Network"]
Parameters.getVector("numberlist", "foo") [ad_parameter_all_values_as_list numberlist foo]

Module initialization: registering filters/procs

Sometimes a Java class that is part of a module may need to run some code at server start-up, usually to register filters or to schedule procedures. This is done with static initializers:
Java Tcl equivalent
public class ModuleFilter extends FilterBase { 

  static { 
    // any code contained within the "static" block will be run the
    // first time this class is loaded
    RequestProcessor.registerFilter("/module", new ModuleFilter());
  } 

  public void filter (HttpServletRequest req, 
                      HttpServletResponse resp,
                      ServletContext ctx) { 
    // main filter action goes here
  }
}
proc module_filter {} { 
 # do main filter action here
} 

ad_register_filter /module module_filter
Each class with a static initializer needs to be referenced in the parameters file to ensure that the class is referenced on server start-up. This is done with the loadclass tag in the ACS parameters file, e.g.:

<loadclass>com.arsdigita.acs.fs.Download</loadclass>