Recently I was pointed in the direction of a blog post requesting hooks for opening / creating / saving etc of documents. With the next release of Notes (9.0.2??) due out later this year, I suspect the chances of a feature request getting in now is quite low. The subsequent release will probably not then come until some time next year. So that means a long wait to get something that modifies LotusScript, Java and Server-Side JavaScript core Domino Object Model classes, as well as contributing some class to handle the events.
Fortunately, the Domino Java API is more easily extensible in XPages and related technologies*. And the OpenNTF Domino API has had listeners for Events for some time. An investigation will may also shed light on what’s involved just to manage custom events, without the implications of multiple languages, access from agents as well as XPages or requirements for new design elements to support developers who prefer SSJS. (I don’t think Script Libraries would be a viable option, because the code would need something to define it as a listener, so the core API would know to use it without custom methods to register the listener in all places.)
Listeners in ODA
So far listeners have only been added to the Database
and Document
class, and only for certain methods called from SSJS / Java (obviously only calls that go through the API and, as we shall see, have them loaded into the relevant Database object). The events (which can be found in org.openntf.domino.ext.Database
) listened for currently are:
- BEFORE_CREATE_DOCUMENT
- AFTER_CREATE_DOCUMENT
- BEFORE_DELETE_DOCUMENT
- AFTER_DELETE_DOCUMENT
- BEFORE_UPDATE_DOCUMENT
- AFTER_UPDATE_DOCUMENT
- BEFORE_REPLICATION
- AFTER_REPLICATION
- BEFORE_RUN_AGENT
- AFTER_RUN_AGENT
The Listener Class
The first thing that needs to be done is to create a Java class that implements the IDominoListener
interface. (Sorry, SSJS won’t work here because we need a class with properties and methods, not a function). In the OpenNTF Domino API Demo Database I called it TestDocumentListener
, because it listens for document-related events. The Java class needs two methods, getEventTypes()
and eventHappened()
, as detailed in the Javadoc (formatting of the code in the examples seems a bit broken in the Javadoc, so I’ll include it here!) The first method returns a list of events for which code should be triggered and is used when registering the listener; the second method is triggered when any event occurs.
-
public List<EnumEvent> getEventTypes() {
-
ArrayList<EnumEvent> eventList = new ArrayList<EnumEvent>();
-
eventList.add(Events.AFTER_CREATE_DOCUMENT);
-
eventList.add(Events.AFTER_UPDATE_DOCUMENT);
-
return eventList;
-
}
This is very simple. In our class we’re saying that this class only uses the event types AFTER_CREATE_DOCUMENT and AFTER_UPDATE_DOCUMENT. The first happens in Database.createDocument
, after the create has happened. The second happens in Document.save
, after the save has happened.
-
public boolean eventHappened(IDominoEvent event) {
-
try {
-
if (event.getEvent().equals(Events.AFTER_CREATE_DOCUMENT)) {
-
// Do something and return true if successful;
-
}
-
if (event.getEvent().equals(Events.AFTER_UPDATE_DOCUMENT)) {
-
// Do something and return true if successful;
-
}
-
return false; // something went wrong, maybe an EnumEvent not implemented
-
return false;
-
}
-
}
This second method is also straightforward, checking the event passed in and running code accordingly. For example, this could update a scoped variable to show how many documents the user has updated or created in a session, or how many documents have been created or updated since the application was loaded into memory.
Registering the Listener
Then this class you’ve created implementing the IDominoListener
interface needs adding as a listener to the relevant Database object, everywhere you call it. This may seem onerous, but I’ve got into the habit in many places of adding a getDataDatabase()
utility method. In OpenNTF Domino API Demo Database, I used a getCurrDb()
utility method, which calls addAllListeners()
. Here is the code:
-
public static Database getCurrDb() {
-
Database retVal_ = Factory.getSession().getCurrentDatabase();
-
addAllListeners(retVal_);
-
return retVal_;
-
}
-
public static void addAllListeners(Database currDb) {
-
currDb.addListener(new TestDocumentListener());
-
}
All this does is add a new TestDocumentListener
object to the current database, so that when the database.create
method and document.save
methods are called, this is one of the listeners checked.
When The Listeners Are Triggered
It’s also worth looking at what happens in ODA itself when the events are fired. It first calls a generateEvent()
method in the relevant class, which is public, so could be called from anywhere that uses ODA. The method returns an instance of the IDominoEvent
interface. The IDominoEvent
takes an implementation of the EnumEvent
interface, a source (e.g. the Document for BEFORE_UPDATE_DOCUMENT event or the Database for the AFTER_CREATE_DOCUMENT event), a target (the Database in our implementations) and a payload (a Java Object, not used currently in any of our implementations). The EnumEvent
interface of course can also be extended to add additional events.
Once the code has an instance of the IDominoEvent
interface, it passes it to the fireListener()
method. This is what retrieves all the listeners registered to the Database object and calls the eventHappened
method of each. As we’ve seen, this will typically check for the current EnumEvent
being triggered and, if found, run some code. The beauty of all this is that with a new enum implementing EnumEvent
and a wrapper around a method, a new Listener could be added and the design model extended. And custom payloads could be added to the events, if required.
Summary
In the demo database, all the listener does is capture the number of documents created or saved. Another potential use might be to save a snapshot of the on-disk document before performing a save in the BEFORE_UPDATE_DOCUMENT event or creating a log in the BEFORE_DELETE_DOCUMENT event.
But, of course, it’s critical that the listeners are registered onto the Database object that’s loaded. In the future, it may be something that can be loaded via the faces-config, for example. But that would need a more sophisticated implementation (parsing the XML, generating an error if there’s an error in the XML etc).
* Daniele Vistalli has done considerable work in CrossWorlds building a mechanism to use OpenNTF Domino API within a Websphere Liberty Profile server. I’ve had some success using OpenNTF Domino API in an OSGi plugin application, though it needs some improvements.