org.dellroad.stuff.pobj
Class PersistentObject<T>

java.lang.Object
  extended by org.dellroad.stuff.pobj.PersistentObject<T>
Type Parameters:
T - type of the root persistent object

public class PersistentObject<T>
extends Object

Main class for Simple XML Persistence Objects (POBJ).

Overview

Instances model an in-memory "database" represented by a root Java object and the graph of other objects that it references. The object graph is backed by a persistent XML file, which is read at initialization time and re-written after each change.

Changes are applied "wholesale" to the entire object graph, and are serialized and atomic. In other words, the entire object graph is read from, and written to, this class by value. As a result, it is not possible to change only a portion of the "database". The entire object graph is read and written as one thing. Similarly, the persistent XML file is updated by writing out a new, temporary copy and renaming the copy onto the original, using File.renameTo() for atomicity (on systems that support it, e.g., UNIX variants).

Update Details

When the object graph is updated, it must pass validation checks, and then the persistent XML file is updated and listener notifications are sent out. Support for delayed write-back of the persistent XML file is included: this allows modifications that occur in rapid succession to be consolidated into a single filesystem write operation.

Support for optimistic locking is included. There is a "current version" number which is incremented each time the object graph is updated; writes may optionally specify this number to ensure no intervening changes have occurred. If concurrent updates are expected, applications may choose to implement a 3-way merge algorithm of some kind to handle optimistic locking failures.

Instances can be configured to preserve one or more backup copies of the persistent file on systems that support hard links (requires JNA; see HardLink). Set the numBackups property to enable.

"Out-of-band" Writes

When a non-zero check interval is configured, instances support "out-of-band" writes to the XML persistent file by some other process. This can be handy in cases where the other process (perhaps hand edits) is updating the persistent file and you want to have a running process pick up the changes just as if setRoot() had been invoked. In particular, instances will detect the appearance of a new persistent file after an instance has started without one. In all cases, persistent objects must properly validate.

A special case of this is effected when setRoot() is never explicitly invoked by the application. Then some other process must be responsible for all database updates, and this class automatically picks them up, validates them, and send out notifications.

Empty Starts

An "empty start" occurs when an instance is started but the persistent XML file is either missing, does not validate, or cannot be read for some other reason. In such cases, the instance will start with no object graph, and getRoot() will initially return null. This situation will correct itself as soon as the object graph is written via setRoot() or the persistent file appears (effecting an "out-of-band" update).

Whther empty starts are allowed is determined by the allowEmptyStart property (default false). When empty starts are disallowed, then start() will instead throw a PersistentObjectException. In this configuration, getRoot() can be relied upon to always return a non-null, valid root.

Delegate Function

Instances must be configured with a PersistentObjectDelegate that knows how to validate the object graph and perform conversions to and from XML. See PersistentObjectDelegate and its implementations for details.

Schema Changes

Like any database, the XML schema may evolve over time. The PersistentObjectSchemaUpdater class provides a simple way to apply and manage schema updates using XSLT transforms.

See Also:
PersistentObjectDelegate

Field Summary
protected  Logger log
           
 
Constructor Summary
PersistentObject(PersistentObjectDelegate<T> delegate, File file)
          Simplified constructor configuring for immediate write-back and no persistent file checks.
PersistentObject(PersistentObjectDelegate<T> delegate, File file, long writeDelay, long checkInterval)
          Constructor.
 
Method Summary
 void addListener(PersistentObjectListener<T> listener)
          Add a listener to be notified each time the object graph changes.
 void checkFile()
          Check the persistent file for an "out-of-band" update.
 long getCheckInterval()
          Get the delay time between periodic checks for changes in the underlying persistent file.
protected  PersistentObjectDelegate getDelegate()
          Get the configured PersistentObjectDelegate.
 int getNumBackups()
          Get the number of backup copies to preserve.
 File getPersistentFile()
          Get the persistent file containing the XML form of the persisted object.
 T getRoot()
          Atomically read the root object.
 T getSharedRoot()
          Get a shared copy of the root object.
 long getVersion()
          Get the version of the current root.
 long getWriteDelay()
          Get the maximum delay after an update operation before a write-back to the persistent file must be initiated.
 boolean isAllowEmptyStart()
          Determine whether this instance should allow an "empty start".
 boolean isStarted()
          Determine whether this instance is started.
protected  void notifyListeners(long newVersion, T oldRoot, T newRoot)
          Notify listeners of a change in value.
protected  T read()
          Read the persistent file.
 void removeListener(PersistentObjectListener<T> listener)
          Remove a listener added via addListener().
 void setAllowEmptyStart(boolean allowEmptyStart)
          Configure whether an "empty start" is allowed.
 void setNumBackups(int numBackups)
          Set the number of backup copies to preserve.
 void setRoot(T newRoot)
          Atomically update the root object.
 void setRoot(T newRoot, long expectedVersion)
          Atomically update the root object.
 void start()
          Start this instance.
 void stop()
          Stop this instance.
 String toString()
          Get a simple string description of this instance.
protected  void write(T obj)
          Write the persistent file and rotate any backups.
 
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
 

Field Detail

log

protected final Logger log
Constructor Detail

PersistentObject

public PersistentObject(PersistentObjectDelegate<T> delegate,
                        File file,
                        long writeDelay,
                        long checkInterval)
Constructor.

The writeDelay is the maximum delay after an update operation before a write-back to the persistent file must be initiated.

Parameters:
delegate - delegate supplying required operations
file - the file used to persist
writeDelay - write delay in milliseconds, or zero for immediate write-back
checkInterval - check interval in milliseconds, or zero to disable persistent file checks
Throws:
IllegalArgumentException - if delegate or file is null
IllegalArgumentException - if writeDelay or checkInterval is negative

PersistentObject

public PersistentObject(PersistentObjectDelegate<T> delegate,
                        File file)
Simplified constructor configuring for immediate write-back and no persistent file checks.

Equivalent to:

PersistentObject(delegate, file, 0L, 0L);

Method Detail

getPersistentFile

public File getPersistentFile()
Get the persistent file containing the XML form of the persisted object.


getWriteDelay

public long getWriteDelay()
Get the maximum delay after an update operation before a write-back to the persistent file must be initiated.

Returns:
write delay in milliseconds, or zero for immediate write-back

getCheckInterval

public long getCheckInterval()
Get the delay time between periodic checks for changes in the underlying persistent file.

Returns:
check interval in milliseconds, or zero if periodic checks are disabled

getVersion

public long getVersion()
Get the version of the current root.

This returns a value which increases monotonically with each update. The version number is not persisted with the persistent file; each instance of this class keeps its own version count. The version is reset to zero when stop() is invoked.

Returns:
the current object version, or zero if no value has been loaded yet

getNumBackups

public int getNumBackups()
Get the number of backup copies to preserve.

Backup files have suffixes of the form .1, .2, etc., in reverse chronological order. Each time a new root object is written, the existing files are rotated.

Back-ups are created via hard links and are only supported on UNIX systems.

The default is zero, which disables backups.


setNumBackups

public void setNumBackups(int numBackups)
Set the number of backup copies to preserve.

Throws:
IllegalArgumentException - if numBackups is negative
See Also:
getNumBackups()

isAllowEmptyStart

public boolean isAllowEmptyStart()
Determine whether this instance should allow an "empty start".

The default for this property is false.


setAllowEmptyStart

public void setAllowEmptyStart(boolean allowEmptyStart)
Configure whether an "empty start" is allowed.

The default for this property is false.


isStarted

public boolean isStarted()
Determine whether this instance is started.


start

public void start()
Start this instance. Does nothing if already started.

Throws:
PersistentObjectException - if an error occurs

stop

public void stop()
Stop this instance. Does nothing if already stopped.

Throws:
PersistentObjectException - if a delayed write back is pending and error occurs while performing the write

getRoot

public T getRoot()
Atomically read the root object.

If there is no persistent file and no value has been set, null will be returned. However the persistent file may appear "out of band" at any time; if so, this will be detected within the configured check interval.

This returns a deep copy of the current root object; any subsequent modifications are not written back.

Returns:
the current root instance, or null if after an "empty start"
Throws:
IllegalStateException - if this instance is not started
PersistentObjectException - if an error occurs

getSharedRoot

public T getSharedRoot()
Get a shared copy of the root object.

This returns a copy of the root object, but it returns the same copy each time until the next change. This method is more efficient than getRoot(), but all callers must agree not to modify the returned object or any object in its graph of references.

Returns:
shared copy of the root instance, or null if after an "empty start"

setRoot

public final void setRoot(T newRoot,
                          long expectedVersion)
Atomically update the root object.

The given object is deep-copied and the copy replaces the current root.

If expectedVersion is non-zero, then if the current version is not equal to it, a PersistentObjectVersionException exception is thrown. This mechanism can be used for optimistic locking.

Parameters:
newRoot - new persistent object
expectedVersion - expected current version number, or zero to ignore the current version number
Throws:
IllegalArgumentException - if newRoot is null
IllegalArgumentException - if version is negative
IllegalStateException - if this instance is not started
PersistentObjectException - if an error occurs
PersistentObjectVersionException - if version is non-zero and not equal to the current version
PersistentObjectValidationException - if the new root has validation errors

setRoot

public final void setRoot(T newRoot)
Atomically update the root object.

The is a convenience method, equivalent to:

setRoot(newRoot, 0)

This method cannot throw PersistentObjectVersionException.


checkFile

public void checkFile()
Check the persistent file for an "out-of-band" update.

If the persistent file has a newer timestamp than the timestamp of the most recently read or written version, then it will be read and applied to this instance.

Throws:
IllegalStateException - if this instance is not started
PersistentObjectException - if an error occurs

addListener

public void addListener(PersistentObjectListener<T> listener)
Add a listener to be notified each time the object graph changes.

Throws:
IllegalArgumentException - if listener is null

removeListener

public void removeListener(PersistentObjectListener<T> listener)
Remove a listener added via addListener().


toString

public String toString()
Get a simple string description of this instance. This description appears in all log messages.

Overrides:
toString in class Object

getDelegate

protected PersistentObjectDelegate getDelegate()
Get the configured PersistentObjectDelegate.


read

protected T read()
Read the persistent file.

Throws:
PersistentObjectException - if an error occurs

write

protected final void write(T obj)
Write the persistent file and rotate any backups.

A temporary file is created in the same directory and then renamed to provide for an atomic update (on supporting operating systems).

Throws:
IllegalArgumentException - if obj is null
PersistentObjectException - if an error occurs

notifyListeners

protected void notifyListeners(long newVersion,
                               T oldRoot,
                               T newRoot)
Notify listeners of a change in value.

Parameters:
newVersion - the version number associated with the new root