Considering Purpose and Design
Our purpose is to design a framework for triggers in the Sun Java System Directory Server using its Plug-in API as a foundation. We might call them LDAP triggers. With triggers, we mean an abstraction that allows us to define an action that must be invoked when an event occurs. The action might be simply recording an entry in a change log, sending a notification to an administrator, or changing an attribute of another logically associated entry. Computer scientists like to remark that by means of triggers, a data repository is turned into an active database; that is, the repository's state transactions are not only a consequence of external actions, but also the result of internal events.
Overall a framework for triggers does the following:
Supports an administrator's manipulation of triggers (creation, deletion, inactivation, and so on.)
Supports predefined actions (for example IGNORE, skip the LDAP operation, LOG to log the action, and so on)
Supports custom actions (for example, the ability to execute user functions loaded from external libraries)
Allows user interaction based on a SQL-like language
In our example, triggers are administrator's objects (replication agreements are an example of administrator's objects), and they cannot be instantiated by non-privileged users. To enable this feature, a non-trivial effort is required to manage permissions and rights.
To design a trigger framework, initially consider the following requirements:
The administrator, through a client console or command line application, can manipulate triggers through simple commands (create, delete, enable, and so on).
The framework asks predefined actions that an administrator can choose from when creating triggers.
The framework asks for custom actions, possibly implemented by externally, user-provided libraries.
A language for the trigger manipulation, that is, a collection of trigger instructions (such as CREATE TRIGGER, DELETE TRIGGER, and so on) that simplify user interaction with trigger objects.
After we are certain that we know what the customer wants, we ask: "Do we have the right tool or technologies to realize this framework in Sun Java System Directory Server?" and "Is the solution feasible?"
A UNIX expert would easily figure out which tool is needed to create a small language in C: YACC. It allows you to specify a grammar for your language, that, with a lexical analyzer specification provided by you, greatly simplifies the task of writing a compiler. In fact, the YACC task is to generate a compiler for you; it is the kind of tool often referred to as the compiler of compilers.
We have the parser that parses the trigger instructions, but we need a client command-line-based application to accept administrator commands to be parsed. At first we might be tempted to embed the parser in the client application, but this approach is not a good solution. Instead, it's better to run the parsed commands directly in the server, embedding them into a plug-in. This approach permits third parties to write their own client applications. The database server follows a similar architectural solution: third parties embed the SQL parser component in the server itself, and allow client applications, written in different languages and for various platforms, to send plain SQL text.
How do you send the commands the administrator enters in the client application to the LDAP server? Let's put it into context with an extended operation. We could pass the trigger statements to the server-side parser through an extended operation invocation, with the invocation argument being the trigger commands.
In our design, the parser is responsible only for parsing the instructions it turns into statements, like CREATE TRIGGER, in a data structure for an internal LDAP operation. That is, if an administrator issues a command such as DELETE TRIGGER mytrigger, we program the parser to produce a data structure, for example, ParserOutput, which is passed down to a routine that performs the actual data operation.
Intuitively, in a directory server we could record the triggers as LDAP entries. Standard schema, however, does not have an object class suitable for our objects (triggers), therefore we need to extend it.
The object class definition chosen for triggers as it might appear in the 99user.ldif user schema file is as follows:
objectClasses: ( trigger-oid NAME 'trigger' SUP top STRUCTURAL MUST ( cn $ enabled ) MAY ( action $ actiondn $ before $ explain $ externallib $ on ) X-ORIGIN 'user defined' )
The information we choose to store about triggers is as follows:
TABLE 1 Storing Trigger Information
Item |
Description |
cn |
Its name, as typed by the user |
enabled |
Flag that indicates if the trigger is active |
operation |
The trigger run on this LDAP operation (ADD, DELETE, etc.) |
on |
The DN of the entry for which the trigger is registered |
action |
Action code that specifies which action to perform |
actiondn |
For some actions, it indicates on which DN the action must be performed |
before |
1 if this is before a trigger; 0 is it is after a trigger |
externallib |
When the action is external, this attribute contains the name of the library to load |
explain |
As the name suggests, it stores the instruction that created this trigger (it is retrievable by using the explain trigger <trgname> command |
Triggers are stored flatly under a specific branch, ou=triggers,o=sunblueprints in the previous code example.
Of course when a data event occurs, for example, deleting an entry, nothing happens unless we override the default LDAP operation. That's why we need a pre-operation (before triggers) and post-operation (after triggers) plug-in, which is merged in a single plug-in. We address this in the next section. The pre- and post-operation plug-in is the most important component of the framework, because it is where the trigger runs.
Now we have all the pieces to start building a solution. First, let's summarize how the various components functionally cooperate and concur to serve user requests. The logical flow is as follows:
The administrator enters trigger statements at the client application prompt.
The client application issues an extended operation call, passing as argument the trigger statements verbatim.
On the server side, the extended operation routine invokes the parser to parse the user commands, which turns them into trigger data structures and executes all the trigger operations.
The client application receives a response and reports it to the user.
When an event occurs (an LDAP operation is issued by a user), the pre- and post-operation plug-in searches for triggers registered with the DN of the entry being affected by the data event. If found, it applies the actions associated with the triggers.
In this article, we implement the framework in our code example to provide an advanced example of implementing and using extended operations and directory plug-ins.