The purpose of this document is to get you up
to speed quickly with DODS.
Introduction
The Data Object Design Studio (DODS) is a software development tool. DODS is an
object-oriented design tool that generates the code most developers would rather not write
the old-fashioned way.
DODS allows you to design the data-layer classes that comprise the foundation of an
application that will utilize the Enhydra application framework. Then, DODS will generate
the Java source code for those data-layer classes, and compile them for you.
DODS Projects
In DODS, you create a Data Object entity for each data-layer class needed by your
application. Each Data Object contains Attributes.
An Attribute describes either a piece of data (e.g. int, String, Date) or a reference
to another Data Object. By using reference Attributes, you create an interconnected
hierarchy of Data Objects.
A collection of Data Object specifications is stored as a Project file. You can reopen
a Project file and make changes to your Data Objects.
Code Generation
When you are happy with the Data Objects you have designed, DODS will generate the
source code to implement them. Using DODS to design Data Objects is easy, and the steps
are described in later sections. The purpose of DODS is to generate Java and SQL code for
you. As an simple example, you could create a Data Object to represent a dog with the
Attribute 'name' and the Attribute 'age'. For the 'Dog' Data Object, the following source
code would be generated:
Many-to-One Relationships
For Data Objects that are referenced by other Data Objects, the BDO class will also
contain convenience lookup methods to retrieve those associated objects.
Many-to-Many Relationships
A Data Object that contains references to two other types of Data Objects is used to
implement a many-to-many mapping between objects of those two types. Data Objects involved
in a many-to-many relationship will contain convenience methods to retrieve associated
objects, and methods to map and unmap associations between objects.
Note: The BDO classes are an artifact of earlier projects executed at Lutris
Technologies. The functionality of the BDO classes could (and probably will) be folded
into the DO classes in a later release.
Such changes in the code that DODS generates are accomplished by changing the source
code templates that DODS uses. See the dods.conf file for details. Also see the Enhydra
mailing list for messages posted by developers who have altered the templates toward their
own designs.
Package Structure
When DODS generates your source code, it creates a directory structure that matches the
package hierarchy you have designed. DODS creates the Makefiles in each directory, and
runs 'make' on the generated source code.
The source tree layout for a typical Enhydra application can be seen by running the
'newapp' utility. For example, the commands
cd /tmp
newapp myApp
will produce the following directory structure:
/tmp/
myApp/
myApp/
presentation/
business/
data/
All the application code would be belong in a Java package named "myApp".
DODS would be used to design classes to go in the package "myApp.data".
NOTE: In DODS, each XxxDO class should be created in the package "myApp.data"
(or a sub-package).
When you specify the directory for DODS to write the generated code, it attempts to
match the hierarchy of packages you have created against the hierarchy of directories
above the target directory you specified. If they match, DODS adjusts the Makefiles it
generates to integrate with your application; otherwise, each time you adjust your DODS
project file and regenerate the code and Makefiles, you must perform this integration by
hand.
Using the Generated Code
After you create a database using the .sql files, you can code your application
business objects to use your Data Objects. You can then code your presentation objects to
use your business objects. Here are some examples of using the classes generated by DODS
NOTE: Run 'make javadoc' on the generated source code to see more examples. Also see
javadoc for the QueryBuilder class.
Create a new 'dog' row in the database for a dog named 'Bob':
DogBDO bob = DogBDO.createVirgin();
bob.setName( "Bob" );
bob.setAge( 1 );
bob.commit();
The servlet composes an HTML page containing a SELECT using bobName as option text and
using bobHandle as the OPTION VALUE, and containing an INPUT field labeled
"Age". It then sends that HTML to the vet's browser. The vet wants to change
Bob's age, and so enters the new age, chooses the option labeled "Bob", and
submits the form to servlet B. Servlet B uses the handle VALUE of the selected OPTION to
instantiate a DogBDO to represent Bob's row in the dog table. It then sets Bob's new age,
and updates the row.
String bobName = bob.getName();
String bobHandle = bob.getHandle();
String dogHandle = getOptionFromRequest();
String newAge = getAgeFromRequest(); // get value of "Age" INPUT field from
request parameter
DogBDO selectedDog = DogBDO.createExisting( dogHandle );
selectedDog.setAge( newAge );
selectedDog.commit();
Where the methods getOptionFromRequest( ) gets the attribute VALUE of chosen
OPTION from request parameter and getAgeFromRequest( ) gets the value of "Age"
INPUT field from request parameter.
Performing a query
From another HTML page, the vet searches for dogs by name. Servlet C is passed the name
submitted from the HTML page, and performs a search of the dog table for dogs with that
name.
String dogName = // get INPUT name from request parameter
DogQuery q = new DogQuery();
q.setQueryName( dogName );
DogBDO selectedDog;
while ( null != ( selectedDog = q.getNextBDO() ) )
debugMsg( dogName + "'s age is " + selectedDog.getAge() );
Performing a more complex query
Say you want to print any dogs named "Fido" that are 2, 3 or 4 years old. Use
the Query class as usual, but access its QueryBuilder object to refine the query.
DogQuery dq = new DogQuery();
dq.setQueryName( "Fido" );
QueryBuilder qb = dq.getQueryBuilder();
qb.addWhere( DogDO.age, 2, QueryBuilder.GREATER_OR_EQUAL );
qb.addWhere( DogDO.age, 4, QueryBuilder.LESS_OR_EQUAL );
qb.addOrderByColumn( DogDO.age );
// print SQL "select" statement before execution,
// compare against next example
qb.debug();
DogBDO[] youngFidos = dq.getBDOArray();
for ( int i = 0; i < youngFidos.length; i++ ) {
DogBDO dog = youngFidos[i];
print( dog.getName() + " is " +
dog.getAge() + " years old." );
}
Performing the same query using just the QueryBuilder. When you are simply generating a
data report (especially one that joins multiple tables) it is sometimes simplest to use
the QueryBuilder directly.
Vector fields = new Vector(); // bind set of desired columns
fields.addElement( DogDO.name );
fields.addElement( DogDO.age );
QueryBuilder qb = new QueryBuilder( fields );
qb.addWhere( DogDO.name, "Fido" );
qb.addWhere( DogDO.age, 2, QueryBuilder.GREATER_OR_EQUAL );
qb.addWhere( DogDO.age, 4, QueryBuilder.LESS_OR_EQUAL );
qb.addOrderByColumn( DogDO.age, "ASCENDING" );
// print SQL "select" statement before execution,
// compare against previous example
qb.debug();
RDBRow row;
while ( null != ( row = qb.getNextRow() ) ) {
print( row.get( DogDO.name ).getString() + " is " +
row.get( DogDO.age ).getInteger() + " years old." );
}
Using RDBColumn and RDBRow
Note that the column names are not expressed as hard-coded strings; instead the static
final RDBColumn members of the DogDO class are used. This protects the developer against
problems caused when the DODS GUI is later used to make changes to the 'dog' table.
Examples:
- Another developer changes the column name in the table. The above example query would
continue to compile and execute correctly. Whereas, hard-coded strings would cause a
run-time error which would require fixing.
- Another developer removes the 'age' column from the 'dog' table. The example query would
no longer compile, reminding the developer precisely what change is needed in the
application code. Whereas, hard-coded strings would cause a run-time error, and more
difficult maintenance.
QueryBuilder supports other where-clause syntax (see addWhereOpenParen and addWhereOr),
including subqueries (see addWhereIn.)
Extending a DODS-generated Query class to provide a convenient interface
for performing the same query as above:
class AdvancedDogQuery extends PersonQuery {
void setQueryAgeRange( int min, int max ) {
QueryBuilder qb = dq.getQueryBuilder();
qb.addWhere( DogDO.age, 2, QueryBuilder.GREATER_OR_EQUAL );
qb.addWhere( DogDO.age, 4, QueryBuilder.LESS_OR_EQUAL );
qb.addOrderByColumn( DogDO.age );
}
}
// using the extended query class
AdvancedDogQuery dq = new AdvancedDogQuery();
dq.setQueryName( "Fido" );
ad.setQueryAgeRange( 2, 4 );
DogBDO[] youngFidos = dq.getBDOArray();
for ( int i = 0; i < youngFidos.length; i++ ) {
DogBDO dog = youngFidos[i];
print( dog.getName() + " is " +
dog.getAge() + " years old." );
}
Using DODS
There are three different panels in DODS: There is a Graphical View on top, and below
that, there is a Package/Object Tree and an Attribute Table. The Graphical View shows the
hierarchy of the DataObject you've created. For instance, Customer extends Person, etc.
The Tree is a directory structure of the packages which hold the Data Objects which
will be created by DODS. When the project is finished and built, this directory structure
will be created with the appropriate .java files in each directory corresponding to the
Data Objects in those packages.
The Table is used to display the attributes of a DataObject. When a DataObject is
selected in either the Tree or the Graphical View, it's Attributes are listed in the
table. For instance, a Customer might have a phone number, Address, and probably a name.
Each of these attributes would be listed in the table when the Customer object is
selected.
Example
Run DODS by executing the command output/bin/dods. As a simple example collection of
Data Objects, let's use Customers, Stores and Store Employees.
Step 1: Creating the package hierarchy
First, we are going to need packages for these data objects. In the Tree panel is a
folder icon for the default package named "root". Click the "root"
package icon to select it. Click Edit-->Package to bring up a dialog box, change the
name to "myApp", and click Ok. Now let's create sub-packages. Choose
Insert-->Package; A dialog will pop up asking for a name, and also displaying a tree to
choose the package that this new package will be in. Currently the only package to put it
in is "myApp." Type in the name "data" and hit OK. This creates a new
package folder on the Tree.
This arrangement of packages fits into the package hierarchy generated by the 'newapp'
utility for an application named 'myApp'. Remember: Java package names start with
lowercase, Java class names start with uppercase.)
Step 2: Creating the data objects
Create the example Person Data Object
Since Customer and Employees are both types of people, let's create a base class named
Person to contain the common attributes.
Click (select) the "data" package in the Tree, and click Insert-->Data
Object.
The Data Object Editor dialog appears. Set the name to "Person". Leave the
"Extends" set to "Nothing"; the Person class does not extend any other
classes in the Tree.
Click the Package tab and make sure the new "Person" Data Object will be
placed in the "myApp.data" package.
Click the Database tab and set the Table Name to "person". For now, ignore
the other settings.
Click OK at the bottom of the dialog, and the "Person" Data Object appears in
the Graphical View and in the Tree.
The "Person" Data Object is represented in the Tree as a blue circle icon. If
you need to adjust the settings for a package or Data Object, you can double-click it's
icon to bring up the appropriate Editor dialog.
A Person will have a name. In DODS, "name" will be an Attribute of the
"Person" Data Object. When DODS generates the PersonDO.java class for you,
"name" will be a data member, and will have get and set methods. In the
PersonSQL.sql file that DODS generates, "name" will be a column specified in the
"person" table.
To add the "name" Attribute to Person, select Person in the Tree, and click
Insert-->Attribute. The Attribute Editor dialog appears.
Under the General tab, set the Name of the Attribute to "name".
Under the Java tab, set the Java Type to String. You may provide an initial value for
the "name" Attribute, but leaving it blank is fine. You may also provide Javadoc
text, which will appear in the PersonDO.java file that DODS will generate.
Under the Database tab, set the dB Type to VARCHAR (a likely database type for storing
Strings.) Here you can specify whether you want the database table "Person" to
be indexed by the "name" column, whether the column can contain a NULL value,
and whether you intend to search the table by "name" values. Since searching by
name is often useful, check the "Can be queried" checkbox.
If all Persons were to have the same name, you set the "name" value to be
constant; the "name" data member in the PersonDO class would be
"final", and the "person" database table would have no
"name" column. Ignore the "Referenced DO" checkbox for now.
Click OK at the bottom of the Attribute Editor dialog to add the "name"
Attribute to the "Person" Data Object. In the Table panel the "name"
Attribute now appears. The Table columns marked with colored glyphs indicate the
checkboxes chosen in the Attribute Editor. Note that because you checked the "Can be
queried" checkbox under the Database tab, a 'Q' glyph is shown for this Attribute.
These glyphs are for your quick reference, and clicking their boxes is a quick way of
toggling the settings for an Attribute. You can move your mouse over each glyph heading in
the Table to see a reminder of the Attribute setting each glyph represents. When you have
a Data Object with many Attributes, you can sort the Attributes in the Table by clicking
on a heading glyph.
Go ahead and create a Store object as well, and give it a name attribute.
In our example, there will be two kinds (subclasses) of Person: Customers and
Employees.
Create the example Employee Data Object by extending Person
Select Person in the Tree and click Insert-->Data Object. The Data Object Editor
appears. Enter "Employee" for the name of the new Data Object. In the
"Extends" menu, select "myApp.data.PersonDO."
Click OK at the bottom of the Editor dialog.
In the Tree panel, the circle icon for Person has changed from blue to yellow; this
indicates that Person is now an abstract class.
NOTE: In DODS, Data Objects which are extended must be abstract. When a Data Object is
extended, DODS automatically marks it as abstract. This is due to the way Data Object
classes generated by DODS map to the database. Only non-abstract "leaf" classes
have tables in the database.
Note also that the blue circle icon for Employee is in the "myApp" package,
instead of the "myApp.data" package; we forgot to set the package for Employee
in the Editor. Simply drag the Employee icon to the "data" package folder icon.
Establish a one-to-many relationship between Store and Employee
Now let's establish the relationship between Stores and Employees. For our example, one
or more (i.e. many) Employees work for each Store, and each Employee works for only one
Store. To represent this, add an 'employer' Attribute to Employee. Click
Insert-->Attribute again and set the Attribute name to 'employer'. Under the Java tab,
set the type this Attribute to myApp.data.EmployeeDO.
Under the Database tab, check the "Referenced DO must exist" checkbox. This
will impose an integrity constraint in the database to ensure than a Customer's
accountManager is always a valid Employee.
Click OK. Now click on the Customer in the Graphical View panel. An arrow appears,
pointing from Customer to Employee; this indicates the object reference you just created.
DODS will create a StoreBDO.getEmployeeDOArray() method to retrieve an array of
EmployeeDO objects that reference a given StoreDO.
Create the example Customer Data Object and establish a many-to-many relationship to
Stores
Create a Customer object that extends Person. Add an accountNumber Attribute to the
Customer Data Object; select the Customer icon and then click Insert-->Attribute. Set
the Attribute name to "accountNumber"; under the Java tab, make it an integer;
under the Database tab, check "Can be queried."
For our example application, let's say we want to be able to know which Customers visit
each Store, and also which Stores are visited by each Customer.
This is a many-to-many relationship: each Customer visits many Stores, and each Store is
visited by many Customers. This relationship cannot be established simply be giving
Customer a reference Attribute to Store, and vice versa; such an arrangement would allow
the database to show each Customer visiting only one Store, and each Store having just one
Customer. To represent a many-to-many relationship, we need to create a special
intermediary object.
Create an object named CustStore. Give it an Attribute named 'customer' that is a
reference to CustomerDO. Give it another Attribute named 'store' that is a reference to
StoreDO. Each CustStore object instance in the database will show that a given Customer
visits a given Store.
DODS will create a CustomerBDO.getStoreDOArray_via_CustStore() method that returns an
array of StoreDO objects referenced by a given CustomerDO. DODS will also create a
StoreBDO.getCustomerDOArray_via_CustStore() method that returns an array of CustomerDO
objects referenced by a given StoreDO. See the generated code for other handy methods.
Step 3: Saving your project
That pretty much explains everything about creating a set of Data Objects. You should
now save your project. There are several other Options on the menus.
You can edit Packages, Data Objects, and Attributes that already exist by selecting
them and choosing Edit-->...
You can delete packages, data objects and attributes, but the undo function only saves
the last delete that has been performed.
Step 4: Building your project
Click Build
Finally, when you are ready to build all of the code for the Data Objects, click
File-->Build All. This option is available if there are no errors in the Objects you
have created. If there is an error somewhere, you can choose Edit-->Find Next Error,
and it will take you to the Data Object which has an error. Errors show up in the Table
with a red exclamation mark. Click on the exclamation mark to see what the error is.
If you have not saved your project before, the Save Project dialog will appear. Enter
the path and name for your project file and click OK.
Next you will see a dialog labeled Destory and Create New DODS Directory.
For this example you would navigate to the directory
/tmp/myApp/myApp
and select the subdirectory
data/
Or, type /tmp/myApp/myApp/data.
WARNING:Always verify the path you enter is correct. DODS removes everything in and
under that directory, and recreates it to match the current package structure of your
project.
Running with Parameters
The script that starts DODS
output/bin/dods
can be given optional parameters.
dods [ project file [ output directory [ regen ] ] ]
If you specify a project file, it will be opened automatically when DODS starts.
If you specify an output directory, it will be preselected when you click the Build
button.
If you provide "regen" as the third parameter, the DODS GUI will not appear,
but the named project file will be loaded, and the code generators will write source code
to the named output directory. The "regen" parameter is useful for running DODS
from a makefile to completely rebuild an application. The project file can be thought of
as the actual "source code" for the data layer of the application.
The Data Object Editor
The Database Tab (in the Data Object Editor dialog), has the following:
- Table Name: When entering a name for a database table, bear in mind the naming
restrictions of your chosen database vendor. For example, there may be a maximum length
for the name, the name cannot be an SQL reserved word, and there may be names that are
reserved for use by the database itself.
- Every Class: For future use.
- Every Concrete Class: For future use.
- Entire Hierarchy: For future use.
- Lazy Loading: This flag affects the behavior of the DO class (defined in the DO.java
file for the Data Object.) If checked, then when you supply a known ObjectId to create a
DO instance, the DO instance is created but the corresponding row in the table is not
retrieved until the first get or set method call is made. This is useful when you are
reconstituting a hierarchy of objects from rows in the table, but you will not always need
the data for this particular object in that hierarchy. Checking 'Lazy Loading' delays the
hit on the database until the moment the data is actually needed. Depending upon how your
application accesses its database, this can result in extra database hits, and hurt
performance. If not checked, then all the data members are retrieved from the table when
the DO instance is created. If your application will not always need these data members,
this can waste memory.
- Cached: This flag affects the behavior of the DO class. If checked, all DO instances are
stored in a cache inside the DO class. Subsequent queries of the table using the Query
class will first check the cache before hitting the database. This is appropriate when
different parts of your application hold references to the same DO instance.
WARNING: there are known design issues with the generated code regarding the cache
mechanism.
- Fully Cached: This flag affects the behavior of the DO class. If checked, the entire
table is queried and cached when your application starts. This is appropriate for tables
of "static" data which is accessed frequently and which will not change during
the execution of your application. Note that there is a refreshCache() method available if
your application needs to refresh the cache with new values in the table.
The Attribute Editor
The Database Tab (in the Attribute Editor dialog), has the following:
- dB Type: This pulldown menu holds a list of database column types that are valid for
storing the Java type chosen for this Attribute.
- Size: For some column types (e.g. CHAR, VARCHAR, etc.) a size can be specified for the
column.
- Is An Index: If checked, a database index is created on this column in the table.
- Can Be Null: If *not* checked, the "NOT NULL" restriction is placed on the
column.
- Can Be Queried: If checked, the Query class generated for this Data Object will include
a set method for this column to allow restricting the search.
- Referenced DO Must Exist: If checked and this Attribute is a reference to another Data
Object (i.e. the Java type for this Attribute is another DO) then a "REFERENCES"
restriction is placed on the column. This enforces referential integrity between the
tables for these two Data Objects.
- Value is Constant: If checked, the Attribute is made a constant data member in the DO
class, and no column is created in the table for this Data Object.
The Query Classes
The Query classes generated by DODS allow you to search the database for DOs.
Attributes of the DO which are marked "Can be queried" will have a method
setQueryAttributeName() in the Query class. These setQuery methods allow you to prune the
search results.
The Query classes generated by DODS all use the QueryBuilder helper class. The
setQueryAttributeName methods invoke the QueryBuilder.addWhereClause() method to construct
the SQL select command that performs the search. After creating an instance of a Query
class, you can obtain the associated instance of QueryBuilder to add extra clauses to the
select command. (Some developers elect to extend a Query class to implement new methods
for such extra functionality.) Using extra clauses, your select command can perform more
complex searches.
DB2 Note: The QueryBuilder.NOT_EQUALS qualifier will not work with DB2 databases. Use
the String "<>" instead.
Querying a View
It is possible to use DO/Query classes to access a view instead of a table.
Say you use DODS to create a DO named Person, and you want a view named 'count_people':
create view count_people (total) as select count(*) from person
You would create another DO named Count, specify its table name to be 'count_people',
and add an integer attribute named 'total', and mark that attribute as Can Be Queried.
Then click Build to let DODS generate the classes PersonDO, PersonBDO, PersonQuery,
PeopleSQL.sql CountDO, CountBDO, CountQuery, CountSQL.sql
Discard the CountSQL.sql file, and remove the 'create table count_people' statement
from the create_tables.sql file that DODS generated.
Use the create_tables.sql file to define your tables in your database. Then manually
create the count_people view (using the create view above.)
The CountQuery class can now be used just as you would any Query class. The CountDO
object returned by CountQuery will contain the 'total' you desire. NOTE: Only 'get'
methods should be called on the CountDO object.
Running with XWindows
Depending on the platform you may need to do some configuration work to get DODS to
display properly in an XWindows environment. Below are some instructions for getting DODS
to work via remote X Server using the JDK1.2. Linux and XVision users may also need to
install new font.properties files for Java 2.
If you're running DODS under Linux with JDK1.2, it should just work, provided your
fonts are world readable, and the directories containing them are executable. Blackdown's
prerelease JDK1.2 had permissions problems that are probably fixed in their more current
version.
If you're running either Linux or Windows, and you telnet to your Unix (Solaris/Linux)
Server and want to run DODS. You may run into some horrendous display issues if your
system is not configured properly.
If your workstation is a Linux system, you might notice that JDK1.2 does not support 16
bit color depths on a remote XFree86 Server. If you're running 16 bit color depth, you
probably want to change it down to 8 bits. Here's how I do this (On Red Hat, anyway - I
don't know how different versions of Linux/XFree86 are packaged). There might be a better
way, but I haven't found it:
On the system hosting DODS
- Copy font.properties.xfree86 provided with DODS to $JDKDIR/jre/lib/font.properties this
will get rid of font not found problems. (JDKDIR is the path to java1.2.)
- Make a backup copy of /etc/X11/XF86Config - DON'T LOSE THIS COPY
- Change the color depth level to 8.
- If you feel comfortable editing XF86Config by hand, go ahead and do that to adjust your
color depth to 8,
- If prefer to use a tool, Red Hat supplies XConfigurator - to use that, first you have to
shutdown your X Server (I do this by editing /etc/inittab, changing the default init level
to 3, and rebooting, but there are other ways)
- Run XConfigurator, change your bpp (bits-per-pixel) to 8. (if you have problems here,
you can restore your backup XF86Config file)
- Restart your X Server (set default init level back to 5) . You're done.
Windows XVision Users
If your workstation is a Windows system running XVision, then you may notice Java
dumping core quite heinously when trying to run DODS. Then XVision will give you some
suggestions about configuration changes you can make to fix this problem. So, you want to
do two things:
- On the UNIX system, copy font.properties.xvision to $JDKDIR/jre/lib/font.properties,
this will get rid of font not found problems.
- On the Windows system, go into the Color tab of XVision properties, click the radio
button that allows the user to select the color properties, then select PseudoColor and
uncheck the Windows
System Colors.
These problems are probably not particular to DODS, but more to differences between
JDK1.1.7 and 1.2. If you don't have any problems when trying to run DODS remotely, there's
no reason to make these
configuration changes. It should be made clear that these changes are to be made only if
you're running Java applications like DODS via remote X server, and are having problems.
If you still have font not found warnings after replacing the font.properties file, you
can make a choice to either live with DODS without the fonts that Java can't find, or try
to fix the problem yourself. You can somewhat easily edit font.properties using sed.
For instance, if I encountered font not found warnings on the Java console, I would
telnet to the UNIX box and do the following:
- Enter xlsfonts to find out which fonts my X server can use
- For each font not found (you'll see a lot of warnings, but usually two, maybe three
unfound fonts mentioned over and over again), pick a new font from the list given by xlsfonts.
- cat font.properties | sed -e 's///' > font.properties.NEW
Where is a font selected from xlsfonts, formatted in the same manner as the other fonts in
the file (with \* instead of real metrics, \%d for the point size, etc -
reading font.properties will give you an idea of what it should look like)
- cp font.properties font.properties.ORIG;
mv font.properties.NEW font.properties
With this process, it shouldn't take one too long to generate a font.properties
that their system can use. It's especially important to keep that original font.properties
file if you're not familiar with either sed or the X font structure - you can get into a
mess pretty quick.
Note: In some cases dialogs may appear blank when you open them ununder XWindows. If
this happens, you can resize the dialog to force the controls to appear.
|