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.
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.
Each non-constant Attribute in a Data Object is represented by a
column in the table definition generated for that object. In addition,
each generated table definition contains two columns used by the Enhydra
database framework: 'oid' and 'version'.
// method to instantiate a new 'dog' object not yet in the
DB
static DogDO createVirgin() { ... }
// methods to retrieve a 'dog' object from the 'dog' table
// using various forms of the primary key
static DogDO createExisting(String handle) { ... }
static DogDO createExisting(BigDecimal handle) { ... }
static DogDO createExisting(ObjectId handle) { ... }
// method returning the primary key value
// for this virgin/existing object
String getHandle() { ... }
// get/set methods for each Attribute of 'dog'
String getName() { ... }
void setName(String newValue) { ... }
int getAge() { ... }
void setAge(String newValue) { ... }
// methods to retrieve rows from database
DogBDO getNextBDO(); // retrieve one row at a time
DogBDO[] getBDOArray(); // retrieve all rows at once
class DogBDO {
// methods to retrieve a 'dog' object from the 'dog' table
// using various forms of the primary key
static DogDO createExisting(String handle) { ... }
static DogDO createExisting(BigDecimal handle) { ... }
static DogDO createExisting(ObjectId handle) { ... }
// method returning the primary key value
// for this virgin/existing object
String getHandle() { ... }
// get/set methods for each Attribute of 'dog'
String getName() { ... }
void setName(String newValue) { ... }
int getAge() { ... }
void setAge(String newValue) { ... }
// commit() inserts this virgin object into the DB as a new
row,
// or updates the existing row with changes made with setXxx
void commit() { ... }
// delete() removes the DB row represented by this object
void delete() { ... }
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.
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:
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.
Create a new 'dog' row in the database for a dog named 'Bob':
DogBDO bob = DogBDO.createVirgin();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.
bob.setName( "Bob" );
bob.setAge( 1 );
bob.commit();
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.
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() );
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." );
}
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." );
}
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.
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.)
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 releationship 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.
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.
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 exclaimation mark. Click on the exclaimation 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.
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 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.
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.
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:
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 encounterred font not found warnings on the Java console, I would telnet to the Unix box and do the following:
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 under XWindows. If this happens, you can resize the dialog to force the controls to appear.