Side of Software
Persistence Library 2.0

sos.db
Interface Database

All Known Implementing Classes:
AbstractDatabase, AbstractSerializationDatabase, FileDatabase, InMemoryDatabase

public interface Database

A database of objects. This interface provides a way to add objects to a named database. When the client modifies these objects, the database is automatically updated. When the client no longer references these objects, they are automatically removed from the database.

This interface provides sophisticated database functionality through a simple, unobtrusive interface. It hides all of the details of a complex database management system. From a client's perspective, the database--or persistence layer--can be nearly transparent. Clients need not worry about database IDs or keys. There is no concept of a table or query, and there is no need for a mapping from an application class to a schema.

Database State

At any given time, a database is in one of two states:

A database can be closed and opened any number of times. Similarly, a database can be deleted and created any number of times. The following state diagram illustrates the changing of state as a result of a method invocation. If a method does not appear on an arc leaving a state, then the method throws an IllegalStateException.

Note: If the database system is a client-server system and this interface describes the client, then the operations create, open, close, and delete apply to the client side.

Database and Proxy Objects

A database stores application objects for later use. The methods that deal with database objects are
   Object addObject( Object object ) throws TransactionAbortedException
   boolean containsObject( Object object )
The method addObject returns a proxy object. The proxy object implements the same interfaces as the original object. After calling addObject, the client should not retain any references to the original object; it should reference the original object only through the returned proxy object. Passing the proxy object to containsObject returns true.

Note: Clients do not need to invoke addObject on immutable database objects.

A consequence of using proxies is that ordinary methods may throw TransactionAbortedException as a result of a database error or conflict. Even though this exception is a run-time exception, it is intended to be caught.

Root Object

The database's root object serves as an entry point into the database. The client defines the root object and supplies it in the call to create. Like all other database objects, it should be referenced only through its proxy object. The root's proxy object is returned in getRoot.

The root object is typically a data structure, such as a map or index, and all database objects must be reachable (recursively) from this root.

Transactions

Transactions provide a way to treat a group of operations as one unit. The Database interface supports nested transactions that satisfy the ACID requirements. In other words, they exhibit:

When the client wishes to treat a group of operations as atomic, it should invoke startTransaction, execute the operations, and then invoke commitTransaction. This chunk of code must be executed by the same application thread, and it should catch and handle a TransactionAbortedException. If the client wishes to cancel a transaction programmatically, it should call abortTransaction.

Oftentimes, this transaction block of code would correspond to a synchronization block if the client weren't using this library. The startTransaction corresponds to monitor acquisition and the commitTransaction corresponds to the monitor release. With transactions, however, there is no need to lock database objects in the application code. The database handles the concurrency (and deadlock avoidance) automatically.

Implementations

This package provides two types of databases:

Although these databases can be exchanged with very little effort, clients requiring persistence cannot substitute InMemoryDatabase for FileDatabase.

Example

The following (incomplete) code illustrates the use of a database in an application.

 public class Application {
   private Database db;

   public Application() throws IOException {
     db = new ...;  // some Database implementation like FileDatabase

     if( !db.exists() )
       db.create( new HashMap() );
     db.open( false );
   }

   private Person createPerson() {
     Person person = new PersonImpl();  // some implementation of interface Person
     person = (Person)db.addObject( person ); // immediately add object to database
     return person;                           // return the proxy object
   }
     
   private void onCreatePerson() {
     try {
       db.startTransaction();
       
       Person person = createPerson();
       person.setFirstName( firstName );
       person.setLastName( lastName );

       db.commitTransaction();
     }
     catch( TransactionAbortedException tae ) {
       Throwable cause = tae.getCause();

       // inform the user that the operation failed

       // The state of the database is the same as before
       // the call to startTransaction. Specifically,
       // no new Person has been added.
     }
   }
 }
 
Thread-safety. The methods startTransaction, commitTransaction, abortTransaction, addObject, containsObject, and getRoot are expected to be invoked by different threads. No explicit synchronization is needed. The other methods, however, are not expected to be executed by multiple threads; explicit synchronization is needed.

Transaction Policy. If possible, implementations of this interface should delegate transactional behavior, such as locking and deadlock avoidance, to an instance of TransactionPolicy.

When a top-level transaction is started, the database should invoke startTransaction on the underlying transaction policy. Before a database accepts a new object (via addObject), it should invoke registerObject on the transaction policy. The database should let the transaction policy carry out all method invocations on the actual object by invoking invoke.

Last, before a database automatically deletes an object, it should invoke unregisterObject on the transaction policy.

Logging. Although it is not required, implementations of Database should use Java's logging API to log database activity. It should log activities in the "sos.db" logger, which, for convenience, is available as the public static field logger.

Since:
1.0
See Also:
FileDatabase, InMemoryDatabase

Field Summary
static java.util.logging.Logger logger
          The object that should be used to log database activity.
 
Method Summary
 void abortTransaction()
          Aborts all changes under the calling thread's current transaction.
 java.lang.Object addObject(java.lang.Object object)
          Adds the specified object to this database and returns a proxy object that the client should use instead.
 void close()
          Closes this database.
 void commitTransaction(Progress progress)
          Commits all changes under the calling thread's current transaction.
 boolean containsObject(java.lang.Object object)
          Returns true if this database contains the specified object.
 boolean create(java.lang.Object root)
          Creates a new database if and only if it does not exist.
 boolean delete()
          Deletes this database.
 boolean exists()
          Returns true if this database exists.
 java.lang.String getName()
          Returns the name of this database.
 java.lang.Object getRoot()
          Returns the root object of this database.
 TransactionPolicy getTransactionPolicy()
          Returns the transaction policy governing the transactions.
 boolean isOpen()
          Returns true if this database is open and ready to accept requests.
 void open(boolean readOnly)
          Opens this database.
 void startTransaction()
          Starts a new (potentially nested) transaction for the calling thread.
 int transactionDepth()
          Returns the nesting depth of the transaction associated with the current thread.
 

Field Detail

logger

static final java.util.logging.Logger logger
The object that should be used to log database activity. It is the logger associated with "sos.db", using the resource bundle named "sos.db.database".

Method Detail

abortTransaction

void abortTransaction()
Aborts all changes under the calling thread's current transaction. The state of the system is "rolled back" to that prior to the start of the transaction. The parent transaction (if any) is not affected.

Throws:
java.lang.IllegalStateException - if this database is not open
NoTransactionException - if the calling thread does not have an active transaction
See Also:
commitTransaction(sos.db.Progress), startTransaction(), isOpen()

addObject

java.lang.Object addObject(java.lang.Object object)
                           throws TransactionAbortedException
Adds the specified object to this database and returns a proxy object that the client should use instead. Any subsequent changes to the original object may cause the database to be in an inconsistent state. It is essential to only update the original object through the returned proxy object.

The proxy object will implement the same interfaces as object. Specifically, the interfaces returned by invoking getClass().getInterfaces() on the proxy object will be (in order) the interfaces implemented by the top-most superclass, the interfaces of the next lower superclass, etc. down to the interfaces of the object's class. Also, an interface will not be listed again if a superclass implements the same interface.

Any updates made to the proxy object will get propagated to the original object and the changes will automatically get saved in the database when the top-most transaction enclosing the updates commits.

The object will be automatically removed from this database after it becomes unreachable.

If there is no active transaction for the calling thread, addObject is atomic only with itself. That is, it is as if startTransaction is invoked immediately before the call to addObject and commitTransaction immediately after.

Parameters:
object - object to add to the database
Returns:
a proxy object that implements the same interfaces as object
Throws:
java.lang.IllegalStateException - if this database is not open or if this database is open in read-only mode
java.lang.NullPointerException - if object is null
TransactionAbortedException - if this database cannot add object and the transaction has been aborted as a result. The cause of the abort can be obtained with getCause. This exception is intended to be caught.
See Also:
containsObject(java.lang.Object), isOpen(), startTransaction(), commitTransaction(sos.db.Progress)

close

void close()
           throws java.io.IOException
Closes this database. It is the client's responsibilty to ensure that all active transactions have finished.

Throws:
java.io.IOException - if an I/O error occurs

commitTransaction

void commitTransaction(Progress progress)
                       throws TransactionAbortedException
Commits all changes under the calling thread's current transaction. The changes are guaranteed to persist only if the transaction is a top- level transaction. If the transaction's parent later aborts, the commit will change to an abort.

Callers of this method have a chance to follow the method's progress and to suggest an abort in the middle of committing. A custom Progress parameter can be passed in to update a progress bar in the user interface or to respond to a cancel button, for example. If the progress is canceled, this method aborts with a TransactionAbortenulldException.

Parameters:
progress - an object to be updated and checked while the commit is in progress (may be null to indicate that no updating/checking is necessary)
Throws:
java.lang.IllegalStateException - if this database is not open
NoTransactionException - if the calling thread does not have an active transaction
TransactionAbortedException - if this database cannot commit the transaction or if the progress is canceled. The cause of the abort can be obtained with getCause. This exception is intended to be caught.
See Also:
startTransaction(), abortTransaction(), isOpen()

containsObject

boolean containsObject(java.lang.Object object)
Returns true if this database contains the specified object. object must be a proxy object returned by addObject in order for the method to return true. If object is null, the method returns false. If this database is closed, then the method throws an IllegalStateException.

No transactions are used or created when checking for containment, so TransactionAbortedException is never thrown.

Parameters:
object - the object to check for containment
Returns:
true if this database contains object
Throws:
java.lang.IllegalStateException - if this database is not open
Since:
2.0
See Also:
addObject(java.lang.Object), isOpen()

create

boolean create(java.lang.Object root)
               throws java.io.IOException
Creates a new database if and only if it does not exist.

The root object should be an index-type object (like a map) that will hold references to objects added to this database. The primary way to retrieve database objects is through this root object.

After the invocation of this method, clients should no longer reference root. They should instead invoke getRoot and reference the returned object.

Parameters:
root - the root of this database
Returns:
true if this database does not exist and is successfully created; false if this database already exists
Throws:
java.io.IOException - if an I/O error occurs
java.lang.NullPointerException - if root is null and this database does not allow a null root
See Also:
getRoot()

delete

boolean delete()
               throws java.io.IOException
Deletes this database. This database must already by unopened or closed. All system resources pertaining to this database (such as data, index, or log files) are deleted.

Returns:
true if this database is successfully deleted
Throws:
java.lang.IllegalStateException - if this database is open
java.io.IOException - if an I/O error occurs
See Also:
close(), isOpen()

exists

boolean exists()
Returns true if this database exists. To exist, a database normally has to have been initialized with create. Normally, when a database exists, a call to open will put it in a state where it is ready to accept database operations such as addObject and getRoot.

Returns:
true if this database exists
See Also:
open(boolean)

getName

java.lang.String getName()
Returns the name of this database.

Returns:
this database's name

getRoot

java.lang.Object getRoot()
                         throws TransactionAbortedException
Returns the root object of this database.

This object may not be the same object provided in the original invocation of create; however, it will implement the same interfaces and forward any requests to the original object.

Returns:
this database's root object (may be null)
Throws:
java.lang.IllegalStateException - if this database is not open
TransactionAbortedException - if the root object cannot be retrieved and the transaction has been aborted as a result. The cause of the abort can be obtained with getCause. This exception is intended to be caught.
See Also:
create(java.lang.Object), isOpen()

getTransactionPolicy

TransactionPolicy getTransactionPolicy()
Returns the transaction policy governing the transactions.

Returns:
this database's transaction policy
Since:
2.0

isOpen

boolean isOpen()
Returns true if this database is open and ready to accept requests.

Returns:
true if this database is open
See Also:
open(boolean), close()

open

void open(boolean readOnly)
          throws java.io.IOException
Opens this database. If this database exists, calling open puts it into a state where it can accept database operations such as addObject and getRoot. If this database does not exists, this method throws an IllegalStateException. If this database is already open, no change to the database occurs.

Parameters:
readOnly - true if database updates are not allowed
Throws:
java.io.IOException - if an I/O error occurs
See Also:
exists()

startTransaction

void startTransaction()
                      throws TransactionAbortedException
Starts a new (potentially nested) transaction for the calling thread. The transaction lasts until a matching call to commitTransaction or abortTransaction by the same thread. A call to startTransaction by the same thread before the transaction finishes starts a nested transaction with the original transaction as its parent. Nested transactions are started and stopped in a stack-like fashion.

All operations on database objects during a transaction are treated as a single atomic operation with respect to this database. An application should use transactions like it would the synchronized statement.

Throws:
java.lang.IllegalStateException - if this database is not open
TransactionAbortedException - if the transaction cannot be started. The cause of the exception can be obtained with getCause. This exception is intended to be caught.
See Also:
abortTransaction(), commitTransaction(sos.db.Progress), isOpen()

transactionDepth

int transactionDepth()
Returns the nesting depth of the transaction associated with the current thread.

Returns:
the nesting depth of the transaction associated with the current thread
Throws:
java.lang.IllegalStateException - if this database is not open

Side of Software
Persistence Library 2.0

Copyright 2004-08 Side of Software (SOS). All rights reserved.