Copyright © 2002, 2003 SMB GmbH
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
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.
Ozone started by Falko Braeutigam as a multimedia study project around ten years ago after reading an article about a new, document centric approach to desktop software where the idea was that the programs should not be primary but the data. The author called this "multimedia" because this technology should allow mixing data from different sources and of different format into one document. The text part should be controlled by a text component, the pictures by a picture component. While looking for a software architecture to support this, i.e. something that was able to serve as a container for the logic (components) that manipulates the persistent data he got an idea for a new approach to software development where logic and data is not separate and objects does not disappear when the program stops and where we should not have to build "programs" like before. Today we are calling this components and application servers.
This evolved into a system, which relies on a single-instance architecture where there is only one instance of a database object inside the database server, controlled via Proxy objects. Since the proxy object represent the actual database object it is used by the client as if it was the actual database object.
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:
SQL in code: besides being extremely ugly, it is also hard to maintain, and impossible for a compiler to syntax test.
performance: first you need to create an SQL statement, then the server has to parse it, process it, and send back the results.
data integrity: when changing multiple data you need to either have a complex statement, or you need to start and stop transactions.
synchronisation: if a statement or transaction fails you might need to re-read the old data from the rdbms in order to have the same data in memory as on disk
object-relational mapping: you need to translate your object into relational data and back.
server side code: if you want to run code on the server side you must use some manufactorer-specific language for stored procedures or macros or whatever the specific database calls it.
SQLa != SQLb: ever tried to store an empty java.lang.String ("") in an Oracle db using JDBC? If yes: ever tried to read it back? If yes: what did you get? For those who don't know: you will not recieve an empty string: instead you get a null-string! This is only a very basic example; if you delve deeper you will find that almost every rdbms has it's own SQL accent and its own peculiarities.
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.
In order to learn to use Ozone the first thing you need to do is (temporary) forget everything you know about relational data and rdbms-es. And everything really means 'everything' here: entity knowledge (tables, indexes, foreign keys) as well as procedural knowledge (locking, transactions) and meta knowledge (E-R diagrams). Let it all go. Stop thinking about data and objects as separate things (they were never in the first place) and return to a pure Object Oriented view of the world.
This is the first and probably most important step of getting into the Tao of Ozone.
Did we already mention that you should stop thinking about relational data? Just nagging you here ;-). But seriously, you should and go even further: until it is mentioned in this guide again just stop thinking about persistency altogether. The beauty of Ozone is that it will perform better and your application design will be cleaner if you do not think in a persistency centric way. The best way to stop thinking persistency centric would be to not think about persistency at all. In the end the whole data storing stuff will just fall into place in a natural way, the mist will subside and you will see the whole picture (and you'll be able to dodge bullets too). Just give yourself time to read this guide and perform its exersizes. If you find yourself thinking things like: ?That's all fine and all, but get on with it: how do I actually use Ozone?? then please read the quick start.
Table of Contents
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?
The point has been reached at which you can safely start thinking a bit about persistency again, but only a bit. You should still refrain from thinking about tables, primary indexes, foreign keys and other relational persistency related stuff. Only focus on Ozone and java and objects; that is all you need.
Back to the questions that might have popped up in your mind: "Where and how did we do that?". The answer is simple: by making use of interfaces and factories we are more than half way on the journey to get into the basics of the Tao of Ozone.
It is all in the use of factories and interfaces. The example uses interfaces to reference objects; it does not reference the objects directly. This is the same for any Ozone application: you never directly use any *Impl instance, you only use interfaces (for persistent objects). The example also does not create objects directly, it uses factories for that. In an Ozone application you do exactly the same thing: you use a factory to create (persistent) objects. To be honest, there is a slight difference between the factory stuff in the example and in Ozone: in the example you had to write the factory yourself, in Ozone factories are created for you. More about that later.
In the example we use only interfaces. What is behind the interface we do not need to know. If we replaced the implementation classes with totally different ones that also implement all interfaces like they should, the whole example would still work as it does with 'our' *Impl classes. So if we replaced the *Impl classes with other classes for which the instances would automatically persist themselves whenever they changed, we have the same example, but then with a database underneath.
Ozone works a bit like this: instead of calling your own *Impl instances you always call Ozone generated proxies that relay each and every call to the *Impl instances that they are 'linked' to. Lets go a bit deeper into how this comes to be. When you use an Ozone generated factory to create a persistent object, two things happen: 1) an object is created on the server side; this is an instance of the *Impl class you have written yourself 2) an object is created on the client side; this is a proxy, an instance of a class created by Ozone. These two objects are connected to each other: every method you call on the proxy gets 'transported' to the server and executed there. The return code (if any) is then send back to the proxy object, which returns it to its caller.
In order to make the example work with Ozone we have to change a few things.
Every class for which we want to store the instances into an Ozone database must extend org.ozoneDB.OzoneObject.
This is a slight exaggeration: in fact these classes need only implement org.ozoneDB.OzoneCompatible, but then you would still need to write some code; extending OzoneObject is the easiest solution.
Every interface which we want to use for persistent classes must extend org.ozoneDB.OzoneRemote.
'Mark' every methods that changes its object.
Change every appearance of the this keyword in every implementation class into a method call to self() when this is passed as a parameter to a method.
this.foo(); // use of this is okay here this.memberVariable = 5; // okay here as well otherObj.bar(this); // should replace 'this' with 'self()' here
Make sure every (object) parameter in every method call in the interface implements java.io.Serializable.
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.
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.
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.
Do a search/replace (this -> self()) in the three *Impl files.
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.
Some odbms-es use a post processor to literally change the java byte-code in the classfiles compiled by javac; for every method that changes an object an explicit call to start a transaction is inserted at the start of that method and a similar call to end the transaction is inserted at the end. This is dirty. Very dirty. Almost as dirty as writing self-modifying code. The moment Sun decides to change the classfile structure your database software must be upgraded. Not good. Not good at all. And now for the positive news: the Ozone Post Processor (OPP) does not fiddle with your classes. Period.
Now that we know what OPP does not do it is probably useful to focus on what it actually does. OPP does three things:
Create proxy classes; these are the classes that implement the same interface as the persistent implementation class does.
Create factories.
Create fake Factory classes to avoid the chicken-and-egg problem which may arise when using factories. As stated earlier OPP does not directly fiddle with your classfiles; it does however indirectly need them to create proxy classes
.Table of Contents
Now that we had a first walk through of how to develop with Ozone we are ready to go through a more extensive example: the Tutorial. This tutorial gives you a very simple example, which shows the most important features of and the work with ozone. Other, more complex, samples can be found in the 'samples' directory of the Ozone distribution. You may use them to get familiar with the more advanced concepts of the Ozone system or as starting points for your own projects.
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:
Comment in the interface source code.
Put a /*update*/ comment at the end of each method that changes the state of the object.
Pattern for the names of the update methods.
Name all update methods after a regular expression pattern. Such a pattern can be for example .*_update". When running OPP (the section called “OPP and Proxy objects”) this pattern has to be specified to make OPP able to determine the methods for which it have to generate special code. The Garage interface the section called “The "Garage" interface and "GarageImpl" class” uses patterns.
Ozone Class Descriptor.
An Ozone Class Descriptor (OCD) is an XML, which holds some meta information of the classes. The OPP (see the section called “OPP and Proxy objects” ) uses the class descriptor to determine the lock level of each transaction.
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.javaIn 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 following "picture" shows all classes and interfaces that are needed for the Proxy System of Ozone. "Higher" classes/interfaces extend "lower" ones.
The classes on the left are server-side classes and the classes on the right are client-side classes. The [remote interface] is the glue that allows to use proxy objects like the actual objects.
########### .....>> # OPP # >>....... . ########### . . ^ . . ^ . --------------- . --------------- *Impl . *Impl_Proxy --------------- . --------------- | \ ~~~~~~~~~~~~~~~~~~ / | | ----- [remote interface] --- | --------------- ~~~~~~~~~~~~~~~~~~ --------------- OzoneObject | OzoneProxy --------------- | --------------- | ~~~~~~~~~~~~~~~~~~ | OzoneRemote ~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~ OzoneCompatible ~~~~~~~~~~~~~~~ Java interfaces : ~~~~~~~~ Java classes : -------- executable : ########
However, the only thing the programmer has to do is to write the code of the *Impl class and put all remote methods that should be accessible from the client side in the remote interface. OPP takes *Impl and remote interface and generates the *Impl_Proxy proxy class.
This section describes all steps that has to be completed to make a database class. It is a brief overview without code or examples. However, following the examples of this tutorial is the best way to learn how to work with Ozone.
Write the implemention.
Create a new class and name it *Impl (for example AutoImpl). This class should extend OzoneObject.
Create the remote interface.
When the implementation of the *Impl class is finished you have to create a Java interface that represents the remote interface of the *Impl class. Use a simple name for the interface (for example Auto).
Mark all update methods.
All methods of the remote interface that change the internal state of the object or that call a method that changes the internal state of the same object (for example this.doSomething()) has to be marked. Add a comment /*update*/ to the end of each line that defines an update method in the remote interface.
Let the *Impl class implement the newly created remote interface.
Let OPP create the proxy class.
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.
With this brief tutorial you have got a first idea how to work with Ozone. You should play around with the examples to get the right feeling for it. It is easy to use Ozone, if you always keep the basic principles in mind. Especially important is the meaning of Proxy objects in Ozone. The rest is programming as usual.
Table of Contents
There are two basic ways of working with an Ozone Object Server:
Ozone is running as a remote application server started separately.
Ozone is used as a local persistence engine for your application and started by your application.
ExternalDatabase represents the basic idea of a remote object server. The remote database is started by org.ozzoneDB.Server which is invoked by the db startup scripts (ozone.bat and ozone respectively). ExternalDatabase can then be viewed as the client side representation of that server. Use the static method openDatabase(String _url) to get a reference to the server. e.g.
ExternalDatabase.openDatabase("ozonedb:remote://localhost:3333")
All samples uses an External database. The important thing to remember is that all classes stored must be in the classpath. Either start the server in the directory where your compiled classes are -such as described in the "simple sample", or create a directory somewhere (eg. an "ozoneDeploy" dir at the same level as your database dir and copy all your Ozone Objects there. Add that directory to your classpath before starting the server.
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.
Unlike JDBC, Ozone is not command oriented. That is, in ozone you implicitly specify which datbase (connection) to use just by calling the right database object from the right thread.
So, ExternalDatabase does not specify one database connection (like a JDBC connection) but it represents an entire ozone server/database. Each client site db object proxy knows the ExternalDatabase it belongs to. When the proxy is invoked then the ExternalDatabase can check, if the current thread has already joined a (long/explicite) transaction. If not, then this invocation is wrapped in it's own (short/implicit) transaction. In this case ExternalDatbase picks an actual (socket) connection from the pool (or creates a new one if the pool is empty) - sends the request to the server - handles return value - and puts the connection back to the pool. Thus ozone provides pooling and multiplexing. Multiplexing means that several client threads can use the same connection entwinded. Since each connection represents a server side thread, it is possible that 100 client side threads are served by just 5 server side threads.
This means that you can hold on the the reference to the OzoneInterface (either ExternalDatabase or LocalDatabase ) and need not call the close method unless you are done using the Ozone server. Please see the SimpleServlet example in the samples directory for an example.
* Either extend OzoneObject or implement OzoneCompatible
* Object lifecycle (creation, onCreate, onDelete...)
There are two ways to create new Ozone Objects
Use the database to create Ozone Objects (e.g. MyClass blah = (MyClass)db.createObject(); )
Use the factory generated by OPP to create Ozone Objects
final String signature = String.class.getName() + "|" + Map.class.getName(); MyClass blah = (MyClass)db.createObject(MyClassImpl.class, signature, new Object[] {"foo", theMap});
* serialVersionUID
* use of transient and private readObject()
* Externalizable
Additional fields are handled in ozone as additional fields are handled in Java Serialization. If you add a field to a class and deserialize an object which was stored by the old class version, this field gets default-inizialized, that is zero for numbers, false for booleans and null for objects. As no constructors are called during deserialization, there cannot be other initial values for new fields than the default ones.
* buildt in schema evolution