DRAGOS features a comprehensive and powerful event system.
Most changes in the DRAGOS system and graph data are advertised
using events, and interested parties can register with the
EventManager to be notified of such changes.
There are different kinds of events. For every one of these kinds there is a corresponding listener interface that must be implemented by those classes that want to be receive those events.
|DataEvents (GraphEntityClassEvents and GraphEntityEvents)||GraphEntityClassEvents signal changes in the schema, e.g. definition of a new Node class or a new attribute for such a class.
GraphEntityEvents indicate changes in the graph data: creation of entities, modification of attribute values and so on.
Both types of events describe manipulation of data in the database, and can only occur inside transactions. The abstract super class DataEvent not only emphasizes these commonalities, but also allows uniform support for transaction-awareness (see below).
|GraphPoolEvents||These events are triggered for graph pool related actions such as opening, closing, or deleting a graph pool.|
|TransactionEvents||Events of this type are fired before and after a transaction changes its state, e.g. is rolled back or committed|
Built on top of the event system is the
which provides a uniform way to define patterns of events and actions
to execute when a match is found, making it a great tool to perform
automated graph rewriting.
Most applications should make use of the
because it handles transaction details and thus leads to fewer bugs and also better
code readability and maintainability. But for cases where that extra
bit of control is needed, the underlying event mechanism is also accessible.
It is recommended to read the first part of this document, dealing with the
event system itself, even if you only want to use the
Understanding the event system will help you in choosing the right coupling
mode and avoiding pitfalls.
Since much of the following discussion deals with events in the context of transactions, you should be familiar with the Guide to Transactions.
The EventManager is the heart of event handling in DRAGOS. In contrast to what you may be used to (e.g. from GUI programming), you do not register with the actual object generating the events, but with the EventManager.
There are a number of reasons for this:
When registering as a listener for a DataEvent,
you must specify an
EventCouplingMode that identifies when you want to receive the events:
IMMEDIATE- events will be fired as soon as they occur (as in GUI programming).
BEFORE_COMMIT- events will be queued until the data has passed the consistency checks and the top-level transaction is about to be committed. This implies that for nested transactions, no queued events are fired when calling
commit()on the nested transaction itself. It also implies that, when queued event handling starts, the top-level transaction will be the current one (otherwise,
commit()would result in an exception), thus providing a fixed and well-defined context of execution.
AFTER_COMMIT- events will be queued and fired after the data is actually committed to the database.
Operating on possibly inconsistent data is very difficult and requires great care. If possible at all, try to avoid doing so!
When receiving events in
IMMEDIATE mode, no checks have been performed.
BEFORE_COMMIT mode, the data has passed the consistency checks. If a listener performs any changes on the data, the data is checked again (possibly resulting in a consistency exception), and any events generated by this change are added to the end of the queue, as if they had occured in the main body of the transaction. No guarantees are made as to the order of listeners or the fairness of event distribution, only that each listener will receive all of the events that occur in this transaction in the same order they are generated. So if you need a fixed order of execution for a number of listeners working closely together, it would be best to only register a helper class as listener that receives the events and redistributes them according to your needs internally. Also, when implementing listeners, please take care that you do not keep modifying the data in reaction to every event, which in turn will generate another event, thus creating an infinite loop!
All these problems do not apply to
AFTER_COMMIT. The changes are already written to the database, and consistency is guaranteed. If a transaction is rolled back, its event queue is silently discarded, and the listeners will not receive any events (because no actual changes have been performed on the data).
Executive summary: Use
AFTER_COMMIT if you can,
BEFORE_COMMIT if you need to modify the data before is is written to the database, and
IMMEDIATE only if you absolutely have to. Also, take a look at the rules section below, since the
RuleEngine can take care of many of those details for you.
Another group of events handled by the EventManager are the TransactionEvents. These events are fired before and after a transaction has changed its state.
To prevent confusion: The transactions we are talking about here are always user transactions, never database transactions (see the Guide to Transactions for a discussion of these terms).
You can register to receive events only from a specific TransactionManager or from all TransactionManagers.
A number of events may be generated during the life cycle of a transaction. Normally, events are fired when:
Each type of event occurs at most once during a single transaction's life cycle, some are even mutually exclusive (like COMMIT and ROLLBACK). The only exception to this rule is when a
GrasGXLException is thrown by a listener during the
BEFORE_ROLLBACK - in this case, it is possible to restart the operation, thus
triggering a second event of that type, or even perform a rollback instead of a commit
(or vice versa), which would trigger the appropriate event. It still holds for
AFTER_ event types, though.
Putting together all those events, this is what happens when a top-level transaction is committed, in the precise order:
BEFORE_COMMITTransactionEvent is sent to all listeners.
EventCouplingMode.BEFORE_COMMITis processed. Among the listeners is the RuleEngine, which executes all rules registered for
CouplingMode.DEFERRED. Every single time a listener has processed a single event, a consistency check is performed on any changed graph data elements.
AFTER_COMMITTransactionEvent is sent to all listeners. The RuleEngine is among the listeners again, this time executing
CouplingMode.DECOUPLEDrules (in separate transactions).
For more details on the events generated when nested transactions are involved,
please refer to the
Transaction API documentation.
Besides the different DRAGOS-specific types of events described above, the EventManager can also relay generic events. This facility is offered as a service to extensions and applications, and not used in the DRAGOS core itself. Right now, the only reason to use it would be asthetic: all events would run through the EventManager, thus providing a single responsible instance. But later on, when EventManager is extended, other advantages like network transparency would also become available to the clients automatically.
Please note that this is not the only way for an extension to use custom events! In fact, if your event can be expressed as a subclass of any of the pre-defined event classes, it is strongly encouraged to do so, especially for DataEvents, where you gain transaction-awareness for free!
The generic event service is offered through channels. A channel is identified by a subinterface of
java.util.EventListener, which the listeners implement.
If you want to broadcast
java.awt.event.ActionEvents through the EventManager, you would choose the
java.awt.event.ActionListener as both interface for your listeners and channel identifier.
You would add a listener like this:
// generate the event ActionEvent e = new ActionEvent(this, ActionEvent.ACTION_PERFORMED , "test"); // use reflection to get the receiving method Class parameterTypes = new Class; parameterType = ActionEvent.class; Method receivingMethod = ActionListener.class.getMethod("actionPerformed", parameterTypes); // fire the event EventManager.getInstance().fireGenericEvent(ActionListener.class, receivingMethod, e);
You can do the reflection once during initialization, and store the Method object, thus reducing the amount of code and time required to fire an event.
com.company.projectname.MyActionListener extend java.awt.event.ActionListener. The same technique can be used to simulate listening on specific instances of event sources, if the number of instances is limited.
To put it simply: Event handling should not mess with transactions.
The only allowed modification is setting the
state if an unrecoverable error occured. You may start a new or nested
transaction (depending on whether there is an active transaction),
but only if you close it before returning from event handling.
Anything else will most probably lead to random things breaking
at some random time.
The rule engine is a service running in the
DRAGOS kernel, like the
You just tell it which actions to perform when certain
patterns of events are matched, and it will take care of
This coupling mode executes actions during the
A transaction is committed through the following
BEFORE_COMMITphase. Besides the
RuleEngine, there might be other listeners for this type of event, and they too might modify the graph data. The difficulties that come with such undetermined behaviour (especially when debugging) are one reason to exclusively use the
RuleEnginefor transformations if possible, more reasons are given below.
This coupling mode executes actions during the
AFTER_COMMIT phase of the original
transaction, but each wrapped inside their own, new
transaction. The main advantage is that in this case,
an action may set its transaction to "rollback only"
if anything goes wrong, without affecting the main
transaction or the actions of any other rules.
Especially when executed after very complex transactions involving many changes, this mode may yield better performance: The transaction overhead will probably be more than compensated by the vastly reduced number of changed entities that have to be verified by the GraphPoolChecker and SchemaChecker.
If given the choice, always define your logic as rules instead of writing an event listener yourself. Not only does this reduce the amount of application code and the associated chance of bugs. The common format for rules also means the application is easier to understand and maintain compared to custom code. And if necessary, changing rules at runtime is much easier than deploying and using a new class.
What has been said regarding event handling in general also applies here: Manipulation of transactions is limited. Please refer to the subsection with the same name in the event section for details.
As a PEGS developer, you have little to deal with events, and only in a quite straightforward way:
Whenever you modify the graph data, create the respective event as soon as you are done and hand it over to the EventManager, which will take care of the rest. For a complete list of event types, look at the API documentation.