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.

  1. public List<EnumEvent> getEventTypes() {
  2.     ArrayList<EnumEvent> eventList = new ArrayList<EnumEvent>();
  3.     eventList.add(Events.AFTER_CREATE_DOCUMENT);
  4.     eventList.add(Events.AFTER_UPDATE_DOCUMENT);
  5.     return eventList;
  6. }

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.

  1. public boolean eventHappened(IDominoEvent event) {
  2.     try {
  3.         if (event.getEvent().equals(Events.AFTER_CREATE_DOCUMENT)) {
  4.             // Do something and return true if successful;
  5.         }
  6.         if (event.getEvent().equals(Events.AFTER_UPDATE_DOCUMENT)) {
  7.             // Do something and return true if successful;
  8.         }
  9.         return false; // something went wrong, maybe an EnumEvent not implemented
  10.     } catch (Exception e) {
  11.         return false;
  12.     }
  13. }

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:

  1.     public static Database getCurrDb() {
  2.         Database retVal_ = Factory.getSession().getCurrentDatabase();
  3.         addAllListeners(retVal_);
  4.         return retVal_;
  5.     }
  6.     public static void addAllListeners(Database currDb) {
  7.         currDb.addListener(new TestDocumentListener());
  8.     }

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.

Leave a Comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Scroll to Top