![]() ![]() ![]() ![]() |
![]() |
This chapter introduces the DiscRack application, and uses it as a comprehensive example to illustrate some key concepts of Enhydra application development.
Enhydra 3.0 includes the DiscRack application, which is installed to the <enhydra_root>
/examples/DiscRack
directory. Throughout this chapter, this top-level directory containing DiscRack will be referred to as <DiscRack_root>.To build and run DiscRack, you need to make the following modifications to the installed files:
- Edit
config.mk
in <DiscRack_root>and change the
ENHYDRA_DIR
variable to your Enhydra root directory.
- Edit the configuration file
discRack.conf.in
in <DiscRack_root>/discRack
, and make sure all the Database Manager configuration settings are correct, as described in "Configuring the Database Manager." Also refer to Appendix A, "Database Configuration."
- Build the application by entering the make command from the <DiscRack_root> directory.
- Edit the
start
script in <DiscRack_root>/discRack
,and
make sure theCLASSPATH
variable references the location of your JDBC library, as described in "Configuring the Database Manager." Also, make sure the path to the multiserver executable is correct.Important: DiscRack uses the database and corresponding application data layer described in Chapter 4, "Tutorial: Building Enhydra Applications." Before you can run the application, you must load the database schema, as described in "Loading the Schema." Alternatively, you can load the Microsoft Access database in <DiscRack_root
>/discRack/data/discRack.mdb
.To run DiscRack, enter the following commands:
cd <DiscRack_root>/discRack/output
./start
To access the application, enter the URL
http://Localhost:5555
in your browser location field. Your browser displays the following screen:
![]()
Play around with the application to get a sense for how it works. Click on the "Sign Up!" button to add yourself as user, then add some discs to your inventory. Try viewing your inventory and editing one of the discs.
Before discussing the workings of the DiscRack application, it is useful to understand how you go about developing an Enhydra application in general. You can adapt the traditional software development process to Enhydra application development to ensure that:
- The application does what it is supposed to do
- You complete the project in a timely and cost-effective manner
- The application is easy to maintain and upgrade
An in-depth discussion of software methodology is beyond the scope of this book, but it is instructive to understand the basic principles and how they apply to the simple DiscRack application, so that you can reap the benefits when developing a more complex real-world application.
The following process is loosely based on Lutris Technologies' Structured Delivery Process (SDP), a rigorous methodology that Lutris developed over the course of many projects. The simplified process described below may be suitable for small projects; for more information on methodology for large, team development projects, see the information on the SDP on the Lutris web site at http://www.lutris.com
.
A simplified Enhydra application development process consists of the following steps:
- Requirements definition: creating a problem statement of what the application is supposed to accomplish as specifically as possible. This statement essentially defines the high-level goals of the application.
- Functional specification: outlining how the application solve the problem stated in the requirements definition.
- Design and storyboard: designing the presentation, data, and business layers of the application, and then creating the storyboard.
- Development and testing: coding and testing the application.
- Deployment: packaging and installing the application in its operational environment.
This abbreviated methodology is presented here to illustrate the key aspects of the development process. Complex, real-world applications generally call for a more comprehensive process that includes project milestones, cost analysis, documentation, and so on.
DiscRack Requirements Definition
The Otter family needs a way to track their compact disc collections. Each family member has a CD collection, and they sometimes get mixed up: Otters forget who owns what. They decide that an Enhydra application would be the perfect way to help them manage their CDs. After some discussion, they arrive at a brief requirements statement:
DiscRack will enable each user to keep track of his or her individual CD inventory; and to add, modify, and delete CDs as needed. The application will keep track of all the pertinent information about each CD, including artist and title.
DiscRack Functional Specification
Briefly, DiscRack will meet its requirements as follows:
- Maintain a list of users and passwords; users must log in with a user name and password to access their CD inventory.
- Allow new users to sign up by entering their name, a user name, and a password.
- Once logged in, a user can see his CD inventory and:
- Add new CDs to the inventory
- Edit existing CD entries
- Delete an existing entry, with confirmation prompt
- The information that will be displayed for each CD includes artist, title, genre, and whether the user likes the CD.
Design and Storyboard
The bulk of this step consists of the engineering design for the application, including the design of database schema and corresponding data layer, business logic, and presentation logic. The user interface design can be largely encapsulated by a storyboard.
A storyboard is a visual way of describing a user's navigation paths through the application. It provides an outline of the application's user interface, and a framework from which the rest of the application design can proceed. A conceptual storyboard that is largely an application flow chart is sometimes referred to as a site map, in contrast to a mocked-up HTML storyboard. This book will refer to both as a storyboard.
The storyboard for DiscRack is shown in Figure 5.1.
![]()
Figure 5.1 DiscRack Storyboard
You can see from the storyboard that there are five HTML pages in the application. You can also see that the DiscCatalog page that shows the CD inventory is the central page in the application. The first page the user sees will always be the Login page; the last page will always be the Logout page.
DiscRack includes a working storyboard (or application "mockup") in the resources directory. It is a set of static HTML pages that illustrate how the application works. To see the storyboard, load this file in your browser:
<DiscRack_root>/discrack/resources/personMgmt/Login.htmlThis displays DiscRack login page.
Click on the "Login" button to "log in" and see the disc catalog; click on the "Sign Up" button to display the Signup page. Click around on the links, and you can see the rest of the storyboard. The flow of the HTML pages follows Figure 5.1. Of course, none of the back-end logic is activated--all the HTML is static. But the storyboard gives you a good feel for how the application works.
Development, Testing, and Deployment
The remaining steps are development, testing, and finally deployment. Rather than go through the exercise of describing these steps for DiscRack, the rest of this chapter describes the DiscRack application itself.
When you build an application from the top level, the make files create an
output
directory containing the configuration files and the start script. Also, there will be alib
directory with a JAR file that contains all the application's class files, along with any other files (for example, GIFs or style sheets).To deploy the application, you just need to copy these files to the server on which you want the application to run, and make the appropriate changes to the configuration files to reflect the new location. Of course, Enhydra must be installed on this server, and you need to have any ancillary libraries (such as your database's JDBC driver) available.
The DiscRack application consists of 23 classes in nine packages:
discRack package DiscRack The application object DiscRackException A simple base exception class presentation layer/package BasePO An abstract base class for all presentation objects DiscRackSessionData A container for session data ErrorHandler A class to handle exceptions not caught elsewhere in the application DiscRackPresentationException A presentation layer exception class presentation.personMgmt package A package that contains classes for managing presentation related to the PERSON table: Register and Login
presentation.discMgmt package A package that contains classes for managing presentation related to the DISC table: Edit and DiscCatalog Business layer/package DiskRackBusinessException A business layer exception class business.person package A package that contains two classes: Person, which represents a person PersonFactory, which has a single method that returns the Person object for a user name business.disk package A package that contains two classes: Disc, which represents a disc. DiscFactory, which has methods to return a Disc object for an ID or for the owner's name. Data layer Described in "Loading the Schema." The six HTML files are in the
resources
directory. These correspond to the five HTML pages shown in the storyboard, plus an error page that appears when an error occurs that is not handled by an exception.
The presentation layer includes all of the HTML, Java, and JavaScript that defines the user interface of the application.
Presentation Base Class
All of the Presentation objects in DiscRack are derived from a common base class, BasePO, which is an implementation of the Enhydra interface HttpPresentation. This interface has one method, run( ), which takes the HTTP request as a parameter.
A Presentation base class enables the application to group common functionality in one place. Notice that BasePO is an abstract class, so it cannot be instantiated itself, only sub-classed. Also, some of its methods are declared abstract, so subclasses must implement them.
BasePO has methods to handle some of DiscRack's key tasks:
- User log in and session maintenance
- Event handling, and calling the HTML generation methods in the subclass Presentation objects
It is important to realize that you are not required to use a base Presentation class. An alternative is to use the Enhydra Application object to perform common tasks.
The central method in BasePO is run( ), which makes method calls to perform session maintenance and event handling:
public void run(HttpPresentationComms comms) throws Exception {// Initialize new or get the existing session data
initSessionData(comms);
// Check if the user needs to be logged in for this request.
if(this.loggedInUserRequired()) {
checkForUserLogin();
}
// Handle the incoming event request
handleEvent(comms);
}
Every time a client browser requests a presentation object URL, the application calls this method. Its logic is very simple:
- Initialize or get the existing session data by calling initSessionData( ).
- If this PO requires a log in--as determined by loggedInUserRequired( ), an abstract method implemented by each PO--then call checkForUserLogin( ) to determine if the user has already logged in; if not, then redirect the browser to the login page.
- Call handleEvent to handle the current event and determine what HTML to generate.
Each of these methods is explained in the following sections.
The run method has a parameter, comms, that is an object containing information about the HTTP request. Its member properties include: application, exception, request, response, session and sessionData. These six properties provide all of the information for the request. For example, you can retrieve session data with getComms( ).sessionData.get( ) and query string parameters with getComms( ).request.getParameter( ).
Session Data and Log In
The basics of Enhydra session maintenance were introduced in "Maintaining Session State." In contrast to the way session information was handled in that previous example, DiscRack stores all its session information in a single object, DiscRackSessionData, and saves that object in the user's session.
DiscRackSessionData is a simple container class that has two properties: a Person object that represents the user and a string called userMessage for error messages such as "Please choose a valid disc to edit." DiscRackSessionData has member properties for these data, and methods to get and set them.
There are several advantages of keeping session data in one object:
- It centralizes control of session information; this is especially helpful when multiple presentation objects access the same session data.
- It is type-safe. Since Session.getSessionData( ) returns a generic Object, if you store session data separately, you have to cast each item to the appropriate type, which can lead to runtime errors that are hard to debug.
- It facilitates session data maintenance. If there is a large amount of session data, you can periodically clean up the unneeded data. For example, say you wanted to store an array of hundreds of discs in the user's session to speed access, but you didn't necessarily want leave it there until they log out. With a session data object, you could easily implement a method to clean up unneeded data in the session.
The first thing each PO does is to call initSessionData( ). The main portion of this method is shown here:
Object obj = getComms().sessionData.get(DiscRackSessionData.SESSION_KEY);if(null != obj) {
this.mySessionData = (DiscRackSessionData)obj;
} else {
this.mySessionData = new DiscRackSessionData();
getComms().sessionData.set(DiscRackSessionData.SESSION_KEY, this.mySessionData);
}
The first statement in this code snippet gets the session data object, using the session key "DiscRackSessionData." If the session data object exists, it gets type cast to DiscRackSessionData; otherwise, the code creates a new DiscRackSessionData object and saves it to the user's session with set( ).
The loggedInUserRequired( ) Method
BasePO has an abstract method called loggedInUserRequired( ) that returns a boolean value. Thus, every PO is required to implement this method, which indicates whether a user is required to be logged in to access the associated page. In BasePO.run( ), if this method returns true, then checkForUserLogin( ) is called.
The checkForUserLogin( ) Method
The checkForUserLogin( ) method determines if a user has a valid login. If not, then it redirects the browser to the Login page:
...Person user = getUser();
if (null == user) {
...
throw new ClientPageRedirectException(LOGIN_PAGE);
}
...
Several statements that write debug messages to a log channel have been removed from this code for clarity. The call to getUser( ) is really just a call to getSessionData( ).getUser( ), which retrieves the Person object saved in the current session. If the user has not logged in, or the session has timed out, then this method will return
null
, and the code will throw a ClientPageRedirectException with the URL to the Login page as the argument to the constructor.When a client browser is redirected by a ClientPageRedirectException, any parameters from a query string that were available to the original presentation object are lost. So if you want to pass an error message, you must put the information in the user's session or directly into the query string of the redirected URL.
Event Handling
In this context, an "event" refers to the task a user is performing. While you could create a separate PO for each task in an application, in many cases it makes sense to have a single PO handle multiple events. For example, the Edit PO responds to four events: showing the add page, showing the edit page, actually adding a disc to the database, and deleting a disc from the database. The Login PO handles three events: show page, login, and logout.
DiscRack keeps track of the event it is processing with the "event" parameter, which is sent in the query string of a request. For example, the URL
http://Localhost:8000/discMgmt/Edit.po?event=showAddPagespecifies the event "showAddPage."
DiscRack illustrates several techniques for setting the event:
- The "showAddPage" event is defined in the
DiscCatalog.html
page by the JavaScript onClick event handler of the "Add a New Disc" button. This calls the JavaScript function showAddPage(), which explicitly adds the event to the URL requested:document.location='Edit.po?event=showAddPage'
. This function is defined inpresentation/discMgmt/DiscCatalogScript.html
, not the DiscCatalog page, as explained in "Replacing JavaScript."
- The "add" event (to add a disc to the database) is defined in the
Edit.html
page by a hidden form field:<input type="hidden" name="event" value="add" id="EventValue">
. When the user clicks the Add button, "event=add" is added to the form submission request along with the other form data the user entered.
- The "exit" event is defined in the DiscCatalog.html page by the second form's ACTION attribute,
"../personMgmt/Exit.html"
. At compile time, this URL is replaced by'../personMgmt/Login.po?event=logout'
, as explained in "URL Mapping."Although DiscRack does not demonstrate it, you can also set the event when you throw a PageRedirectException. You use this exception to transfer control from one PO to another. To specify an event, add the string "?event=someEvent" to the URL string passed to the constructor of PageRedirectException.
Once the event is set, the handleEvent( ) method of BasePO performs the actual event handling:
String event = getComms().request.getParameter(EVENT);String returnHTML = null;
if (event == null || event.length() == 0) {
returnHTML = handleDefault();
} else {
returnHTML = getPageContentForEvent(event);
}
getComms().response.writeHTML(returnHTML);
This method gets the "event" parameter from the request query string and calls the appropriate event handler. If it does not find "event" in the request query string, it calls handleDefault( ) which is an abstract method, and so must be implemented by all BasePO subclasses. Otherwise, it calls getPageContentForEvent( ) which returns the string content for the specific event and PO. This method contains the following three lines:
Method method = this.getClass().getMethod(toMethodName(event), null);String thePage = (String)method.invoke(this, null);
return thePage;
This code uses reflection (defined in the java.lang.reflect package) to call the method in the PO corresponding to the current event. Reflection allows you to call a method whose name is defined at runtime.
The call to toMethodName( ) returns a string "handleXxx" where "xxx" is the current event (for example "handleShowAddPage" for "showAddPage"). The call to method.invoke( ) then calls this method.
Reflection allows BasePO to call methods in its subclasses without knowing in advance the names of the methods. This scheme works as long as the presentation object code follows the appropriate naming conventions: for every event "foo," there must be a method handleFoo( ) in the PO class that needs to handle that event.
HTML Pages
The HTML pages for DiscRack are in the <discRack_root>
/discRack/resources
directory. Keeping the HTML pages there rather than in the presentation directory cleanly separates the HTML files from the Java files. Although this is superfluous for small applications, it is a key advantage for large applications with a graphic design team and a programming team.The make files in the presentation layer control how the application uses the HTML files. There are a total of three make files in the presentation layer, one in the top level, and one in each sub-directory. To keep the HTML files in a directory separate from the presentation classes, the make files use the HTML_DIR directive that specifies the relative path to the directory containing the HTML files. For example, in
presentation/Makefile
, you'll see:
HTML_DIR = ../resourcesAnd in
presentation/discMgmt/Makefile
:
HTML_DIR = ../../resources/discMgmtThe make rules will also find any HTML files in the presentation directories (for example
discMgmt/DiscCatalogScript.html
).The
HTML_CLASSES
directive indicates the names of the class files that XMLC creates, as explained in "Adding a New Page to the Application."Notice there is a
presentation/media
directory that contains only a make file. This directory mirrors the final package structure for the JAR file. A line in the make file copies the GIF into the finished jar file:
JAR_INSTALL = \../../resources/media/*.gif
Maintaining the Storyboard
The storyboard is initially just a mockup of the application. But with a few simple steps, you can maintain the working storyboard throughout the entire development process. This capability becomes particularly important for large applications created by a team of programmers and graphic designers: each team can work on their part of the application separately from the other.
After the graphic designers complete their work, you can then replace the old, "mock up" user interface with the new improved interface, that may include improved graphics, JavaScript special effects, style sheets, and so on. An example of doing this is illustrated in "Replacing the User Interface."
In addition to keeping the HTML files separate from the Java code, as described in the previous section, there are three steps you have to follow to maintain the storyboard during development:
- Define rules to map URLs like
Login.html
toLogin.po
- Remove dummy data from the HTML files
- Replace JavaScript, if necessary
Each of these steps is described in detail in the following sections.
In the working storyboard, as in any static HTML pages, hyperlinks reference other HTML pages. That is, the URLs in hyperlinks end in
.html
. However, in the working application, links to dynamic pages reference presentation object URLs that end in.po
.So, you need to do something to convert the "normal" URLs in the storyboard to
.po
URLs. You do this by using the XMLC -urlmapping option to map URLs from one form to another. You use this option like this:
-urlmapping oldURL newURLTo use this option in the make process, you must create an XMLC options file, and then identify the file in the make file with the
XMLC_HTML_OPTS_FILE
directive. For example:
XMLC_HTML_OPTS_FILE = options.xmlc
The
presentation/discMgmt/options.xmlc
file contains the lines:
-urlmapping 'Edit.html' 'Edit.po'-urlmapping 'DiscCatalog.html' 'DiscCatalog.po'
-urlmapping '../personMgmt/Exit.html' '../personMgmt/Login.po?event=logout'
When XMLC compiles the files in this directory, it will replace occurrences of the first string (for example, "Edit.html") with the second string (for example, "Edit.po") in hyperlink URLs and FORM ACTION attributes.
HTML files often contain "dummy" data to make the storyboard pages look more representative of their actual runtime appearance. You need to remove this dummy data from the production application.
Look in
presentation/discMgmt/options.xmlc
again; in particular, look at the last line:
-delete-class discardMeThe -delete-class option tells XMLC to remove any tags (and their contents) whose
CLASS
attribute is "discardMe." For example, if you look inresources/discMgmt/DiscCatalog.html
, you see this HTML:
<tr class="discardMe"><td>Sonny and Cher</td>
<td>Greatest Hits</td>
<td>Boring Music</td>
<td>Not</td>
</tr>
It's not that we don't love Sonny and Cher: the
CLASS
attribute in the table row definition marks the row for deletion. UnlikeID
, the value of aCLASS
attribute does not have to be unique in the page. You can remove all of the dummy in the application with the same "discardMe" value.In addition to replacing URLs, you often need to replace JavaScript in the storyboard with JavaScript to be used in the "real" application. For example,
resources/DiscCatalog.html
contains the following script:
<SCRIPT id="DummyScript"><!--
function doDelete()
{
document.EditForm.action='DiscCatalog.html';
if(confirm('Are your sure you want to delete this disc?')) {
document.EditForm.submit();
}
{
document.location='Edit.html';
}
//-->
</SCRIPT>
These functions help to keep the storyboard working. At runtime, though, the application needs to use the "real" functions, which are defined in
presentation/DiscCatalogScript.html
, for example:
...function showAddPage()
{
document.location='Edit.po?event=showAddPage';
}
...
Because XMLC views JavaScript as a comment, the URL mapping option will not work on this URL inside the JavaScript function. So, you have to replace it at runtime with the following code in
DiscCatalog.java
:
DiscCatalogHTML page = new DiscCatalogHTML();HTMLScriptElement script = new DiscCatalogScriptHTML().getElementRealScript();
XMLCUtil.replaceNode(script, page.getElementDummyScript());
This is an example of replacing a node with a node from another document. This implementation uses the XMLCUtil class.
Because this action happens at runtime, it may have a slight effect on performance. If performance is critical, you may wish to replace the JavaScript in the final deployed version of the application.
Maintaining the storyboard seems like additional unnecessary work, but it is worth the work when your HTML is evolving in parallel with the Java code. As an example of the power of a working storyboard, you can exchange the HTML in Disk Rack from the basic HTML to designed HTML.
Once the graphic design is completed, you can replace the user interface of the application with its final version. DiscRack includes a
resources_finished
directory containing "finished" versions of the HTML pages, along with a graphic and a stylesheet.To replace the original storyboard resources with the "finished" resources:
- Rename the
resources
directory toresources_old
- Rename the
resources_finished
directory toresources
- Edit <DiscRack_root>
/discRack/presentation/media/Makefile
and in the JAR_INSTALL directive, remove the two comment symbols (#
), and add a continuation character (\) after the first line; so that it looks like this:
JAR_INSTALL = \../../resources/media/*.gif \
../../resources/media/*.css \
../../resources/media/*.jpg
This ensures that the new JPEG graphics files and the style sheet file are included in the packaged application JAR file.
- Rebuild the presentation package, by entering the following commands from the directory <DiscRack_root>
/discRack/presentation
:
make cleanmake
The make clean command will remove all the old classes, so that make will completely rebuild the application from scratch.
Now, restart and access the application. You see the new and improved user interface:
![]()
Populating a List Box
The DiscCatalog page illustrates how to populate a
SELECT
list box, which is a common task. First, look at the HTML for theSELECT
tag inDiscCatalog.htm
l:
<SELECT id="TitleList" Name="discID"><OPTION selected VALUE="invalidID">Select One</OPTION>
<OPTION id="templateOption">Van Halen: Van Halen One</OPTION>
<OPTION class="discardMe">Sonny and Cher: Greatest Hits</OPTION>
<OPTION class="discardMe">Sublime: 40 oz. to Freedom</OPTION>
</SELECT>
Now look in
DiscCatalog.java
for the code that populates the list box.
HTMLOptionElement templateOption = page.getElementTemplateOption();Node discSelect = templateOption.getParentNode();
The first line above retrieves the DOM object corresponding to the template
OPTION
tag. The second line calls getParentNode( ) to get the containerSELECT
tag. Since theSELECT
tag has an ID attribute, this line could have also been:
Node discSelect = page.getElementTitleList();Then, following some code for populating the table, there is one line to remove the template row.
templateOption.removeChild(templateOption.getFirstChild());The other
OPTION
tags haveCLASS
="discardMe," which causes XMLC to remove them at build time, as explained in "Removing Dummy Data."Then, within the for loop that iterates over the discs belonging to the current user, the following lines actually populate the list box:
HTMLOptionElement clonedOption = (HTMLOptionElement) templateOption.cloneNode(true);clonedOption.setValue( currentDisc.getHandle() );
Node optionTextNode =
clonedOption.getOwnerDocument().createTextNode(currentDisc.getArtist() + ": " +
currentDisc.getTitle());
clonedOption.appendChild(optionTextNode);
discSelect.appendChild(clonedOption);
The first line copies (clones) the template option element into a DOM object of type HTMLOptionElement. The second line sets the
VALUE
attribute to the value returned by getHandle( ), which is the disc'sOBJECTID
, an unique identifier. The third (very long) line creates a text node consisting of "artistName: titleName." Finally, the last two lines append the text node to the option node, and then append the option node to the select node.The resulting runtime HTML will look something like this:
<SELECT name='discID' id='TitleList'><OPTION value='invalidID' selected>Select One</OPTION>
<OPTION value='1000001'>Funky Urchin: Lovely Spines</OPTION>
<OPTION value='1000021'>The Seagulls: Screaming Fun</OPTION>
</SELECT>
Although this example might seem obscure, it is fairly short, and you can extend its basic functionality to handle more complex situations. For example, you modify it to set the default selection based on a second query.
Populating a Form
When a user chooses a disc from the list box and clicks on the "Edit Disc" button, a form appears that is populated with the existing values for that disc. The user can then edit the values and submit them back to the database.
![]()
Here is the HTML for the form elements in
Edit.html
. TheTABLE
tags have been omitted for clarity:
<INPUT TYPE="hidden" NAME="discID" VALUE="invalidID" ID="DiscID">Artist: <input name="artist" id="Artist" >
Title: <input name="title" id="Title" >
Genre: <input name="genre" id="Genre" >
Do you like this disk?
<input TYPE="checkbox" name="like" CHECKED ID="LikeBox">
<INPUT TYPE="submit" VALUE="Save This Disc Info">
In
Edit.java
, the event-handling method handleDefault( ) calls showEditPage( ) with a null parameter to populate the form with the selected disc's values. Ordinarily, the only request parameter (other than the event type) is the disc ID, accessed by this statement:
String discID = this.getComms().request.getParameter(DISC_ID);These statements also access the other request parameters, but ordinarily they are null (but see the error-handling case discussed later):
String title = this.getComms().request.getParameter(TITLE_NAME);String artist = this.getComms().request.getParameter(ARTIST_NAME);
String genre = this.getComms().request.getParameter(GENRE_NAME);
Then, a call to findDiscByID( ) retrieves a Disc data object that has that ID:
disc = DiscFactory.findDiscByID(discID);Then, there is a series of if statements that check the values of title, artist, genre, and isLiked, which are normally null. Therefore, the following statements are executed (the surrounding if statements are not shown for brevity):
page.getElementDiscID().setValue(disc.getHandle());page.getElementTitle().setValue(disc.getTitle());
page.getElementArtist().setValue(disc.getArtist());
page.getElementGenre().setValue(disc.getGenre());
page.getElementLikeBox().setChecked(disc.isLiked());
These statements use XMLC calls to set the
VALUE
attributes of the form elements; the values are retrieved from the disc object.When the user finishes editing, and clicks "Save this Disc Info," handleEdit( ) processes the changes. This method calls saveDisc( ), which attempts to save the new values--if successful, it redirects the client to the DiscCatalog page; if any of the new values are null, though, saveDisc( ) throws an exception. The catch clause then calls showEditPage( ) with an error string and with request parameters.
Note that ClientPageRedirectException is a subclass of java.lang.Error, so it is not caught by the catch clause when it is thrown.
try {saveDisc(disc);
throw new ClientPageRedirectException(DISC_CATALOG_PAGE);
} catch(Exception ex) {
return showEditPage("You must fill out all fields to edit this disc");
}
The result is that when a user tries to edit a disc and delete some of the values, the edit page will re-display, maintaining all the non-null form element values, and restoring the previous values to the null-valued form elements. The page will also display the error string.
The DiscRack business layer is simple, consisting primarily of two packages, Disc and Person, and two corresponding factory classes DiscFactory and PersonFactory. A factory is an object whose primary role is to create other objects.
The Business Objects
The business objects Disc and Person are largely wrappers for the corresponding data layer classes, DiscDO and PersonDO, with get and set methods for each property in the data objects (or column in the database tables). For example, Disc has getArtist( ) and setArtist( ) methods.
The objects in the business layer perform all the interfacing with the data layer. So, if the data layer needs to change, nothing in the presentation layer is affected; conversely, if the presentation layer changes, nothing in the data layer is affected.
DiscFactory has two static methods:
- findDiscsForPerson( ), that returns an array of Disc objects that belong to the Person object specified as the method's argument.
- findDiscByID( ), that returns the single Disc object that has the ID specified in the method's argument.
PersonFactory has one static method, findPerson( ), that returns a Person object that has the user name specified in the method's argument. If the method finds more than one person in the database, then it writes an error message to the log channel and throws an exception.
Using Data Objects
To help understand ow DiscRack uses DODS data layer code, look at the findPerson( ) method in PersonFactory. The comments have been removed from this code for brevity.
public static Person findPerson(String username)throws DiscRackBusinessException
{
try {
PersonQuery query = new PersonQuery();
query.setQueryLogin(username);
query.requireUniqueInstance();
PersonDO[] foundPerson = query.getDOArray();
if(foundPerson.length != 0) {
return new Person(foundPerson[0]);
} else {
return null;
}
} catch(NonUniqueQueryException ex) {
...
First, this method instantiates a new PersonQuery object. PersonQuery is a data layer object used to construct and execute a query on the person table. It has a number of setQueryXXX( ) methods for qualifying the query parameters (that is, setting the values to be matched in the
WHERE
clause of theSELECT
statement). For example, the above code calls setQueryLogin( ), with username as a parameter, to set the value to be matched in the LOGIN column.Next, the method calls requireUniqueInstance( ), which indicates that the query is to return a single row, and will throw an exception otherwise. Then, it calls getDOArray( ), which executes the query, returning an array of PersonDO objects. Finally, the method returns a single Person object returned by the query; if the query did not return any rows, it returns
null
.
Lutris Technologies http://www.lutris.com 1200 Pacific Ave., Suite 300 Santa Cruz, CA 95060 Voice: (831) 471-9753 Fax: (831) 471-9754 documentation@lutris.com |