Ozone Users Guide

Falko Braeutigam

Gerd Mueller

Per Nyfelt

Leo Mekenkamp

Ozone Documentation License, Version 1

This document is free software; you can redistribute it and/or modify it provided that the terms of the GNU Library General Public License as published by the Free Software Foundation version 2 of the License; and the following terms are met.

The Ozone Database Project <ozone@ozone-db.org>

Included in the ozone distribution is code and documentation made available by other copyright holders and under different licenses. All these licenses allow worldwide, royalty free distribution, whether alone or as part of a larger product. License, copyright and disclaimer of this software is included in this directory.

The document is Copyright (C) 1997-2003 by SMB GmbH, Rohrteichstr. 18, 04347 Leipzig, Germany, All rights reserved.

You must give prominent notice with each copy of the work that the document is used in it and that its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License.

The name ozone must not be used to endorse or promote software products derived from this software without prior written permission of SMB.

Software products derived from this document may not be called ozone nor may ozone appear in their names without prior written permission of SMB.

This document is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details.


Table of Contents

Introduction
About Ozone
Background
Basics concepts
When to use Ozone
Persistence
Forget about relational data
Forget about persistance
Quick start
Interfaces and implementations
Factories
main program
Back to persistency
A bit of theory
adapting the code for use with Ozone
Ozone Post Processor
Tutorial
Overview
Basics
The Car interface
The CarImpl class
OPP and Proxy objects
Step by step
A simple client application
The "Garage" interface and "GarageImpl" class
The GarageApp program
Summary
The various types of databases
Creating and storing objects

The Ozone Database Project is a java based Object Server that makes it possible to execute persistent Java objects in a transactional environment.

Ozone is an Open Source project distributed under the using the LGPL license (GNU Library General Public License). This basically means you can use it freely in both commercial and non-commercial situations and also build on top of the code and include Ozone in your project without having to pay royalties to anyone.

The Ozone Core however, is under the GPL license ensuring that the Ozone product itself is not used as base for a commecial closed sorce OODB based on Ozone.

The main goal of Ozone is to add persistence and transactions to the java object model in a transparent manner. Ozone applications should look and work like ordinary java applications. Methods on these persistent Objects should be able to be invoked on the server side making Ozone an applicaiton server, not just an object storage.

Ozone does not introduce a new type system, object or computation model, or query language like relational DBMSs and ODMG/OQL does. Ozone just lets you work with persistent Java objects in a transactional environment. Ozone is Java! No other types than Java types, no other query/update language than Java, no impedance mismatch!

The Ozone project aims to evolve an object server that allows developers to build pure object-oriented, Java based, persistent applications. I.e. you can create your Java objects and let them run in a transactional database environment without any split between data model and object model. This makes it different from other application server environments e.g. EJB, which assumes that data (often stored in a relational database) is separate from the object (entity bean). EJB's also differentiate between data objects (entity beans) and objects performing business logic (session beans). Ozone objects are just one type but can act like entity beans or session beans if you choose to design them that way but there is no database other than Ozone itself behind the scenes.

Besides Java, Ozone also have an extended XML support making it an excellent XML data repository, especially when the XML manipulation you want to do can be created as Ozone objects inside the server. Ozone includes a fully W3C compliant DOM implementation that allows you to store XML data. You can use any XML tool to provide and access these data. Support classes for Apache Xerces-J and Xalan-J are included. Work is also underway to support the XML:DB api for XML databases.

Most programmers these days are familiar with relational databases (rdbms-es). These databases really shine when they are used the way they were designed: as stand-alone applications. SQL is also nice when it is used for the purpose it was designed for: human-rdbms communication. If an SQL-frontend (maybe with the assistance of a gui for naive end-users) connected to a rdbms is all you need, then you do not need Ozone. In fact, it is probably a bad idea to use Ozone in such situations.

In which situations then would the use of Ozone be appropriate? Simple: in almost all other situations.

Before expanding on this bold statement, let us first go back to the rdbms. As said, the rdbms/SQL combo is designed for human-computer interaction. Nowadays however a lot of applications interact with an rdbms using an SQL interface. So we have computers talking to computers while they use a language that was created for humans. If this does not strike you as odd then I suggest communicating to your friends and relatives in hexadecimals, or rather, in the screeching noise you hear when you by accident get a fax-machine on the phone. Besides the odd feeling there are mayor consequences to this hack-on approach of using rdbms-es, a few of which are:

Although the situation has in recent years improved, these problems have merely been moved out of sight from the average developer. Technologies like Apache Torque and Suns EJBs are still hacks to get around some of the limitations of rdbms-es.

Object databases are a relatively new kind of storage systems; they behave different than their relational or hierarchical cousins. Most importantly, they do not need any of the hacks relational systems need to be integrated into an application. Ozone is an object database that mostly stands out because of its proxy-based object references and because it is Free (as in 'beer' as well as in 'speech'; visit www.gnu.org for more about Free Software).

Lets go back to the bold statement of using Ozone almost everywhere: It is generally considered good pratice to separate business logic from visualisation and persistance logic. Everywhere where you use a business layer of Java objects you can use Ozone. Ozone provides an almost extremist view on business logic vs. persistance logic separation. So extreme even, that it might come as a surprise to 'rdbms brainwashed' developers: just like the little bald boy in the movie Matrix stated that "to be able to bend a spoon you must first understand that there is no spoon", this guide states: to be able to write persistent application you must first understand that there is no persistence. Getting into the "Tao of Ozone" first and foremost means letting go of some things.

Ozone is for persistence as what RMI is for remote object communication: you know its there and you know the principles of how it works ( well, soon you will know at least:) ) but you do not really have to think much different about it compared to "normal" java code.


Please note that this document is not complete and describes features only available in the current development version of the next version of Ozone; these features might change before the final release of the next version.

“When a superior man hears of the Tao, he immediately begins to embody it. When an average man hears of the Tao, he half believes it, half doubts it. When a foolish man hears of the Tao, he laughs out loud. If he didn't laugh, it wouldn't be the Tao.” - ch. 41 Tao Te Ching

This guide is the result of the knowledge we gained from developing with Ozone. As in any programming environment, you can make a lot of errors, there are pitfalls, and performance issues. Having read this you might ponder and think wether or not it might be worthwhile to put the energy in bringing yourself closer to the Tao of Ozone. There is only one answer to that question: “yes, it is worthwhile”.

Ozone is a wonderful piece of Free software. Read this guide carefully, follow the excercises and implement the guidelines. In the beginning you might feel like Neo after having swallowed the red pill, but in the end you could find yourself stopping bullets in mid-air.

As most people benefit and learn most from looking at examples and doing excersizes, the remainder of this guide will have a lot of them. Instead of using all sorts of UML diagrams the main focus is on java source code; Ozone is a 100% java product and can only interface with other java programs.

Say we want to write an address book in Java for both companies and people that might be working for those companies. To do that we start by defining 3 interfaces. We use interfaces rather than classes, because we want to have a fully pluggable and open application and in order to do that, we want to use interfaces where possible. Compare this to how J2EE is set up: Sun defines the interfaces and other manufacturers can create implementations; Sun of course can create implementations as well. So we define the following classes:

        package org.ozoneDB.samples.AddressBook;

        interface Entity {
            public String getName();
            public void setName(String name);
            public String getAddress();
            public void setAddress(String address);
        }


        package org.ozoneDB.samples.AddressBook;

        import java.util.Iterator;        
        
        interface Person extends Entity {
            /** Returns an Iterator over the Company instances this person works for. */
            public Iterator employerIterator();
            public boolean hasEmployer(Company company);
            public void addEmployer(Company company);
            public void removeEmployer(Company company);
        }


        package org.ozoneDB.samples.AddressBook;

        import java.util.Iterator;

        interface Company extends Entity {
            public Iterator employeeIterator();
            public boolean hasEmployee(Employee employee);
            public void addEmployee(Employee employee);
            public void removeEmployee(Employee employee);
        }
	

The iterator returned by Company.employeeIterator() of course returns an iterator that iterates over all the employees that that company employs. Now that we have these interfaces, we also need classes implementing them. That should not prove to be too much of a hassle. The only thing to keep in mind is that if you add a Person to a Company then that Company must also be automatically be added to that Person; it is a two way link. The easiest but not the fastest in execution time to do this is something like

        Collection employers = new TreeSet();
        public void addEmployer(Company employer) {
            employers.add(employer);
            // test to avoid endless recursion
            if (!company.hasEmployee(this)) {
                company.addEmployee(this)
            }
        }
	
excersize: write an abstract class EntityImpl which implements Entity; then write classes PersonImpl and CompanyImpl implementing interfaces Person and Company and both extending EntityImpl.

Because we have a fully pluggable architecture, we also need a pluggable way to create objects. Simply calling a constructor would not do the trick, because then the name of the implementing class would be hard-coded into our application. To avoid this we make use of a Factory Pattern:

        public class PersonImplFactory {

            public static PersonImplFactory getInstance() {
                return new PersonImplFactory();
            }

            public PersonImplFactory() {
            }

            public Person create() {
                return new PersonImpl();
            }
        }
	
Our application does not (yet) at this stage support other factories than the default one. Adding functionality for factories from other implementations is rather trivial and falls outside the scope of this guide, so we do not provide an implementation for that. exercise: create a factory class for Company/CompanyImpl, just like PersonFactory is for Person/PersonImpl

A few mayor building blocks of our address book application are now ready. We have defined interfaces for persons and companies, written implementations for those interfaces, and have created factories to create instances of those implementations. Now we want to bring them to their first use. For that we use the following little program:

        package org.ozoneDB.samples.AddressBook;

        import java.util.Collection;
        import java.util.TreeSet;

        public class FirstTest {

            private static String[] cNames = new String[] {
                "The Beatles",
                "Rolling Stones",
                "U2",
            };

            private static String[][] pNames = new String[][] {
                new String[] { "John", "Paul", "George", "Ringo" },
                new String[] { "Mick", "Keith", "Charlie", "Ronnie" },
                new String[] { "Bono", "Edge", "Larry", "Adam" },
            };

            public FirstTest() {
                Collection companies = new TreeSet();
                Collection persons = new TreeSet();

                createStructure(companies, persons);
                displayStructure(companies, persons);
            }

            private void createStructure(Collection companies, Collection persons) {
                PersonImplFactory personFactory = PersonImplFactory.getInstance();
                CompanyImplFactory companyFactory = CompanyImplFactory.getInstance();
                for (int i = 0; i < pNames.length; i++) {
                    Company company = companyFactory.create();
                    company.setName(cNames[i]);
                    companies.add(company);
                    for (j = 0; j < pNames[i].length; j++) {
                        Person person = personFactory.create();
                        person.setName(pNames[i][j]);
                        company.addEmployee(person);
                        persons.add(person);
                    }
                }
            }

            private void displayStructure(Collection companies, Collection persons) {
                System.out.println("There are " + companies.size() + " companies:");
                for (Iterator i = companies.iterator; i.hasNext() ) {
                    Company company = (Company) i.next();
                    System.out.print(company.getName() + ", employee(s): ");
                    for (Iterator j = company.employeeIterator(); j.hasNext() ) {
                        Person person = (Person) j.next();
                        System.out.print(person.getName + " ");
                    }
                    System.out.println();
                }

                System.out.println("There are " + persons.size() + " persons:");
                for (Iterator i = persons.iterator; i.hasNext() ) {
                    Persons person = (Person) i.next();
                    System.out.print(person.getName() + ", employer(s): ");
                    for (Iterator j = person.employerIterator(); j.hasNext() ) {
                        Company company = (Company) j.next();
                        System.out.print(company.getName + " ");
                    }
                    System.out.println();
                }
            }

            public static void main(String[] args) {
                new FirstTest();
            }
        }
        
exercise: run this application and be sure you understand each and every part of it. Since the sixties popmusic has really been big business. But anyway, it is time to see if you can be chocked again. Here goes: we have now layed down the foundations for an address book application that uses Ozone as a database. At this point I can think of three words that might be the start of the question that popped up in your mind after that last statement. These three words are: 1) where 2) how 3) what. "Where" as in "Where did we do that?", "How" as in "How did we do that?". These questions should at this point not be asked. The only correct question starts with "What": "What is a database?". Remember we forgot all about persistency a while ago?

In order to make the example work with Ozone we have to change a few things.

And that's it. These are the only changes needed to ensure your objects are persistent in an Ozone database. Let's get down down and dirty on the sourcecode.
  1. change EntityImpl to have the following header:

    		import org.ozoneDB.OzoneObject;
    		public abstract class EntityImpl extends OzoneObject implements Entity {
    		
    As the other *Impl classes extend EntityImpl, they automatically extend OzoneObject, so there is no need to change them.

  2. change Entity to have the following header:

    		import org.ozoneDB.OzoneRemote;
    		public interface Entity extends OzoneRemote {
    		
    As with the *Impl classes, the other interfaces extend this one, so there is no need to change them.

  3. Change the method definition of setName() to this:

    		public void setName(String name); /*update*/
    		
    excersize: find out which methods change their object and 'mark' these on the same way as setName(); do this for all implementation classes.

  4. Do a search/replace (this -> self()) in the three *Impl files.

  5. OzoneRemote extends Serializable. That is a given fact. The only other type of class used as a parameter in method calls is java.lang.String, which also implements Serializable.

Now the source is ready to be used by Ozone; at least the source code you yourself have to write. There is more source needed, but that is taken care of by Ozone itself.


To avoid object replication Ozone uses a single-instance architecture. This means that, at all times, there is just one instance of a database object, which lives inside the database server. Such database objects are controlled from the client via Proxy objects.

Sounds slow, doesn't it? It isn't! In fact, it's the same way as EJB works -but easier to program and faster when executing ;) The fact that database objects are controlled via Proxy objects does not mean that each and every method call needs a roundtrip from the client to the server. This is because Proxies can be used to reference a database object from the client and from other database object inside the server. So, most (almost all) database method calls are done inside the server, which is very fast. But back to the step-by-step introduction.

Proxy object represent the actual database object - inside the client applications and inside other database object. A proxy object can be seen as a persistent reference. Proxy classes are automatically generated out of the database classes.

In most cases the Ozone server runs in a separate Java VM. The client applications connect the server via UNIX domain sockets. However, it is also possible to run the Ozone server and the client inside the same Java VM. See the section called “The various types of databases” for a more extensive description. The example of this tutorial works with a separate server.

To run the example of this tutorial you have to install the Ozone system properly and start an Ozone server. Installation is described in the INSTALL file, which can be found in the Ozone root directory.

Both, client and server need to access the database classes and the generated Proxy classes. The most simple way to accomplish this is to start the client and the server from the directory where the classes are located.

In order to work properly the client applications need several *.jar files, which come with the Ozone distribution, in their CLASSPATH. The easiest way to accomplish this is to start client application via the ojvm command instead of just calling java.

In our example we would like to store cars. So we need a class named Car. But we remember that all database objects are controlled by Proxy objects. So the first thing we do is to define the method interface of our Car objects. Only the methods of this interface can be used to access the underlying database object. To mark this interface as an external interface we inherit it from the OzoneRemote interface of Ozone.

            import org.ozoneDB.OzoneRemote;

            public interface Car extends OzoneRemote {

                public void setName( String name );		/*update*/

                public String name();

                public void setYearOfConst( int year );	/*update*/

                public int age();
            }
         

Please note the /*update*/ comment at the end of the set*() methods. Unfortunately in Java there is no way to determine methods that change the state of an object. But Ozone has to know these methods because such methods are update transactions instead of read transactions. So the programmer has to mark update methods explicitely. There are different ways to do so:

Our database objects will be instances of the CarImpl class. This class has to implement the Car interface. It extends OzoneObject to mark it as a database class. Here comes the code for the CarImpl class:

            import org.ozoneDB.OzoneObject;
            import java.util.*;

            public class CarImpl extends OzoneObject implements Car {

                /**
                Set version of the serialized data to make it compatible with
                new class versions.
                */
                final static long serialVersionUID = 1L;

                private String _name;

                private int	_yearOfConst;

                public CarImpl() {
                    _name = new String( "" );
                    _yearOfConst = 0;
                }

                public String name() {
                    return _name;
                }

                public void setName( String name ) {
                    _name = name;
                }

                public void setYearOfConst( int year ) {
                    _yearOfConst = year;
                }

                public int age() {
                    Calendar cal = Calendar.getInstance();
                    return cal.get (Calendar.YEAR) - _yearOfConst;
                }
            }

         

Now we can compile the interface and the class. You can use the following command line:

javac Car.java CarImpl.java

In the last step we create the proxy class for CarImpl. Proxy objects are the substitutes for the database objects in your application. Their job is to pass your method calls for database objects through the connection to the database. They have the same interface as the actual database objects and you can work with them as they were database objects.

To create the proxy class for CarImpl we use the following command line: opp CarImpl

OPP is the Ozone Post Processor. It creates the proxy class for a given database class. In our example it creates a file named Car_Proxy.class.

Note that the Ozone server and the OPP have to have access to the class files of all Ozone related classes of an application. If you are working with packages, you must add the corresponding directories to you CLASSPATH. In this example we do not use packages. If there is the ./ directory in your CLASSPATH you can complete our car example by just starting the Ozone server and the OPP in the directory of this example.

The client application will create a car, make some operations on it and finally delete it. Here is the code of the client application:

            import org.ozoneDB.*;

            public class MyApp {

                private static ExternalDatabase	db;

                public static void main( String[] args ) throws Exception {

                    if (args.length == 0) {
                        System.out.println( "usage: ojvm MyApp create|delete|print" );
                        System.exit( 1 );
                    }

                    // create and open a new database connection
                    db = ExternalDatabase.openDatabase( "ozonedb:remote://localhost:3333" );
                    System.out.println( "Connected ..." );

                    db.reloadClasses();

                    if (args[0].equals( "create" )) {
                        createCar();
                    } else if (args[0].equals( "delete" )) {
                        deleteCar();
                    } else {
                        printCar();
                    }

                    db.close();
                }

                public static void createCar() throws Exception {
                    // create a new Car object with the name "my_first_car"
                    // the return value is Car_proxy, which implements the Car-interface
                    Car car = (Car)(db.createObject( CarImpl.class.getName(), 0,
                            "my_first_car" ));

                    car.setName( "gottfried" );
                    car.setYearOfConst( 1957 );
                }

                public static void printCar() throws Exception {
                    Car car = (Car)(db.objectForName( "my_first_car" ));
                    if (car != null) {
                        System.out.println( "The car " + car.name() + " is "
                                + car.age() + " years old." );
                    } else {
                        System.out.println( "Object my_first_car not found." );
                    }
                }

                public static void deleteCar() throws Exception {
                    Car car = (Car)(db.objectForName( "my_first_car" ));
                    if (car != null) {
                        db.deleteObject( car );
                    } else {
                        System.out.println( "Object my_first_car not found." );
                    }
                }
            }
        

Compiling the program ist straight forward: javac MyApp.java But at first we have to start the Ozone server: ozone -d'your_ozone_data_directory' Then we run our small application: ojvm MyApp create

At first the program connects to the database on host "localhost" at port 3333. If everything was okay, we get a Connected output. The next step is to reload our database classes with reloadClasses(). This is necessary if we made some changes in our class code and the Ozone server was not restarted. In this case it holds the old class bytecode in its cache and it would not use our changed version.

Because we used the create option the program creates now a new database object of the type CarImpl and gives it the name my_first_car.

Please note that this name has no conjunction with the _name -member or the name()-method of CarImpl. This a database feature and it helps us to get our car back if we restart our small program.

The next to method calls assign the name of our car with "gottfried" and the year of contruction with 1957. Last but not least we close the connection and our program has finished.

The following command line starts the application: ojvm MyApp print The application connects to the server again and get our car back by using the objectForName-method. Then it prints the name and the age of our car. If everything was okay we get the following output: The car gottfried is 40 years old. Finally we close the connection.

We can delete our car object from the database by calling: ojvm MyApp delete When you have enough played with your first Ozone-application you can shutdown the Ozone-server simply by pressing q (return) in the Ozone-server terminal window or choose the shutdown command from the administration tool.

In the next sections we will extend out example by adding a garage for out cars.

Our cars need a garage. So we need to make a GarageImpl class and the corresponding interface. Note that opposit to the Car interface the update methods of Garage are not marked by an special comment but they are named after a regular expression pattern. In this example the pattern is .*_update. This pattern has to be specified to OPP when generating the proxy class.

        import org.ozoneDB.OzoneRemote;
        import java.util.*;

        public interface Garage extends OzoneRemote {

            public void addCar_update( String name, int yearOfConst ) throws Exception;

            public Car carForName( String name );

            public Car removeCar_update( String name );

            public Vector oldtimers();
        }
        

We will use a dictionary as the data structure for our garage. This dictionary contains all our cars identified by their name. The next step is clear: we have to write the implementation of our interface and we call this class GarageImpl.

        import org.ozoneDB.OzoneObject;
        import java.util.*;

        public class GarageImpl extends OzoneObject implements Garage {

            /**
            Set version of the serialized data to make it compatible with
            new class versions.
            */
            final static long serialVersionUID = 1L;

            private Hashtable	cars;

            public GarageImpl() {
                cars = new Hashtable();
            }

            public void addCar_update( String name, int yearOfConst ) throws Exception {
                Car car = (Car)database().createObject( CarImpl.class.getName() );
                car.setName( name );
                car.setYearOfConst( yearOfConst );
                cars.put( car.name(), car );
            }

            public Car carForName( String name ) {
                return (Car)cars.get( name );
            }

            public Car removeCar_update( String name ) {
                return (Car)cars.remove( name );
            }

            public Vector oldtimers() {
                Vector result = new Vector();
                for (Enumeration e=cars.elements(); e.hasMoreElements(); ) {
                    Car car = (Car)e.nextElement();
                    if (car.age() >= 20) {
                        result.add( car );
                    }
                }
                return result;
            }

            public void onDelete() throws Exception {
                for (Enumeration e=cars.elements(); e.hasMoreElements(); ) {
                    Car car = (Car)e.nextElement();
                    database().deleteObject( car );
                }
            }
        }
        

Most of the code above is selfexplaining. We add, get and remove cars identified by their name. It seems to be easy, but remember that the cars-list does not contain objects of the type CarImpl! It contains only proxy objects of the type Car_proxy. Each of these objects stands for a database object of the type CarImpl. Both of these types, CarImpl and Car_proxy, implements the Car-interface and the proxy object has the same behaviour as the corresponding database object. In Ozone you always work with proxy objects. The only exception of this rule is, you are standing in a method of a database object. In this case you have of course direct access to the object via the this-reference. Have a look at the oldtimers() method. The result bag the cars dictionary contain proxy objects and so the call to the age() method of the cars is an RMI call.

Finally we take a look at the onDelete-method. Every OzoneObject has this method and it is invoked when we delete this object from the database. This is similar to the destructor in C++. Our onDelete() should delete all the cars of the garage when we delete the garage itself. For this purpose we need a connection to the database so that we can call deleteObject(). The method database() gives us the right database proxy. Every OzoneObject has this database -method and with this link to the database you can call database methods like createObject() or deleteObject.

Now we have to compile the Garage interface and the GarageImpl class and we have to generate the proxy class. javac GarageImpl opp -p".*_update" GarageImpl Note the -p parameter. This specifies the pattern after which all update methods are named.

Our application does four things (depending from the command line arguments): create a new garage, delete the garage, add a new car and print all oldtimers. Here comes the code:

        import java.util.*;
        import org.ozoneDB.*;

        public class GarageApp {

            public static void main( String[] args ) throws Exception {

                if (args.length == 0) {
                    System.out.println( "usage: ojvm GarageApp create|delete|oldtimers" );
                    System.exit( 1 );
                }

                ExternalDatabase db = ExternalDatabase.openDatabase( "ozonedb:remote://localhost:3333" );
                System.out.println( "Connected ..." );

                db.reloadClasses();

                if (args[0].equals( "create" )) {
                    db.createObject( GarageImpl.class.getName(), 0, "my_garage" );

                } else if (args[0].equals( "delete" )) {
                    Garage garage = (Garage)(db.objectForName( "my_garage" ));
                    if (garage != null) {
                        db.deleteObject( garage );
                    }

                } else if (args[0].equals( "add" )) {
                    Garage garage = (Garage)(db.objectForName( "my_garage" ));
                    if (garage != null) {
                        garage.addCar_update( args[1], Integer.parseInt( args[2] ) );
                    } else {
                        System.out.println( "Garage object not found!" );
                    }

                } else if (args[0].equals( "oldtimer" )) {
                    Garage garage = (Garage)(db.objectForName( "my_garage" ));
                    if (garage != null) {
                        System.out.println( "Oldtimers:" );
                        Vector oldtimers = garage.oldtimers();
                        for ( int i = 0; i < oldtimers.size();  i++ ) {
                            Car car = (Car)(oldtimers.elementAt( i ));
                            System.out.println( "    Car " + car.name() + " age="
                                    + car.age() );
                        }
                    } else {
                        System.out.println( "Garage object not found!" );
                    }
                } else {
                    System.out.println( "Unknown parameter: " + args[0] );
                }

                db.close();
            }
        }
        
The functionality of this program is clear. If we give add as option, the next arguments must be the name and the year of the construction of the car - the same with the other options.

Have a look at the oldtimer section in the main() method: The result list of the oldtimer() method contains proxy objects. And so the calls of the age() and name() are RMI calls. But we call these methods from outside the server, this means the methods calls go through the socket connection. But the code looks straight forward. We have just written the code as expected - and it works. However, iterating over collections of database object outside a database method results in very poor performance and consistency problems. In fact all method calls from outside the Ozone server are transactions. If the method call returns, the transaction is commited, otherwise you get an exception and the transaction is aborted. You need not to explicitly start and commit transactions. This means all actions of a transaction must be grouped in one method. In our example we iterate over a collection of objects. Of course this should be a transaction because we do not want the GarageImpl object to be changed while we are working with it.


LocalDatabase is a subclass of ExternalDatabase specialized to work as an in JVM persistance storage for an application. The simplest way to create a LocalDatabase is as follows:

            ExternalDatabase db = ExternalDatabase.openDatabase("ozonedb:local://dbDir")
          
If you need more fine grained control over what is going on such as setting the debug level, overrriding the logging.properties setting or make sure the database is created if it does not exists you can use a more explicit way. For example: since a database will not be created for you if one did not exist before when calling ExternalDatabase.openDatabase() but instead throws an exception if no database was found in the directory you specified as part of the url you might want to query for that first and create a new db only if none is found:
          String dbDir = "/usr/local/test/ozoneDB";
          LocalDatabase db = new LocalDatabase();
          if (!db.exists(dbDir)) {
              db.create(dbDir);
            }
          
After the db has been instantiated you start it up by calling open():
          db.open(dbDir);
          
Then you can use it as usual i.e. db.objectForName(...), db.createObject(...) etc.

If you want to overrride the debug level setting in config.properties located in the root of the database directory you can set it by doing the following:

          // Open the database
          db.open(dbDir, OzoneDebugLevel.ERROR_STR);
          

This setting would instruct the logging system to only log things of error severity and up (i.e. ERROR and FATAL). Ozone uses Log4J for its logging system so the levels corresponds to the Log4J levels in the Level class but we have added some debug levels to make development easier and hence our debug level all derive from OzoneDebugLevel instead of directly from Level. In reality it is often more flexible to not hard code this so that the debug level can be easily changed by editing the properties file compared to recompiling in order to change the log level.