Class AbstractSchemaUpdater<D,T>

java.lang.Object
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 Details

    • log

      protected final Logger log
  • Constructor Details

    • AbstractSchemaUpdater

      public AbstractSchemaUpdater()
  • Method Details

    • getUpdates

      public Collection<? extends SchemaUpdate<T>> getUpdates()
      Get the configured updates. This property is required.
      Returns:
      configured updates
      See Also:
    • 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:
    • 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

      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:
    • 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
      UnrecognizedUpdateException - 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.
      Parameters:
      updateName - schema update name
      Returns:
      true if updateName is valid
    • 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
      Returns:
      true if the database needs initialization
      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
      Returns:
      set of already-applied updates
      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)
      Returns:
      update name
      See Also:
    • getAllUpdateNames

      protected List<String> getAllUpdateNames() throws Exception
      Get the names of all updates including multi-action updates.
      Returns:
      list of update names
      Throws:
      Exception - if an error occurs
    • 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.

      Parameters:
      transaction - transaction within which to apply action
      action - operation to perform
      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.

      Parameters:
      database - database to apply action to
      action - operation to perform
      Throws:
      Exception - if an error occurs while accessing the database