Guide to Porting from ACS Tcl to ACS Java

by Bryan Quinn

Overview

ACS Java 4.0 provides analogs to functionality present in ACS Core 4.0. There is a database API, a templating system, the ad_page_contract validation tool, and identical data models. Once you learn the analogs of ACS functionality in ACS Java, you should be able to port a package with ease.

General Notes on Porting

Before starting the porting process, take some time to learn about what you are doing. Porting a package can quickly turn into a mechanical process of drudgery. However, this does not have to happen. Porting a package from ACS Tcl to ACS Java offers an opportunity to improve the clarity and efficiency of the code. Clarity can increase through careful use of Java classes for separating the functionality of your program into well-defined objects and libraries. Efficiency can increase through the use of the extensive Java APIs and the more efficient byte-code compilation process that Java uses. Porting a package is also an opportunity to experiment with Java. Take the exercise of porting an application as a chance to learn or refresh your knowledge of Java and its APIs, find problems in the ACS Java implementation, and build your engineering skills.

Pre-requisites

Before beginning, I recommend you read one book, Refactoring by Martin Fowler. It explains the engineering practice of writing, testing, and refactoring Java code. These are important skills for working with ACS Java.

Porting Process

  1. First, make sure that you are the only one porting the package. Check in with a coordinator to be sure about this. Start by doing a checkout of the code.
          cvs -d cvs.arsdigita.com:/usr/local/cvsroot checkout acs-packages/package-name
          
    Apply a tag to mark the state of the code when you start. This ensures that you are working against a stable target. If someone continues to program the version you are porting, this won't affect your work. In your local checkout
          cd acs-packages/package-name
          cvs tag port-begin-DATE
          
    It is important to mark the date for clarity. Periodically, you should check to make sure that the code is not being changed. You can do this by
  2. To start the porting project, create a new project in the acs-java-packages directory in the CVS repository. You should begin by committing all of the data models, static files, and documentation for the package as these won't need to be ported.
  3. Before starting to write any code, take the time necessary to understand the package you are porting. Budget a day or two depending on the size of the package to sit down, use the package, and read its code.

    This activity is much like a code review. Your task is to understand what the package does, all of its requirements, and the design of the system that holds it together.

    When performing this evaluation, be critical and inquisitive. Ask yourself if the package is doing everything it should efficiently. It is quite possible that it is not. This is how the process of porting is like review. You're likely to spot problems. Make a note of anything strange, and email the maintainer if appropriate. Even if you are the maintainer of the Tcl version, its worth rereading the code to look for problems.

    Porting a package is a good opportunity to refactor the code. To refactor code is to apply a set of changes to the internal structure of the software to make it easier to understand and cheaper to modify without changing its observable behavior. Notice that this is very similar to the process of porting; both tasks involve altering the code without changing its observable behavior. When moving code from Tcl to Java, you need to understand what that behavior is and find the most efficient and maintainable means of expressing it in Java.

  4. Ideally, you should start by writing unit tests and functional tests for any code that you are writing. Unit tests are tests of individual units of code, e.g. object methods. They should encode the known correct results based on the parameters to the method. This allows the test to be run automatically as part of a daily build test. Functional tests test the overall functionality of a piece of software. An example of a unit test is the DatabaseApiTest that tests each method of the API. An example of a functional test is the TemplatingTest which ensures that the Templating functionality is running correctly.
  5. Now that you're familiar with the package and you've got CVS setup, you're ready to start the porting. Now that you understand the behavior of your package, consider writing functional tests for the Tcl UI. You can reuse these tests for testing the Java port after you've finished the porting.
  6. Start by porting the Tcl library code. Identify a set of Java class abstractions that can contain the code you are porting. The equivalent of Tcl library functions in Java are static methods. Static methods are stateless functional calls attached to an object. Because there is no state involved, you don't need to instantiate a class to start working with the method. For example, java.lang.Math.pow(double a, double b).

    In some cases, you will want to create classes that represent state using member variables. Objects are nothing more than state with associated methods that provide functionality that require that state. Using objects provides a better means of organizing the variables and methods that provide functionality than relying solely on static methods.

    For example, the ACSSession class encapsulates all of the session property information into one object. The session object is used as a means of organizing the functionality and the data structures associated with the object. This organization is more effective than a series of static methods that must maintain their own individual state and variables separate from each other. Several of the methods mutate the internal state as necessary, and others are static methods that don't require the state as appropriate.

  7. Porting .tcl pages is more straightforward and is covered fully in the example below. The purpose of the .tcl pages is to access the database, perform any necessary application logic, and then send a set of variables to the templating system for presentation. Through the use of the ACS Precompiler, some language features, such as the Tcl database API and ad_page_contract can be used with no or only small modifications.
  8. As you port pages and methods, you should be writing and running tests. The tests provide some means of knowing when the porting is complete. If your tests completely cover the class's methods and functionality, then when all of the tests pass, the porting is done. You should click through the ported pages and compare them visually with the Tcl pages to see if the port has been successful.
  9. If you have a fair coverage of tests, all of your code is committed in CVS, and you are confident then the application is ported, your package is ready for code review. The code review may indicate minor or major changes to the system. If the changes are minor, make the changes, and ensure all of the tests still pass. If the changes are major, make the changes, and then contact the reviewer for more information.
  10. Following the incorporation of all changes, make sure that your code is committed. Your package is ready to be submitted to the ACS Repository.

Specific Example: ACS 4.0 News

This example overviews what went into porting the News Application for ACS 4.0. Please refer to the code in the CVS repository for more information.
Tcl version Java version
index.tcl

ad_page_contract {

    Displays a list of available news items.

    @param archive_p show archived news items?

    @author Jon Salz (jsalz@mit.edu)
    @author Christian Brechbuehler (christian@arsdigita.com)
    @creation-date 11 Aug 2000
    @cvs-id porting-guide.html,v 1.3 2001/02/26 20:41:05 bquinn Exp
} {
} -properties {
  context_bar:onevalue
  package_id:onevalue
  subsite:multirow
  item:multirow
  admin_p:onevalue
}

set context_bar     [ad_context_bar]
set package_id      [ad_conn package_id]
set admin_p [ad_permission_p $package_id admin]

db_multirow item news_items_select {
    select news_item_id, title
    from news_items_obj
    where context_id = :package_id
    and sysdate >= release_date
    and (expiration_date is null or expiration_date > sysdate)
}

ad_return_template

	
index.ajp

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

<%
ad_page_contract {
    Displays a list of available news items.

    @param archive_p show archived news items?

    @author Jon Salz (jsalz@mit.edu)
    @author Christian Brechbuehler (christian@arsdigita.com)
    @creation-date 11 Aug 2000
    @cvs-id porting-guide.html,v 1.3 2001/02/26 20:41:05 bquinn Exp

} -properties {
  context_bar:onevalue
  package_id:onevalue
  item:multirow
  admin_p:onevalue
}

String admin_p = 0;
ContextBar context_bar = new ContextBar();
BigDecimal package_id = acs.pkg.getPackageId();

if (Permissions.hasPermission(package_id, acs.session.getUserId(), "admin")) {
    admin_p = "1";
} 

db_multirow item {
    select news_item_id, title
    from news_items_obj
    where context_id = :package_id
    and sysdate >= release_date
    and (expiration_date is null or expiration_date > sysdate)
}

ad_return_template
%>
	
Notice that only three lines were deleted from the Tcl version. These were replaced with 6 lines in the Java version that represent a 1:1 translation of the application logic. The databases API, invocation of the templating system, and ad_page_contract lines were not changed at all. One line at the top of the page that handles the inclusion of all ACS related files is necessary. That is the <@ include file="/acs.jspi"%> line.

The template used by these files does not need to be changed.

Tcl version Java version
item-view.tcl

ad_page_contract {
  View a news item.

  @author Jon Salz (jsalz@arsdigita.com)
  @author Christian Brechbuehler (christian@arsdigita.com)
  @creation-date 11 Aug 2000
  @cvs-id porting-guide.html,v 1.3 2001/02/26 20:41:05 bquinn Exp
} {
  news_item_id:integer,notnull
} -properties {
  body:onevalue
  release_date:onevalue
  title:onevalue
  context_bar:onevalue
}

db_1row news_item_select {
  select * from news_items where news_item_id = :news_item_id
}

set context_bar [ad_context_bar $title]

ad_return_template

	
item-view.ajp
<%@ include file="/acs.jspi" %>

<%
ad_page_contract {
  View a news item.

  @author Jon Salz (jsalz@arsdigita.com)
  @author Christian Brechbuehler (christian@arsdigita.com)
  @creation-date 11 Aug 2000
  @cvs-id porting-guide.html,v 1.3 2001/02/26 20:41:05 bquinn Exp
} {
  news_item_id:integer,notnull
} -properties {
  body:onevalue
  release_date:onevalue
  title:onevalue
  context_bar:onevalue
}

db_1row news_item_select {
  select title, body, release_date from news_items
  where news_item_id = :news_item_id
}

ContextBar context_bar = new ContextBar();
context_bar.add(title.toString());

release_date = Utilities.toPrettyDate(release_date.toString());

ad_return_template
%>

	
The changes are minimal. The context bar must be manually created and the date must be converted into a printable string using a utility.
Tcl version Java version
admin/index.tcl
ad_page_contract {

    News main administration page.

    @author Jon Salz (jsalz@arsdigita.com)
    @creation-date 11 Aug 2000
    @cvs-id porting-guide.html,v 1.3 2001/02/26 20:41:05 bquinn Exp

} {
} -properties {
  context_bar:onevalue
  item:multirow
}

set context_bar     [ad_context_bar]
set package_id [ad_conn package_id]

db_multirow item news_items_select {
    select news_item_id, title
    from news_items_obj
    where context_id = :package_id
} 

ad_return_template

	
admin/index.ajp
<%@ include file="/acs.jspi" %>

<%
ad_page_contract {
    News main administration page.

    @author Jon Salz (jsalz@arsdigita.com)
    @creation-date 11 Aug 2000
    @cvs-id porting-guide.html,v 1.3 2001/02/26 20:41:05 bquinn Exp
} -properties {
  context_bar:onevalue
  item:multirow
}

ContextBar context_bar = new ContextBar();
BigDecimal package_id = acs.pkg.getPackageId();

db_multirow item {
    select news_item_id, title
    from news_items_obj
    where context_id = :package_id
} 

ad_return_template
%>

There is little to say about this one. The porting is transparent and simple.
Tcl version Java version
admin/item-new.tcl
ad_page_contract {

    Form to add an item.

    @author Jon Salz (jsalz@arsdigita.com)
    @author Christian Brechbuehler (brech@arsdigita.com)
    @creation-date 11 Aug 2000
    @cvs-id porting-guide.html,v 1.3 2001/02/26 20:41:05 bquinn Exp

} -properties {
    context_bar:onevalue
    news_item_id:onevalue
} -query {
}

set context_bar	 [ad_context_bar "Add an Item"]
set news_item_id [db_nextval acs_object_id_seq]

ad_return_template
admin/item-new.ajp

<%@ include file="/acs.jspi" %>
<%
ad_page_contract {
    Form to add an item.

    @author Jon Salz (jsalz@arsdigita.com)
    @author Christian Brechbuehler (brech@arsdigita.com)
    @creation-date 11 Aug 2000
    @cvs-id porting-guide.html,v 1.3 2001/02/26 20:41:05 bquinn Exp
} -properties {

    release_date_widget:onevalue
    expiration_date_widget:onevalue
    context_bar:onevalue
    news_item_id:onevalue
}

ContextBar context_bar = new ContextBar();
context_bar.add("Add an Item");
BigDecimal news_item_id = [db_nextval acs_object_id_seq];
String release_date_widget = com.arsdigita.acs.html.HtmlWidgets.dateWidget("release_date");
String expiration_date_widget = com.arsdigita.acs.html.HtmlWidgets.dateWidget("expiration_date");

ad_return_template
%>


       
The ported version of this file has some extra variables in the page contract. If you look in the ACS 4.0 Tcl version of this ADP file, you will notice that there are lines that read:

      <tr>
	<th align=right>Release Date:</th>
	<td><%=[ad_dateentrywidget release_date]%></td>
      </tr>
      <tr>
	<th align=right>Expiration Date:</th>
	<td><%=[ad_dateentrywidget expiration_date ""]%></td>
      </tr>
There is Tcl code included in the template. Naturally, this cannot be ported, and the ACS Tcl Templating guide reccomends against doing this. The version for ACS Java thus constitutes a refactoring of the code. Tcl code that was in the presentation layer got moved into Java. Here is the ACS Java version of the relevant portion of the template:
      <tr>
	<th align=right>Release Date:</th>
	<td>@release_date_widget@</td>
      </tr>
      <tr>
	<th align=right>Expiration Date:</th>
	<td>@expiration_date_widget@</td>
      </tr>

Tcl version Java version
admin/item-new-2.tcl
	ad_page_contract {

    Post a new news item.

    @author Jon Salz (jsalz@arsdigita.com)
    @creation-date 11 Aug 2000
    @cvs-id porting-guide.html,v 1.3 2001/02/26 20:41:05 bquinn Exp

} {
    news_item_id:integer,notnull
    title:notnull
    body:notnull
    release_date:date,array,notnull
    expiration_date:date,array
}

set release_date_str $release_date(date)
if { [info exists expiration_date(date)] } {
    set expiration_date_str $expiration_date(date)
} else {
    set expiration_date_str ""
}

set package_id [ad_conn package_id]

db_dml item_insert {
    declare
        news_item_id integer;
    begin
        news_item_id := acs_object.new(object_id => :news_item_id,
                                       object_type => 'news_item',
                                       context_id => :package_id);
        insert into news_items(news_item_id, title, body, release_date, expiration_date)
            values(:news_item_id, :title, :body, :release_date_str, :expiration_date_str);
    end;
}

ad_returnredirect "index"

admin/item-new-2.ajp
<%@ include file="/acs.jspi" %>
<%

ad_page_contract {
    Post a new news item.

    @author Jon Salz (jsalz@arsdigita.com)
    @creation-date 11 Aug 2000
    @cvs-id porting-guide.html,v 1.3 2001/02/26 20:41:05 bquinn Exp
} {
  news_item_id:integer,notnull
  title:notnull
  body:notnull,html
  release_date:date,notnull
  expiration_date:date
}

BigDecimal package_id = acs.pkg.getPackageId();

db_dml item_insert {
    declare
        news_item_id integer;
    begin
        news_item_id := acs_object.new(object_id => :news_item_id,
                                       object_type => 'news_item',
                                       context_id => :package_id);
        insert into news_items(news_item_id, title, body, release_date, expiration_date)
            values(:news_item_id, :title, :body, :release_date, :expiration_date);
    end;
}

Utilities.sendRedirect(response, "index");

%>
The only difference above is that less processing is needed in the Java version to handle the incoming date variables.

There are a few more files in the news package, but the porting of them is obvious given these examples. These examples illustrate how easy it can be to port packages to ACS Java that are primarily pages. Porting packages that exist more of libraries of code require more thought out refactorings beyond the scope of this document to provide.

It is interesting to note that most of the pages associated with the News application exist simply as wrappers to accept input from the user and use it as variables in a block of SQL or DML. The task of porting the application requires enduring a large amount of tedious and error prone work. When you find yourself working through a lot of tedious work, ask yourself the question, is there a better way? There probably is, and if you can find it, you can accomplish some solid engineering. This is the essence of good refactoring and software design. Be on the lookout for such opportunities throughout the task of porting the applications.

Revision History

Document Revision # Action Taken, Notes When? By Whom?
0.1 Creation January 16, 2001 Bryan Quinn

acs-docs@arsdigita.com
Last modified: porting-guide.html,v 1.3 2001/02/26 20:41:05 bquinn Exp