![]() ![]() ![]() ![]() |
![]() |
This chapter describes how to build a Enhydra application from the ground up, and provides important tips on Enhydra application development. It begins with using the application wizard to create a starting framework, and leads you through using XMLC to expand the application, then adding simple database access.
If you are already familiar with the basics of Enhydra, you may want to skip to the next chapter, "The DiscRack Example Application," for a look at an application with more advanced features.
Note: In this tutorial, you are often instructed to enter commands. On UNIX platforms, you can enter the commands in any shell. On Windows, you must enter the commands in a Cygwin shell window.
The application wizard (sometimes referred to as "newapp") is a quick way to get up and running with Enhydra. It is a command-line tool that generates the basic Java files and directory structure for a new application.
First, create a directory to contain your new application, and name it anything you want, for example "myapps." Then, open a shell window and make the new directory the current directory, for example:
cd myappsNow, run the application wizard by typing
newapp
followed by the name of the application to create. In this case, call it simpleApp:
newapp simpleAppThe application wizard will create a new directory called
simpleApp
. This directory is sometimes referred to as the application root directory.Make this
the active directory:
cd simpleAppNotice that the application wizard created the following in this directory:
- Two make files:
config.mk
andMakefile
- A
README
file that contains some simple instructions to build and run the application
- A
simpleApp
directory that contains all the source code for the application; the contents of this directory are explained laterBuilding the Application
Enter the make command to build the application:
makeThis will create two sub-directories in the application root directory:
- The
classes
directory contains the application's class files.
- The
output
directory contains everything needed to run the application.The top-level make file,
Makefile
, contains directives that tell make to recursively descend the application directory tree, following the directives of the make file in each sub-directory. It also has anINCLUDE
directive that referencesconfig.mk
, which in turn references<
enhydra_root>/lib/stdrules.mk
, a large make file shared by all Enhydra applications.When you build the application, make compiles the files in the
simpleApp/simpleApp
source directory and creates a corresponding directory structure in theclasses
directory. It then combines those classes into ajar
(Java archive) file and places thejar
file into theoutput
directory along with the configuration files needed to run the application.To start the application, enter the following commands:
cd output./start
Now, to access the application, enter the following URL in your browser's location field:
http://localhost:9000.
You will see the following page in your browser:
![]()
You have just built and run your first Enhydra application!
Now hit Ctrl+C in the shell window to stop the Enhydra process.
How It Works
The application created by the application wizard provides a simple example of how Enhydra works. Look at the file
myapps/simpleApp/simpleApp/presentation/Welcome.html
, which contains a few dozen HTML tags. Notice tags such as these:
<CENTER>The current time is <span id="Time">1/1/00 00:00:00 Oh no!</span>.
</CENTER>
At runtime, Enhydra replaces the content of the SPAN tag with a date value. The text in there is just a placeholder; it will never appear at runtime. Note the period outside of the
SPAN
tag will not be replaced. Thus, the sentence will always end with a period.Look also at the
Welcome.java
file in the same directory. In particular, notice these lines of code:
String now = new Date().toString();WelcomeHTML welcome = WelcomeHTML)comms.xmlcFactory.create(welcomeHTML.class);
welcome.setTextTime(now);
comms.response.writeHTML(welcome);
When you build the application, XMLC finds the
SPAN
tag in the HTML and recognizes theID
attribute with value "Time". It creates a Java class called WelcomeHTML with a method setTextTime() that the application uses to modify the text content of theSPAN
tag. In general, XMLC will create a setTextXXX() method for eachSPAN
tag with ID attribute value "XXX".Then, at runtime, the application replaces the original text content of the
SPAN
tag with a string representation of the current date. Then, the call to writeHTML() writes the document out to the HTTP response, looking something like this:
...<CENTER>
The current time is <SPAN>Mon Feb 28 10:42:34 PST 2000</SPAN>.
</CENTER>
...
For a more detailed explanation of XMLC, see "Using XMLC."
The Directories and Files in SimpleApp
Let's take a closer look at the directories and files in the
simpleApp
directory:
- The
simpleApp
sub-directory contains the source code for the application. It is divided into three sub-directories for the business, data, and presentation layers. Thebusiness
anddata
directories are empty in the new application. Thepresentation
directory contains java, html, and media files.
- The classes sub-directory contains the application's compiled Java classes in their package hierarchy, and any associated media files such as
.GIF
files.
- The
output
sub-directory contains the application's configuration files,multiserver.conf
andsimpleApp.conf
, and alib
directory with asimpleApp.jar
file containing an archived package of everything in the classes directory.The finished Enhydra application includes the
jar
file and the configuration file. The make program also copies the multiserver configuration file and the start script to theoutput
directory to make it easier to run the application.The application configuration files contain critical information that determine how an Enhydra application runs:
- The Multiserver configuration file,
multiserver.conf
- The application configuration file, which is named
AppName.conf
(for examplesimpleApp.conf
) by default
- If the application is to be administered by the Multiserver Administration Console, the
multiserverAdmin.conf
file
- The start script, which is named
start
. This is not a configuration file; however, it specifies the all-importantCLASSPATH
that the application will use. If you do not specify aCLASSPATH
in this file, the application will use the systemCLASSPATH
, which may not be correct.By editing the configuration files, you can change the various settings contained in the files, as explained below.
Note: The application wizard creates "input" versions of the two configuration files in the app_root
/
appName directory; for example,simpleApp/simpleApp
. These are the files that you should edit. Running make creates "runtime" versions of the files in the app_root/output
directory, for examplesimpleApp/output
. You should not edit these versions of the files, because they will be over-written each time you re-build the project.The Multiserver configuration file contains information that the Multiserver uses to run the application, including:
- The name and location of the application configuration file
- The name of the log file and other log file information
- The TCP port on which the application will run
The application configuration file contains the following important application-specific information:
- The
CLASSPATH
that this application will use; the difference between thisCLASSPATH
and the one specified in the multiserver start script is that this one applies to the application's own class loader, while the latter is global in scope for all applications run by that Multiserver instance.
- The class name of the application object; for example:
Server.AppClass = simpleApp.SimpleApp
- The prefix used to derive presentation object class names and paths from URLs; for example:
Server.PresentationPrefix = "simpleApp/presentation"
- The maximum length (in minutes) of a user session and the session idle time; for example:
SessionManager.Lifetime = 60 SessionManager.MaxIdleTime = 2
- The default URL for the application; for example:
Application.DefaultUrl = "Welcome.po"
XMLC, the Extensible Markup Language Compiler was introduced in Chapter 2, "Overview." It is a powerful tool that you can use to create applications that have a clean separation between the user interface and the back-end programming logic.
In general, XMLC can work with XML pages, but for practical reasons we are going to focus on how it works with HTML pages. XMLC parses an HTML file and creates a Java object that enables an application to change the HTML file's content at runtime, without regard for its formatting. The Java objects that XMLC creates have interfaces defined by the Document Object Model (DOM) standard from the World Wide Web Consortium (W3C).
For simplicity, the remainder of this chapter will refer to HTML documents, but in most cases the discussion can be extended to include XML documents in general.
Adding a Hit Counter
To get a feel for how XMLC works, you are going to extend your application to display a "hit counter" that shows the number of users who have accessed it.
Find the files
Welcome.html
andWelcome.java
in thepresentation
directory.Add the following line of HTML toWelcome.html
before the closing</CENTER>
tag:
Number of hits on this page: <SPAN ID="HitCount">no count</SPAN>The ID attribute tells XMLC to generate an object corresponding to the SPAN tag, so that it can replace the text "no count" at runtime.
Now, add the two lines of code indicated below to
Welcome.java
in the same directory.
import java.util.Date;import com.lutris.xml.xmlc.*;
import com.lutris.appserver.server.httpPresentation.*; public class Welcome implements HttpPresentation {
Add this line:
static int hitCount=0; // All Welcome PO's will share this.public void run(HttpPresentationComms comms)
throws HttpPresentationException {
String now = new Date().toString();
WelcomeHTML welcome = WelcomeHTML)comms.xmlcFactory.create(welcomeHTML.class); welcome.setTextTime(now);
// Increment the count and write into the html.
Add this line: welcome.setTextHitCount( String.valueOf( ++hitCount ) );
comms.response.writeHTML(welcome);
}
}
Build the application by running make from the top-level
simpleApp
directory. Then restart Enhydra:
cd /myapps/simpleAppmake
cd output
./start
Building the application with make runs XMLC on all the HTML files in the application, in this case, just
Welcome.html
.Test the application by loading
http://localhost:9000
in your browser. You will see this:
![]()
Notice that the page now displays the number of times it has been accessed. Reload the page several times to verify that it works correctly.
The application is doing two things:
- Storing the hit count in hitCount, a static property of the Welcome presentation object
- Writing it to the web page with the setTextHitCount() method
Recall that the presentation manager instantiates a presentation object for each request. So, the Welcome class is instantiated once per browser request. But because hitCount is a static property, it is shared by all Welcome objects, and its value gets incremented by each request.
In the same way that it added a setTextTime method for the
<SPAN ID="Time">
tag, XMLC creates a setTextHitCount method for the<SPAN ID="HitCount">
tag. The application then uses the setTextHitCount method to write the value of hitCount into the page, within the corresponding SPAN tag.Note: XMLC creates the WelcomeHTML class, but by default it deletes the Java source file.
Understanding the DOM
HTML documents have a hierarchical or tree-like structure that can be modeled in an object-oriented language such as Java. The Worldwide Web Consortium's standard for the XML/HTML object model is called the Document Object Model (DOM).
Enhydra applications use the DOM to manipulate HTML content at runtime. For example, consider the following HTML:
<TABLE><TR>
<TD ID="cellOne">Shady Grove</TD>
<TD ID="cellTwo">Aeolian</TD>
</TR>
<TR>
<TD ID="cellThree">Over the River, Charlie</TD>
<TD ID="cellFour">Dorian</TD>
</TR>
</TABLE>
This HTML snippet has a TABLE tag that contains TR tags, that in turn contain TD tags containing text. This defines a tree-like hierarchy, as illustrated in Figure 4.1
Figure 4.1 DOM tree of HTML
Each box or ellipse in the above figure is a node in the tree. The node above a node in the hierarchy is called is parent; the nodes below are called its children. Some nodes (like HTML tags) have attributes, for example a table cell has a background color attribute. The W3C defines packages and interfaces that mirror the object hierarchy of nodes in an HTML document. In addition, XMLC includes an API for changing attribute values.
You could use code like this to set the color of one of the table cells:
HTMLTableCellElement cellOne = theDocument.getElementCellOne();cellOne.setBgColor("red");
The class HTMLTableElement and setBgColor() came from the W3C packages, and getElementCellOne() came from XMLC. This code illustrates one important thing XMLC does--create methods to access nodes in the DOM. XMLC generates the getElementXXX() methods that return objects corresponding to tags with ID attributes. You could change the color of a table cell with the W3C classes alone, but your code would have to traverse the DOM tree, so it would have been more laborious.
SPAN
andDIV
are HTML tags that you may not be familiar with. They are typically used to apply styles using Cascading Style Sheets. Outside of that, they are largely ignored by browsers. XMLC makes extensive use of them, however.Use the
SPAN
tag to enclose a block of text that you want to replace at runtime. In general, aSPAN
tag can enclose any text or in-line tag. An in-line tag is any tag that does not cause a line break in the layout, for exampleA
(anchor) orB
(bold) tags. Do not useSPAN
tags to enclose other tags, such asTABLE
orP
(paragraph). Use theDIV
tag to enclose block tags, such asTABLE
, that do cause a line break in the HTML layout.Using XMLC From the Command Line
Previously, you have run XMLC implicitly when you built the project with make. To run XMLC from the command line, you must have the enhydra.jar file on your
CLASSPATH
.This is something that the make files automatically do for you.Set your
CLASSPATH
with one of the following commands:UNIX:
export CLASSPATH=<enhyra_root>/lib/enhydra.jar:.
Windows:
export CLASSPATH=<
enhydra_root>/lib/enhydra.jar\;.
The basic command-line syntax of XMLC is:
XMLC -options file.htmlwhere options is a set of command line options, and file is the name of the input file. There are several dozen command line options. In this section we introduce three immediately useful ones: dump, class, and keep.
The -dump option makes XMLC display the DOM tree for a document. This is primarily useful as a learning tool; once you are familiar with XMLC you will rarely use it.
Create a new file called
Simple.html
in thesimpleApp/simpleApp/presentation
directory. Add the following HTML to it:
<HTML><HEAD><TITLE>Simple Enhydra Page</TITLE></HEAD>
<BODY>
<H1 ID="MyHeading">Ollie Says</H1>
The current time is <SPAN ID="Time">00:00:00</SPAN>.
</BODY>
</HTML>
Change to the
presentation
directory and enter this command:
xmlc -dump Simple.html
XMLC displays the following in the shell window:
DOM hierarchy: HTMLDocument:null DocumentTypeHTMLHtmlElement: HTML
HTMLHeadElement: HEAD
HTMLTitleElement: TITLE
Text: XMLC Test
HTMLBodyElement: BODY
HTMLHeadingElement: H1: id='MyHeading'
Text: Ollie Says
Text: The current time is
HTMLElement: SPAN: id='Time'
Text: 00:00:00
Text: .
Each line shows the DOM object name followed by a colon and then the corresponding HTML tag; if the tag has attributes they are listed following the tag in name/value pairs. For instance, HTMLHeadingElement is the DOM name for the H1 tag, and it has an id attribute with value "MyHeading." The level of indenting shows the object relationships, so for example, you can see that the first HTMLHeadingElement is the child of HTMLBodyElement.
By default, XMLC creates a class with the same name as the HTML file. So, for
Simple.html
it would createSimple.java
. Use the -class option to specify a name for the class that XMLC creates.By default, XMLC deletes the Java source file that it creates; leaving only the compiled class file. Use the -keep option to keep the Java source file. The source file is useful primarily for understanding how XMLC and the DOM API works.
To create a Java object named SimpleHTML for the HTML file
Simple.html
, and to keep the Java source, enter this command:
xmlc -keep -class simpleApp.presentation.SimpleHTML Simple.html
XMLC generates two files:
SimpleHTML.java
andSimpleHTML.class
.Open
SimpleHTML.java
. At the beginning of the file, you will see two methods, getElementMyHeading() and getElementTime(). XMLC recognized the ID attributes of the heading and SPAN tags and generated these methods. XMLC also generated the method setTextTime() for the SPAN tag. Peruse this file to get an idea of the object that XMLC creates for a very simple document.You're done exploring how XMLC works for now. But keep your
Simple.html
file, because you are going to use it later in this tutorial.
This section covers some more topics essential to Enhydra application development:
- Maintaining session state
- Adding a page to an application
- Populating an HTML table
- Adding a business object to the application
Maintaining Session State
Since HTTP is a stateless protocol, an application that needs to keep user-specific information across multiple requests must perform session maintenance. For an overview of how Enhydra performs session maintenance, see "Session Manager."
Think of the user's session as a container in which the application can store any information associated with a particular user. The class that you use as the container is com.lutris.appserver.server.session.SessionData; it is similar to a hash table in that it has a set() method to which you pass a string key and an object to store, and a get() method that returns the object, given the string key.
Enhydra matches a user to a particular SessionData object with a session key, a very long randomly-generated character string. When the Enhydra session manager first creates a SessionData object for a user, it generates a session key and stores it in its internal data structure. Enhydra also gives the session key to the client, either passed as a cookie or appended to the URL. The next time the client makes a request, the session manager uses the key to find that user's SessionData object.
Generally, you don't need to worry about the session key--Enhydra handles all those details for you "under the hood." You do, however, need to keep track of the keyword strings that you use to get and set each object you want to save to the session.
To help you understand session maintenance, you are going to enhance your application so that the Welcome page displays the number of times a particular user has accessed it, in addition to the total "hits" on the page. For fun, you'll also display the session key on the page.
Add these four lines of HTML just before the closing
</CENTER>
tag inWelcome.html
:
<P>Number of hits from you:<SPAN ID="PersonalHitCount">no count</SPAN>
<P>Session identifier:
<SPAN ID="SessionID">no count</SPAN>
Now add the following code to
Welcome.java
. Add this import statement:
import com.lutris.util.*;Add this member property to the Welcome class (just after hitCount):
final String hits = "HITS";The string "HITS" is the keyword that the application will use to save and recall the hit count information.
Now add the following code to the run() method, just before the call to comms.response.writeHTML(). You can find this code in the
<
enhydra_root>/doc/samples/SessionMaint.java
file.
try {Integer personalHits = (Integer)comms.session.getSessionData().get(hits); if(personalHits == null) {
personalHits = new Integer(1); } else {
personalHits = new Integer(personalHits.intValue() + 1);
} comms.session.getSessionData().set(hits, personalHits);
// Save personalHits to the user's session.
welcome.setTextPersonalHitCount( personalHits.toString() ); welcome.setTextSessionID( comms.session.getSessionKey() );
// Shows the session key value used for session tracking. } catch (KeywordValueException e) {
comms.response.writeHTML("Session access error" + e.getMessage());
}
This code begins by calling getSessionData().get(hits) to get the value stored for the keyword string "HITS." Because SessionData stores only generic java.lagn.Objects, you have to type cast it to Integer. If the object has not been previously stored in the session, the code creates a new Integer of value one. If it does exist, it is incremented.
The code then saves the Integer object back into the session with setSessionData().set(hits, personalHits) and writes the value into the web page with getSessionKey(). Normally, you would not need to deal with the session key, but for curiosity's sake this example shows you how to display it.
Rebuild and start the application, and access the page with your browser. The page now looks like this:
![]()
The page now displays the total number of hits as well as the number of hits from a particular client. Since you are running the application on your Localhost server, it is not accessible to any other clients, so these two numbers will always be the same. However, if it were running on a "real" server, you would see different numbers depending on how many times you had accessed the page versus the total number of hits. Notice also that the session ID string always stays the same.
Adding a New Page to the Application
Next, you are going to add a new page (HTML file and presentation object) to your application. You're going to use the little HTML file you created previously,
Simple.html
. In addition to learning how to add a page, you're also going to play around with the DOM a little bit to become more familiar with it.First, you need to create a new presentation object. Copy the file
Welcome.java
, and call itSimple.java.
EditSimple.java
and change the name of the class from Welcome to Simple, and then remove all the session-related code you added to Welcome. Change all the occurrences of WelcomeHTML to SimpleHTML; also change the welcome identifier to simple.Now, you have a "stripped down" presentation object. The run method should look like this:
public void run(HttpPresentationComms comms) throws HttpPresentationException {String now = new Date().toString();
SimpleHTML simple = new SimpleHTML();
simple.setTextTime(now);
comms.response.writeHTML(simple);
}
Add the following statements at the top of the file, after the other import statements:
import org.w3c.dom.*;import org.w3c.dom.html.*;
Now add the following lines just before the last statement in the run() method:
HTMLHeadingElement heading = simple.getElementMyHeading();heading.setAttribute( "align", "center" );
Text heading_text = (Text) heading.getFirstChild();
heading_text.setData( "Mr. Ollie Otter says:" );
- Gets the HTMLHeadingElement object named "MyHeading," from the DOM
- Sets its
ALIGN
attribute toCENTER
. This will center the heading on the page
- Gets the child object of the heading (a Text object)
- Sets a new value for the text, "Mr. Ollie Otter says:"
You could have done the same thing by putting a SPAN tag around the text in the heading, since then XMLC would have generated a setTextMethod() that you could have then called in the code, but this example illustrates how to do it with the DOM.
Note: This code performs some low-level DOM manipulation that you should normally not do in your application, because it violates the separation of presentation and business logic. It is only presented here to help explain the DOM.
Finally, edit
Makefile
in thepresentation
directory and add the new files to theCLASSES
andHTML_CLASSES
variables, to make them look like this:
CLASSES = \Redirect \
Welcome \
Simple HTML_CLASSES = WelcomeHTML \
SimpleHTML
The
HTML_CLASSES
variable is passed to the XMLC -class option to make it create the specified classes, just as you did on the command line. The Lutris convention is to create fooHTML class for a filefoo.html
. This convention is defined in<
enhydra_root>/lib/stdrules.mk
.Save all the files and run make in the
presentation
directory to build the package.To create a link from the Welcome page to your new page, add the following HTML at the bottom of
Welcome.html
:
<A HREF="Simple.po">Go to Simple Page</A>If you have not done so already, stop your Multiserver by hitting ctrl-C in the shell window, and build the application from the top level:
cd /myapps/simpleApp/simpleAppmake
Now access the application from your browser, as you did before. Click on the link "Go to simple page" to view the Simple PO. You will see the following rather uninteresting page:
![]()
Don't worry, though: you're going to make this page more interesting in the next section.
Populating a table
Another common task in web application development is how to populate an HTML table with dynamic data. This section discusses populating a table using a static String array as the data source; in a later section, you will modify the code to get data from a database.
Follow these steps to populate a table:
- In the
Simple.html
file, create an HTML table with a template row with an ID attribute
- In the corresponding presentation object, programmatically populate the table.
- Rebuild and run the application.
Edit the file
presentation/Simple.html
in your simpleApp project. Add the HTML shown below just before the end of the BODY tag.Note: If you don't want to type in all this HTML, you can copy and paste it from
<
enhydra_root>/doc/GettingStarted/samples/TableCode.html
.
<H2 align=center>Disc List</H2><TABLE border=3>
<TR>
<TH>Artist</TH> <TH>Title</TH> <TH>Genre</TH>
<TH>I Like This Disc</TH>
</TR>
<TR id=TemplateRow>
<TD><SPAN id=Artist>Van Halen</SPAN></TD>
<TD><SPAN id=Title>Fair Warning</SPAN></TD>
<TD><SPAN id=Genre>Good Stuff</SPAN></TD>
<TD><SPAN id=LikeThisDisc>Yes</SPAN></TD>
</TR>
</TABLE>
This HTML contains a table with a single "template" row (
TR
tag). Notice that both this row and theSPAN
tags enclosing the cell contents have ID attributes. This is called a template row, because it is used as a model from which you construct further rows of the table.Programmatically Populate the Table
Now copy the file
<
enhydra_root>/doc/GettingStarted/samples/TableCode.java
. into your application's presentation directory, and rename itSimple.java
. If you like, you can save your oldSimple.java
toSimple.sav
for future reference.Now, look at your new
Simple.java
: In addition to the standard features of a presentation object, the first thing you'll notice in this code is a member property of that is an array of strings representing the content the application will use to populate the table. This array takes the place of a database result set for this example:
String[][] discList ={ { "Felonious Monk Fish", "Deep Sea Blues", "Blues", "Yes" },
{ "Funky Urchin", "Lovely Spines", "Techno Pop", "Yes" },
{ "Stinky Pups", "Shark Attack", "Hardcore", "Not" } };
The next new section of code gets the document objects for the table elements:
HTMLTableRowElement templateRow = simple.getElementTemplateRow();HTMLElement artistCellTemplate = simple.getElementArtist();
HTMLElement titleCellTemplate = simple.getElementTitle();
HTMLElement genreCellTemplate = simple.getElementGenre();
HTMLElement likeThisDisc = simple.getElementLikeThisDisc();
The next section of code removes the ID attributes from these objects. The reason for this is that the DOM requires that each ID in the document be unique; when you make a copy of the table row, you would otherwise have duplicate IDs. The removeAttribute() method removes the attribute with the specified name:
templateRow.removeAttribute("id");artistCellTemplate.removeAttribute("id");
titleCellTemplate.removeAttribute("id");
genreCellTemplate.removeAttribute("id");
likeThisDisc.removeAttribute("id");
Then, a call to getParentNode() gets a reference to the table document object, which you'll be using later:
Node discTable = templateRow.getParentNode();Next comes the heart of the code, a for loop that iterates through each "row" in the "result set," puts text in each cell in the table row, and then appends a copy (or clone) of the row to the table:
for (int numDiscs = 0; numDiscs < discList.length; numDiscs++) {simple.setTextArtist(discList[numDiscs][0]);
simple.setTextTitle(discList[numDiscs][1]);
simple.setTextGenre(discList[numDiscs][2]);
simple.setTextLikeThisDisc(discList[numDiscs][3]);
discTable.appendChild(templateRow.cloneNode(true));
}
That last statement is crucial: The cloneNode() method creates a copy of the Node object which calls it, in this case, templateRow. The boolean argument determines if it copies only the node itself or the node and all its children, and their children, and so on. In this example, the argument is true, because you want to copy the row and its child nodes (the table cells and the text inside them).
Finally, removeChild() removes the template row from the table. This ensures that the "dummy data" in the template does not show up in the runtime page.
discTable.removeChild(templateRow);Rebuild and Run the Application
Now rebuild the application, and load the page in your browser. You should see the following result:
![]()
Adding a Business Object
So far, your application has three objects: the SimpleApp application object, and two presentation objects, Welcome and Simple. Now, you are going to add a business object that you will use it in the following sections. This will not change what the application displays.
The business object represents a list of discs; this is not terribly useful, but it does suffice to illustrate a basic role of business objects as you proceed.
Create a new file called
SimpleDiscList.java
in the business directory,simpleApp/simpleApp/business
. It's in your application's business package, so the first line in the file will be:
package simpleApp.business;Now, add the following lines (cut and paste the array initializer from the Simple class, but be sure to add the underscore in front of the identifier):
public class SimpleDiscList { String[][] _discList ={ { "Felonious Monk Fish", "Deep Sea Blues", "Blues", "Yes" },
{ "Funky Urchin", "Lovely Spines", "Techno Pop", "Yes" },
{ "Stinky Pups", "Shark Attack", "Hardcore", "Not" } }; public SimpleDiscList() {
} public String[][] getDiscList() {
return _discList;
}
}
To ensure that this file gets compiled when you build the project, edit the make file in the
business
directory, and add the name of the file to the CLASSES variable:
CLASSES = \SimpleDiscList
This make file is automatically included by the top-level make file, so that is all you have to do to add the file to the project. Make sure the file compiles by entering make in the
business
directory.Now, back in the
presentation
directory, editSimple.java
as follows:
Add these two lines to create an instance of your new business object, and call its getDiscList() method. These lines take the place of the static array initializer in the previous section.
SimpleDiscList sdl = new SimpleDiscList();String[][] discList = sdl.getDiscList();
Rebuild and test your application.
You won't see anything different, but you have extracted some functionality out of the presentation object into the new business object. This will come in handy in an upcoming section: when you replace the static array with a real database query, you won't have to change your presentation class, because the business object provides a buffer between it and the data layer.
Enhydra uses Java Database Connectivity (JDBC), a standard Java API, to communicate with databases. Enhydra can connect to any JDBC-compliant database, such as Oracle, Sybase, Informix, Microsoft SQL Server, PostgreSQL, and InstantDB.
Before you can proceed to connect the application to a database, you are going to take a brief detour to lay some groundwork. In particular, you are going to:
- Create the database table used by the application
- Establish and test the JDBC connection to your database
- Configure Enhydra's database manager to connect to your database through JDBC
Creating a Database Table
The remainder of this section requires the existence of a specific table in your data, so you need to create that table before proceeding. Most databases provide a tool for directly executing SQL statements. For example, Oracle supplies SQL*Plus. Use this tool to execute the provided SQL file and create the table in your database. The SQL file is in
<
enhydra_root>/doc/GettingStarted/samples/tutorial_create.sql
Here is the command for Oracle SQL*Plus:
SQL> @<
enhydra_root>/doc/GettingStarted/samples/tutorial_create.sql
This SQL file contains a
CREATE
TABLE
statement to create a simple table,LE_TUTORIAL_DISCS
, and someINSERT
statements to populate it with data. You are going to use this table in the forthcoming sections of the tutorial.If you are using a database other than Oracle, refer to your database documentation for instructions on how to execute a SQL file or create tables.
Establishing a JDBC Connection
Before you can create a database application, you need to establish a JDBC connection from your system to the database server, which may be running on a different system.
This section shows you how to write a simple stand-alone program to establish a JDBC connection to database server. Starting with a stand-alone program enables you to isolate any problems that occur.
This example uses an Oracle database. If you are using a different database, refer to your database's documentation for more specific information
Note: If you have already configured JDBC on your system, you can skip this section.
- Configure your database to "listen" on a specific TCP port. Most database servers listen on a certain port by default (for example, Oracle uses port 1521). See your database's documentation for details.
- Install the JDBC driver for your database. This consists of a
zip
orjar
file containing the database-specific JDBC classes, for example the Oracle JDBC driver isclasses111.zip
. See your database's documentation for details.
Windows: The Cygnus tools require the JDBC driver to be on the C drive, so make sure the JDBC driver library is on the C drive.
Every database has its own format. For example, the connection string for the Oracle driver is
jdbc:oracle:thin:@db_host:xxxx:db_inst
where
db_host
is the name of the database server,xxxx
is the port number (1521, by default), anddb_inst
is the name of the database instance. You will also need your database username and password. Use these values as illustrated in the call to DriverManager.getConnection() in the code belowUse the following simple program to test your JDBC connection:
import java.sql.*; public class JDBCTest { public static void main( String[] args ) {Connection con = null;
Statement stmt = null;
ResultSet rs = null; // Load the driver, get a connection, create statement, run query, and print.
try {
Class.forName("oracle.jdbc.driver.OracleDriver");
con = DriverManager.getConnection(
"jdbc:oracle:thin:@your_computer:1521:your_sid" ,"name", "pass" );
stmt = con.createStatement();
rs = stmt.executeQuery("SELECT * FROM LE_TUTORIAL_DISCS");
rs.next();
System.out.println("Title = " + rs.getString("title") +
" -- Artist = " + rs.getString("artist"));
}
catch(ClassNotFoundException e) {
System.err.println("Couldn't load the driver: " + e.getMessage());
}
catch(SQLException e) {
System.err.println("SQLException caught: " + e.getMessage());
}
}
}
- Create a directory with a name such as
/tmp/jdbcTest
.
- Copy the above code from
<
enhydra_root>/doc/GettingStarted/samples/JDBCTest.java
into the new directory.
- Edit the file to put in your connect string, username and password. These appear in the call to getConnection(), the second statement in the try block.
- Compile the file:
javac JDBCTest.java
- Set your
CLASSPATH
so the JVM can find your JDBC driver and the test program. For an Enhydra application, this would normally be performed in an application's start script; however, since this is a stand-alone test application, you'll just do it in the shell window. On UNIX, enter this command:
export CLASSPATH=<
path_to_your_JDBC_driver>:.
If you have populated the table as instructed previously, you will see the following in the shell window:
Title = Rockin Apps -- Artist = Enhydra OrchestraIf there was an error, you will see some exception messages in the shell window that should help you isolate the problem. Also, refer to your database's JDBC documentation or the Enhydra mailing list.
Configuring the Application to use JDBC
To make the JDBC classes available to your Enhydra application, you must put the JDBC driver in the
CLASSPATH
system variable. To do this, setCLASSPATH
in the application's start script. Note there are two copies of the start script: one in the application's sourcedirectory and one in the
output
directory. As withapplication.conf
, building the application overwrites the one in theoutput
directory.Note: Enhydra has its own class loader, so if you put the JDBC driver in the
CLASSPATH
by specifying it in the application's configuration file, the driver will not work.So, edit the file
simpleApp/simpleApp/start
and add these lines just before the last line in the file:
CLASSPATH="JDBC_LIB"export CLASSPATH
where JDBC_LIB is the JDBC driver library (generally a
jar
orzip
file), including the file path. For example,
CLASSPATH="/myapps/lib/classes111.zip"Be careful not to put any blank spaces in this line, since they will prevent it from working properly.
Configuring the Database Manager
Now that you have verified your JDBC connection, you need to provide the database connection parameters to your application. You do this in the application's configuration file,
appName.conf
. In this example, the file issimpleApp.conf
. The make utility copies this file to theoutput
directory after every build, so be sure to edit the file insimpleApp/simpleApp
, not the version insimpleApp/output
.Open
simpleApp/simpleApp/simpleApp.conf
in a text editor. Add the following lines to the bottom of the file:
#--------------------------------------------------------------# Database Manager Configuration #------------------------------------------------------------------- DatabaseManager.Databases[] = "id"
DatabaseManager.DefaultDatabase = "id"
DatabaseManager.Debug = "false"
DatabaseManager.DB.id.ClassType = "Oracle"
DatabaseManager.DB.id.JdbcDriver = "oracle.jdbc.driver.OracleDriver"
DatabaseManager.DB.id.Connection.Url = "jdbc:oracle:thin:@db_svr:1521:db_inst"
DatabaseManager.DB.id.Connection.User = "User"
DatabaseManager.DB.id.Connection.Password = "Password"
DatabaseManager.DB.id.Connection.MaxPreparedStatements = 10
DatabaseManager.DB.id.Connection.MaxPoolSize = 30
DatabaseManager.DB.id.Connection.AllocationTimeout = 10000
DatabaseManager.DB.id.Connection.Logging = false
DatabaseManager.DB.id.ObjectId.CacheSize = 20
DatabaseManager.DB.id.ObjectId.MinValue = 1
Note: This is an example of the configuration file for Oracle; for information on configuring for other databases, see Appendix A, "Database Configuration."
Change all of the items shown in bold to match your connection parameters as follows:
- The
id
identifier is used in the configuration file to identify the database connection. You can use any string here, as long as it is consistent throughout the file.
- The connection string is jdbc:oracle:thin:@db_svr:1521:db_inst. This is the format of the Oracle connection string, where
db_svr
is the database server, 1521 is the TCP port on which the server is listening, anddb_inst
is the name of your database instance.
- Set User to your database user name
- Set Password to your database password
Make sure there is a carriage return at the end of the file; this is required for the file to work properly.
After you edit the configuration file, run make to propagate the changes to the file in the
output
directory.Adding Data Access
Now that you have laid the groundwork, you are ready to add data access to simpleApp. This section describes how to add a simple data object with embedded SQL that replaces the static array used in "Populating a table." The next section, "Using DODS," describes how to build a "real" data layer for the application using DODS.
First, you are going to create a data object that queries the database. Copy the file
SimpleDiscQuery.java
from<
enhydra_root>/doc/GettingStarted/samples
to your data layer, that is, thesimpleApp/simpleApp/data
directory. Remember to edit the make file in the data directory to add the file to theCLASSES
variable, just as you did in "Adding a Business Object."Take a look at
SimpleDiscQuery.java
. In particular, notice the import statement right at the top:
import java.sql.*;This tells you right away that this class is going to use JDBC. In addition to the constructor, there is only one other method, query(), where the object performs most of its real work.
The constructor has essentially one statement:
c = Enhydra.getDatabaseManager().allocateConnection();This statement tells the Enhydra database manager to allocate a database connection. Then, the query() method calls executeQuery on the connection to execute the SQL query statement:
resultSet = connection.executeQuery("SELECT * FROM LE_TUTORIAL_DISCS");The remainder of the code in query() iterates through the result set returned by the
SELECT
statement, and returns it in the form of a Vector of Vectors. Although each row is known to contain only four elements (since there are four columns in the table), the number of rows is unknown in general, which is why the method returns a Vector.However, you will recall that the presentation object Simple expects the data to be in the form of a two-dimensional array of Strings. So, the SimpleDiscList needs to perform some conversion.
Edit the file for the business object you created previously,
SimpleDiscList.java
.Now, find the file
<
enhydra_root>/doc/GettingStarted/samples/SimpleDiscList.java
. You can simply replace your oldSimpleDiscList.java
with this file, or if you prefer, you can make the changes manually:
- Add two import statements at the top:
import simpleApp.data.SimpleDiscQuery;import java.util.*;
- Add a member variable corresponding to the data object:
SimpleDiscQuery _sdq;
- Replace the body of the getDiscList() method with the code in the new file. It converts the Vector of Vectors returned by query() to a two-dimensional String array that the presentation object expects.
Notice that you did not have to change the presentation object at all. The data object provides a buffer between the PO and the data object.
Now, build the application from the top level. When you get it to compile, try running it. If everything is in place, you will see the following:
![]()
Notice the discs displayed in the table have the values from the database, not the static array. You've created your first database query page!
The Data Object Design Studio (DODS) is an graphical object-relational mapping tool that generates SQL code to create tables in a database and the corresponding application code to access the tables. DODS creates code that is specific to different databases, so you don't have to learn the nuances of each database. DODS also handles common issues such as transactions and data integrity.
DODS is most useful when you are creating a database from scratch, or when you are free to modify the database schema, as explained in "Loading the Schema."
You are not required to use DODS in developing a Enhydra database application, but it does significantly simplify the process.
Introduction
This section introduces DODS and explains how to use it to create the database schema and associated data objects for the DiscRack application (see Chapter 5, "The DiscRack Example Application"). This example uses an Oracle database.
Start DODS by entering the following command:
dodsNote: If DODS does not run when you enter this command, you need to set your
PATH
. See "Setting the PATH Environment Variable."You will see the DODS graphical interface. Open the DODS file for the DiscRack project by choosing File | Open, and selecting
<
enhydra_root>/examples/DiscRack/discRack.doml
.DODS will show the DiscRack data model:
![]()
Figure 4.2 DODS with the discRack.doml loaded.
The DODS window has three sub-panels:
- At the top, the object panel shows the entity-relationship model. Each data object (corresponding to a table in the database) is represented by a box with a name inside. Relationships between data objects are shown by red lines between the boxes.
- In the lower left, the package panel displays the package hierarchy in the current data model, with a directory for each object/table in the data model, and a blue dot signifying objects in each package.
- In the lower right, the attribute panel shows the attributes (properties or columns) of the selected data object. Each attribute is represented by a row in the grid, with various information, such as the data type or nullability of the attribute, indicated in columns of the grid.
The schema of the discRack database is pictured in Figure 4.2. DODS shows the features common to both the database schema and the object model. For example, the disc data object has title and artist fields, that are the properties (members) of the Java class as well as the columns of the corresponding database table.
DODS creates Java code for object operations and SQL code for database operations, for example the one-to-many relationship between person and discs.
![]()
Figure 4.3 Disc Rack object-model/schema
Creating Data Objects
Now you are going to create the data layer for the discRack application from scratch.
- Create a new data model by choosing File | New. DODS will close any open data model file and start a new one containing a single package, named "root" by default.
- Select your database type. Using the Database menu, select your database type. For example, if you are using Oracle, choose Database | Oracle. This will ensure that the SQL statements that DODS generates have the correct syntax for your database.
- Now create the package hierarchy.
Begin by changing the name of the root package. In the lower left panel, select the
root
folder then choose Edit | Package. Enter the name of this application in the dialog box, "simpleApp," then hit Enter.Add the data package: select the discRack folder, choose Insert | Package, type "data," then hit Enter.
Similarly, create
disc
andperson
directories as subdirectories of thedata
directory. Recall that package names in Java begin with a lowercase letter. The package panel (in the lower left of the window) should now look like this:
![]()
Select the
person
package in the lower left pane then choose Insert | Data Object. You will see the Data Object Editor dialog box.
![]()
Change the defaults as follows:
- In the Class tab, change the name to "Person"
- In the Package tab, select the person package
- In the DataBase tab, change the "db Table Name" to "Person"
Leave the rest of the defaults and click OK. This will create both a Person table in the database and a Person object in the Java application. Note the class is uppercase "Person" while the package name is lowercase "person."
Now add the login attribute to the Person data object. Select the Person data object (indicated by a blue dot) and click Insert | Attribute. The Attribute Editor window comes up:
![]()
Change the defaults as follows:
- In the General tab, change the name to login.
- In the Java tab, note the default Java data type is string, corresponding to the SQL VARCHAR data type. Leave this value.
- In the Database tab, select the "Can be queried" check box.
Leave the rest of the defaults and click OK.
Notice that the attributes appear in the attribute panel in the lower right of the DODS window. The fields show the different options you can set in the Attribute Editor; for example, the yellow "Q" means that the data object can be queried. Move the cursor over the other symbols to see what they represent.
Repeat this procedure to add three more attributes to the person data object: "password," "firstname," and "lastname." You may want to use the Add Attribute button on the toolbar.
- Create the Disc data object similarly to the procedure in the previous step. Add the following attributes: title, artist, and genre.
Adding the owner attribute is a little more complicated because there is a one to-many relationship between Person and Disc based on the owner attribute. Select the Disc data object and choose Insert | Attribute. Change the name to Owner. Click on the Java tab and then on the Java Type list box. Scroll down and select "discRack.data.person.PersonDO."
Click on Database tab. Select "Can be queried" and "Referenced DO must exist." Choose OK to close the Attribute Editor.
You will notice a red arrow in the interface between the Disc object and Person object showing that Disc uses Person. Notice the attribute pane displays icons indicating that the owner attribute has an object reference and a referential constraint.
Save the data model file. DODS stores the data model specification in a doml file, with an XML-like syntax. Select File | Save and choose a location for the doml file. Save it in the top level project directory.
Now that you have defined all the data objects, DODS is ready to generate and compile the code. The "Build All" command causes DODS to overwrite the existing data directory with all the new information from the data model, so if you want to save your old data layer code, copy it to another directory.
Now, the big moment you have been waiting for... select File | Build All. The "Destroy and Create New DODS Directory" dialog box appears. Select the data directory of your simpleApp project. Choose the Re/Create Directory button. You will see the following warning dialog box:
![]()
Choose Yes, and the build will begin. DODS will align the directory structure in the GUI with the directory structure in the project, generate Java and SQL files, and then run make to build the data layer.
As it proceeds, DODS displays messages in a dialog box:
![]()
If the build is successful, you will see the message "DODS BUILD COMPLETE." If the build fails, look at the messages in the output screen. Verify that the paths of the files are correct. You can also search the doml file for clues, since it is easy to read.
DODS generates the following files in the
data
directory:
- The
disc
andperson
directories, which contain the Java code for the disc and person data objects, respectively, and an SQL file defining the corresponding database table.
- The
create_tables.sql
anddrop_tables.sql
files, which contain standard SQL statements to create and remove, the disc and person tables from a database, respectively.
- Two make files:
Makefile
, andconfig.mk
.
- The
classes
directory, which is initially empty.Each data object directory contains Java source files to create four classes. For example, the person data object contain personDO, personQuery, personDataStruct, and personBDO. The data object and the query classes are the most commonly used classes.
DODS also generates make files for the data layer. This allows you to compile the data layer independently or along with the entire project. The empty classes directory is only used if you compile the data layer separately.
Loading the Schema
The next step in the process is to run the SQL script that DODS generated to create the tables in the database. Figure 4.4 illustrates the complete schema generated by DODS:
![]()
Figure 4.4 Disc Rack database schema generated by DODS
Notice there are some differences from the original database schema:
- The DISC and PERSON tables have two additional fields, OID and VERSION;
- There is a third table, OBJECTID, that contains one column, NEXT, with a single row.
The OID column is the primary key for each table created by DODS. The application code generated by DODS ensures that every row has a value of OID that is unique within the database. Whenever a new row is added to a table, the application generates a unique Object ID to put in the OID column; it uses the OBJECTID table to keep track of the next Object ID to be assigned.
DODS application code uses the VERSION column in each table to ensure that the data that an application is updating is accurate. Because many users can be accessing the database simultaneously, a record can change between the time an application retrieves it when it attempts to change the record.
Every time an application updates a row, it increments the VERSION column in the database. The application qualifies updates on both the VERSION and OID columns--if it finds that there are no rows that have the expected values, then it knows that another process has changed the row it is trying to update, and it throws an exception. You can catch the exception in your application code to handle such situations appropriately.
To load the SQL scripts that DODS creates, follow these steps:
The file contains the SQL CREATE TABLE commands to create the PERSON, DISC, and OBJECTID tables:
create table person( login VARCHAR2(32) DEFAULT '' NOT NULL , password VARCHAR2(32) DEFAULT '' NOT NULL , firstname VARCHAR2(32) DEFAULT '' NOT NULL , lastname VARCHAR2(32) DEFAULT '' NOT NULL , oid DECIMAL(19,0) NOT NULL PRIMARY KEY, version INT NOT NULL);create table Disc ( title VARCHAR2(32) DEFAULT '' NOT NULL , artist VARCHAR2(32) DEFAULT '' NOT NULL , genre VARCHAR2(32) DEFAULT '' NOT NULL , owner DECIMAL(19,0) NOT NULL REFERENCES person ( oid ) ON DELETE CASCADE, isLiked INTEGER DEFAULT 0 NOT NULL , oid DECIMAL(19,0) NOT NULL PRIMARY KEY, version INT NOT NULL ); create table objectid ( next DECIMAL(19,0) NOT NULL );
Note: DODS may generate SQL files that are not fully compatible with your database server. You may have to edit the file manually to remove extraneous text that may be causing errors when reading the file. For example, for Oracle, you may have to remove extra blank lines.
You can configure many things about the SQL that DODS generates in the configuration file
<
enhydra_root>/dods/dods.conf
. For example, by default DODS generates C-style comments, but you can change the style of comments if your database requires a different format.
- Load the tables into the database. For example, the command for SQL*Plus is:
SQL> @<
enhydra_root>/examples/DiscRack/discRack/data/create_tables.sql
- Add some dummy data to the database for testing purposes. The file
<enhydra_root>/doc/GettingStarted/samples/tutorial_insert.sql
contains some sample data, including one person and several discs. Here is the command for SQL*Plus:
SQL> @/<enhydra_root>/doc/GettingStarted/samples/tutorial_insert.sqlUsing the DODS Data Objects
Now all you need to do is modify the business object, SimpleDiscList, to use the DODS data objects instead of the simplified one you created previously. Replace your old
SimpleDiscList.java
with the file<
enhydra_root>/doc/GettingStarted/samples/SimpleDiscList.java
.The main difference between the old and new objects is in the getDiscList() method; here is the heart of it:
...try {
DiscDO[] discArray;
DiscQuery dquery = new DiscQuery();
discArray = dquery.getDOArray();
String result[][] = new String[4][discArray.length]; for(int i=0; i< discArray.length; i++) {
result[0][i] = (String)discArray[i].getTitle();
result[1][i] = (String)discArray[i].getArtist();
result[2][i] = (String)discArray[i].getGenre();
result[3][i] = discArray[i].getIsLiked() ? "Yes" : "No";
return result;
...
This code uses the DiscQuery and DiscDO objects in the data.disc package to get data from the database. DiscQuery provides a set of methods for querying the DISC table; by default it performs the equivalent of SELECT * FROM DISC. It has methods that you can use to qualify the query (the WHERE clause of the SELECT statement) and order the result set. The getDOArray() method returns an array of DiscDO objects returned from the query.
The DiscDO object is the basic data object representing a row of data from the DISC table. It has getter and setter methods for each column in the table. The above code only uses the getter methods getTitle(), getArtist(), getGenre(), and getIsLiked(), which returns a boolean value. All of them except getIsLiked() return a string, so the method performs some simple logic to translate the boolean value to the appropriate string.
The last step is to recompile the project, by running make at the top level. Then start the multiserver with the start command, and load
http://localhost:9000
in your browser.When you access the Simple page, you should see the following in your browser:
![]()
If you don't see this page, check the following:
- Look in the
discRack.conf
file in theoutput
directory to be sure that the database settings are correctly listed.
- Check the output displayed in the shell window when you start the Multiserver for errors. If the database settings are in
discRack.conf
and the JDBC driver is in the application'sCLASSPATH
, there should be no errors listed when multiserver starts.
- Re-run the JDBC connection test to verify that the database is correct and JDBC is working.
- Try putting the wrong password into the application configuration file. Multiserver should start, but the application will return an SQL exception and a stack trace.
- Make sure you do not have any extraneous Java VMs running. Sometimes, the classloader can fail to find the correct classes if it picks up an old
CLASSPATH
from a running VM.
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 |