A significant use-case that the CDM aims to support is that of web-based or networked nomenclators, taxonomic treatments, and other applications that serve authoritative, dynamic data for (re-)use by taxonomists and other software applications. As an example, a CDM store containing a web-based monograph or revision of a particular plant or animal family might be referenced by other taxonomists, or other taxonomic databases that deal with the same taxa. To allow applications to record and resolve changes to data over time, for example, to allow users or client applications to determine how a taxonomic classification or species page has been altered since they last accessed that information, the CDM has a fine-grained versioning functionality that records changes to objects and their relationships, and allows the prior state of the dataset to be reconstructed.
The CDM uses hibernate-envers, a
versioning / auditing library that is part of the hibernate core library.
The versioning functionality is limited by the features that envers
provides. Envers stores changes to entities on a per-transaction basis.
Consequently, it is not possible to resolve changes that take place within
the same transaction. Each transaction results in the creation of an
AuditEvent
object that provides metadata about the
audit event and also allows the state of the database at that point to be
reconstructed (because an AuditEvent
represents a
point in time across the entire database, rather than on a per-object
basis). To learn more about envers and the way that it versions data,
check out the presentation given by its creator, Adam Warski here.
Versioning is enabled by default, and calls to methods like
save
, update
, and
delete
, will automatically result in data being
versioned. Application developers only need to be aware of the existence
of versioning when reading data, and only then if they wish to retrieve an
object in its prior state. If applications wish to retrieve objects from
the current state of the database, they do not need to perform any
additional operations.
Because versions of objects are related to a global
AuditEvent
, and because applications may call
several service layer methods when retrieving data for presentation in a
particular view, the CDM stores the AuditEvent
in
the static field of an object called
AuditEventContextHolder
, allowing the CDM and any
application code to discover which particular
AuditEvent
a view relates to without needing to
pass the AuditEvent
explicitly as a method
parameter (this pattern is borrows from the
SecurityContext
class in Spring-Security).
To query the CDM at a particular AuditEvent
,
applications need to place the AuditEvent
in to the
AuditEventContextHolder
and then call DAO methods
as usual.
// This would retrieve the current version of the taxon with a matching uuid. Taxon taxon = taxonDao.find(uuid); // Set the audit event you're interested in AuditEventContextHolder.getContext().setAuditEvent(auditEvent); // This method call now retrieves the taxon with a matching uuid at the audit event in context // or null if the taxon did not exist at that point. Taxon taxon = taxonDao.find(uuid); // Now clear the context AuditEventContextHolder.clearContext(); // Further calls to the persistence layer will return the most recent objects
Not all DAO methods are available in non-current contexts, either
because they require certain methods that Envers doesn't currently support
(such as case-insensitive string comparison), or are across relationships
- currently envers does not support queries that place restrictions on
related entities. In some cases this will be addressed in future releases
of envers, and the CDM will incorporate these new releases as they occur.
Some methods rely on the free-text-search functionality provided by
hibernate search. Because hibernate search (and apache Lucene) are based
on an optimized set of index files that reflect the current state of the
database, it is not possible to search these indices at prior events. It
is unlikely that the free-text-search functionality will ever be available
in non-current contexts. If an application calls such a method in a
non-current context, an
OperationNotSupportedInPriorViewException
is thrown, giving applications an operation to recover.
Objects retrieved in prior contexts can be initialized using the
propertyPaths
parameter, or (if the transaction is
still open) by calling accessor methods in domain objects directly (just
as you would with normal hibernate-managed entities).
In addition to being able to retrieve objects at a given state, the
DAOs implement the IVersionableDao
interface that offers five specific methods for working with versioned
objects.
Table 5.1. IVersionableDao
methods
Method | Description |
---|---|
List<AuditEventRecord<T>> getAuditEvents(T t,
|
Returns a list of audit events (in order) which affected
the state of an entity t. The events returned either start at
the |
int countAuditEvents(T t,
|
Returns a count of audit events which affected the state of an entity t. |
AuditEventRecord<T> getNextAuditEvent(T t);
|
A convenience method which returns a record of the next (relative to the audit event in context). |
AuditEventRecord<T> getPreviousAuditEvent(T t);
|
A convenience method which returns a record of the previous (relative to the audit event in context). |
boolean existed(UUID uuid);
|
Returns true if an object with uuid matching the one supplied either currently exists, or existed previously and has been deleted from the current view. |