org.dellroad.stuff.schema
Class AbstractSchemaUpdater<D,T>

java.lang.Object
  extended by org.dellroad.stuff.schema.AbstractSchemaUpdater<D,T>
Type Parameters:
D - database type
T - database transaction type
Direct Known Subclasses:
PersistentObjectSchemaUpdater, SQLSchemaUpdater

public abstract class AbstractSchemaUpdater<D,T>
extends Object

Handles the initialization and schema maintenance of a database.

In this class, a database is some stateful object whose structure and/or content may need to change over time. Updates are uniquely named objects capable of making such changes. Databases are also capable of storing the names of the already-applied updates.

Given a database and a set of current updates, this class will ensure that a database is initialized if necessary and up-to-date with respect to the updates.

The primary method is initializeAndUpdateDatabase(), which will:


Field Summary
protected  Logger log
           
 
Constructor Summary
AbstractSchemaUpdater()
           
 
Method Summary
protected  void apply(T transaction, DatabaseAction<T> action)
          Execute a database action within an existing transaction.
protected  void applyInTransaction(D database, DatabaseAction<T> action)
          Execute a database action.
protected abstract  void commitTransaction(T transaction)
          Commit a previously opened transaction.
protected abstract  boolean databaseNeedsInitialization(T transaction)
          Determine if the database needs initialization.
protected  String generateMultiUpdateName(SchemaUpdate<T> update, int index)
          Generate the update name for one action within a multi-action update.
protected abstract  Set<String> getAppliedUpdateNames(T transaction)
          Determine which updates have already been applied to the database.
protected  Comparator<SchemaUpdate<T>> getOrderingTieBreaker()
          Determine the preferred ordering of two updates that do not have any predecessor constraints (including implied indirect constraints) between them.
 Collection<? extends SchemaUpdate<T>> getUpdates()
          Get the configured updates.
 void initializeAndUpdateDatabase(D database)
          Perform database schema initialization and updates.
protected abstract  void initializeDatabase(T transaction)
          Initialize an uninitialized database.
 boolean isIgnoreUnrecognizedUpdates()
          Determine whether unrecognized updates are ignored or cause an exception.
static boolean isValidUpdateName(String updateName)
          Determine if the given schema update name is valid.
protected abstract  T openTransaction(D database)
          Begin a transaction on the given database.
protected abstract  void recordUpdateApplied(T transaction, String name)
          Record an update as having been applied to the database.
protected abstract  void rollbackTransaction(T transaction)
          Roll back a previously opened transaction.
 void setIgnoreUnrecognizedUpdates(boolean ignoreUnrecognizedUpdates)
          Configure behavior when an unknown update is registered as having already been applied in the database.
 void setUpdates(Collection<? extends SchemaUpdate<T>> updates)
          Configure the updates.
 
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
 

Field Detail

log

protected final Logger log
Constructor Detail

AbstractSchemaUpdater

public AbstractSchemaUpdater()
Method Detail

getUpdates

public Collection<? extends SchemaUpdate<T>> getUpdates()
Get the configured updates. This property is required.

Returns:
configured updates
See Also:
setUpdates()

setUpdates

public void setUpdates(Collection<? extends SchemaUpdate<T>> updates)
Configure the updates. This should be the set of all updates that may need to be applied to the database.

For any given application, ideally this set should be "write only" in the sense that once an update is added to the set and applied to one or more actual databases, the update and its name should thereafter never change. Otherwise, it would be possible for different databases to have inconsistent schemas even though the same updates were recorded.

Furthermore, if not configured to ignore unrecognized updates already applied (the default behavior), then updates must never be removed from this set as the application evolves; see setIgnoreUnrecognizedUpdates(boolean) for more information on the rationale.

Parameters:
updates - all updates; each update must have a unique name.
See Also:
getUpdates(), setIgnoreUnrecognizedUpdates(boolean)

isIgnoreUnrecognizedUpdates

public boolean isIgnoreUnrecognizedUpdates()
Determine whether unrecognized updates are ignored or cause an exception.

Returns:
true if unrecognized updates should be ignored, false if they should cause an exception to be thrown
See Also:
setIgnoreUnrecognizedUpdates()

setIgnoreUnrecognizedUpdates

public void setIgnoreUnrecognizedUpdates(boolean ignoreUnrecognizedUpdates)
Configure behavior when an unknown update is registered as having already been applied in the database.

The default behavior is false, which results in an exception being thrown. This protects against accidental downgrades (i.e., running older code against a database with a newer schema), which are not supported. However, this also requires that all updates that might ever possibly have been applied to the database be present in the set of configured updates.

Setting this to true will result in unrecognized updates simply being ignored. This setting loses the downgrade protection but allows obsolete schema updates to be dropped over time.

Parameters:
ignoreUnrecognizedUpdates - whether to ignore unrecognized updates
See Also:
isIgnoreUnrecognizedUpdates()

initializeAndUpdateDatabase

public void initializeAndUpdateDatabase(D database)
                                 throws Exception
Perform database schema initialization and updates.

This method applies the following logic: if the database needs initialization, then initialize the database and record each update as having been applied; otherwise, apply any unapplied updates as needed.

Note this implies the database initialization must initialize the database to its current, up-to-date state (with respect to the set of all available updates), not its original, pre-update state.

The database initialization step, and each of the update steps, is performed within its own transaction.

Parameters:
database - the database to initialize (if necessary) and update
Throws:
Exception - if an update fails
IllegalStateException - if this instance is not configured to ignore unrecognized updates and an unrecognized update has already been applied
IllegalArgumentException - if two configured updates have the same name
IllegalArgumentException - if any configured update has a required predecessor which is not also a configured update (i.e., if the updates are not transitively closed under predecessors)

isValidUpdateName

public static boolean isValidUpdateName(String updateName)
Determine if the given schema update name is valid. Valid names are non-empty and have no leading or trailing whitespace.


databaseNeedsInitialization

protected abstract boolean databaseNeedsInitialization(T transaction)
                                                throws Exception
Determine if the database needs initialization.

If so, initializeDatabase(T) will eventually be invoked.

Parameters:
transaction - open transaction
Throws:
Exception - if an error occurs while accessing the database

initializeDatabase

protected abstract void initializeDatabase(T transaction)
                                    throws Exception
Initialize an uninitialized database. This should create and initialize the database schema and content, including whatever portion of that is used to track schema updates.

Parameters:
transaction - open transaction
Throws:
Exception - if an error occurs while accessing the database

openTransaction

protected abstract T openTransaction(D database)
                              throws Exception
Begin a transaction on the given database. The transaction will always eventually either be committed or rolled back.

Parameters:
database - database
Returns:
transaction handle
Throws:
Exception - if an error occurs while accessing the database

commitTransaction

protected abstract void commitTransaction(T transaction)
                                   throws Exception
Commit a previously opened transaction.

Parameters:
transaction - open transaction previously returned from openTransaction()
Throws:
Exception - if an error occurs while accessing the database

rollbackTransaction

protected abstract void rollbackTransaction(T transaction)
                                     throws Exception
Roll back a previously opened transaction. This method will also be invoked if commitTransaction() throws an exception.

Parameters:
transaction - open transaction previously returned from openTransaction()
Throws:
Exception - if an error occurs while accessing the database

getAppliedUpdateNames

protected abstract Set<String> getAppliedUpdateNames(T transaction)
                                              throws Exception
Determine which updates have already been applied to the database.

Parameters:
transaction - open transaction
Throws:
Exception - if an error occurs while accessing the database

recordUpdateApplied

protected abstract void recordUpdateApplied(T transaction,
                                            String name)
                                     throws Exception
Record an update as having been applied to the database.

Parameters:
transaction - open transaction
name - update name
Throws:
IllegalStateException - if the update has already been recorded in the database
Exception - if an error occurs while accessing the database

getOrderingTieBreaker

protected Comparator<SchemaUpdate<T>> getOrderingTieBreaker()
Determine the preferred ordering of two updates that do not have any predecessor constraints (including implied indirect constraints) between them.

The Comparator returned by the implementation in AbstractSchemaUpdater simply sorts updates by name. Subclasses may override if necessary.

Returns:
a Comparator that sorts incomparable updates in the order they should be applied

generateMultiUpdateName

protected String generateMultiUpdateName(SchemaUpdate<T> update,
                                         int index)
Generate the update name for one action within a multi-action update.

The implementation in AbstractSchemaUpdater just adds a suffix using index + 1, padded to 5 digits, producing names like name-00001, name-00002, etc.

Parameters:
update - the schema update
index - the index of the action (zero based)
See Also:
SchemaUpdate.isSingleAction()

apply

protected void apply(T transaction,
                     DatabaseAction<T> action)
              throws Exception
Execute a database action within an existing transaction.

All database operations in AbstractSchemaUpdater are performed via this method; subclasses are encouraged to follow this pattern.

The implementation in AbstractSchemaUpdater simply invokes action.apply(); subclasses may override if desired.

Throws:
Exception - if an error occurs while accessing the database

applyInTransaction

protected void applyInTransaction(D database,
                                  DatabaseAction<T> action)
                           throws Exception
Execute a database action. A new transaction will be created, used, and closed. Delegates to apply() for the actual execution of the action.

If the action or commitTransaction() fails, the transaction is rolled back.

Throws:
Exception - if an error occurs while accessing the database