Project

General

Profile

Generic Session Database Triggers

The Generic Session Database Triggers is a new feature added as an extension to OE triggers. As name suggests:
  • they are generic, meaning a trigger will handle events of same type from multiple tables. Currently, the selector is a database, but in future versions a more granular filter might be implemented, using regexps or wildcards, allowing the trigger to apply only to specific tables;
  • they are session bound. As normal session triggers, they are bound only to current user context and are automatically dropped when the procedure/method which reach the end of its scope. While a generic trigger is active, the normal session triggers are hidden and will not be executed. The schema triggers behaviour is unchanged: if the generic triggers were set with override and the schema triggers allows that, the latter will not be executed.

The generic trigger code is NOT written in ABL but pure Java. They are written by the customer and provided as individual bytecode, usually in stand-alone jars. These triggers allow a very low-level access to runtime items (buffers, old values). As such, the writer is responsible for OE specific details, like opening buffer scopes, transactions, handling parameters, etc. If additional tables are accessed, the scope management for it must also be done by the programmer. Failing to properly do the management will surely affect the server stability, which will have strange abends as consequences.

Java side

To install a generic trigger one first needs to implement it. This is done by extending the abstract class GenericDatabaseTrigger. This class exposes the following overridable methods:
  • public void assign(Buffer buf, BaseDataType oldValueCopy, character fieldName)
    To be implemented if an Assign event is needed. On callback, buf is the modified buffer, the oldValueCopy is the value before the assign took place and the fieldName is the name of the field affected. This last parameter is different from normal assign triggers because the same trigger can be configured for multiple fields/properties.
  • public void create(Buffer buf)
    To be implemented to receive Create events. The buf parameter will contain the new record.
  • public void delete(Buffer buf)
    To be implemented to receive Delete events. The buf parameter will contain the deleted record.
  • public void find(Buffer buf)
    To be implemented to receive Find events. The buf parameter will contain the record loaded from database.
  • public void write(Buffer newBuf, Buffer oldBuf)
    To be implemented to receive Write events. The buffer parameters will contain the record to be flushed to database and its original version.

The other methods found in this abstract class will NOT be invoked therefore they are marked as final to prevent their accidental override.

Here is a very simple example which only prints to stderr some details of the parameters of assign and write events:

package com.mycompany.myapplication.triggers;

import com.goldencode.p2j.persist.*;
import com.goldencode.p2j.persist.trigger.*;
import com.goldencode.p2j.util.*;

public class DemoGenericTrigger
extends GenericDatabaseTrigger
{
   @Override
   public void assign(Buffer buffer, BaseDataType oldValueCopy, character fieldName)
   {
      BufferImpl afterImage = (BufferImpl) buffer;
      System.err.println(
         "Generic ASSIGN(" + fieldName.toStringMessage() + "): [" + afterImage.name().toStringMessage() + "] " +
         afterImage.rowID() + " " +
         afterImage.dereference("isbn").toStringMessage() + " " +
         afterImage.dereference("book-id").toStringMessage() + " was: " +
         oldValueCopy.toStringMessage()      
      );
   }

   @Override
   public void write(Buffer newBuf, Buffer oldBuf)
   {
      BufferImpl afterImage = (BufferImpl) newBuf;
      BufferImpl beforeImage = (BufferImpl) oldBuf;

      System.err.println(
         "Generic WRITE After: [" + afterImage.name().toStringMessage() + "] " +
         afterImage.rowID() + " " + 
         afterImage.dereference("isbn").toStringMessage() + " " +
         afterImage.dereference("book-id").toStringMessage() + "\n" +
         "              Before:  [" + beforeImage.name().toStringMessage() + "] " +
         beforeImage.rowID() + " " +
         beforeImage.dereference("isbn").toStringMessage() + " " +
         beforeImage.dereference("book-id").toStringMessage()
      );

      // if needed:
      RecordBuffer rbAfter = afterImage.buffer();
      RecordBuffer rbBefore = beforeImage.buffer();
   }
}

This class must be compiled, packaged and provided into classpath of the application when FWD starts.

The bridge

The above class must be registered with FWD'a database trigger manager. The operations are performed using a com.goldencode.p2j.persist.trigger.TriggerManagerHandle Java object. It expose a set of methods which allow to register and deregister both kind of triggers:
  • public String registerGenericTrigger(String eventName, String database, String triggerClassName, Boolean override)
    Register a generic trigger for the eventName events of all tables in the specific database. The triggerClassName parameter denotes the full name of the trigger class. The last parameter, if true will suppress the execution of any existing schema trigger. This form of the method can be used only with create, delete, find and write events.
  • public String registerGenericTrigger(String eventName, String database, String triggerClassName, String property, Boolean override)
    Register a generic trigger for assign events of a property on all tables in the specific database. The triggerClassName parameter denotes the full name of the trigger class. The last parameter, if true will suppress the execution of any existing schema trigger. This form of the method can be used only with assign events.
  • public String deregisterGenericTrigger(String eventName, String database)
    Deregister a generic trigger for eventName in a database. This form of the method can be used only with triggers defined for create, delete, find and write events.
  • public String deregisterGenericTrigger(String eventName, String database, String property)
    Deregister a generic trigger for Assign events of property in a database. This form of the method can be used only with assign events.

All the above methods will return null if the operation was successful and a short text describing the failure error.

ABL code

To setup a generic trigger Direct Java Access must be used. Here are the lines needed to obtain the TriggerManagerHandle instance and to install a write and an assign trigger using the class above.

USING com.goldencode.p2j.persist.trigger.* FROM java.

DEFINE VARIABLE triggerManagerHandle AS com.goldencode.p2j.persist.trigger.TriggerManagerHandle.

triggerManagerHandle = new TriggerManagerHandle().
triggerManagerHandle:registerGenericTrigger("write", "fwd", "com.mycompany.myapplication.triggers.DemoGenericTrigger", No).
triggerManagerHandle:registerGenericTrigger("assign", "fwd", "com.mycompany.myapplication.triggers.DemoGenericTrigger", "isbn", No).

and this is the code for deregistering the triggers:

// using the 'triggerManagerHandle' variable defined above
triggerManagerHandle:deregisterGenericTrigger("write", "fwd").
triggerManagerHandle:deregisterGenericTrigger("assign", "fwd", "isbn").

Here is the full test procedure:

USING com.goldencode.p2j.persist.trigger.* FROM java.

DEFINE VARIABLE triggerManagerHandle AS com.goldencode.p2j.persist.trigger.TriggerManagerHandle.

triggerManagerHandle = new TriggerManagerHandle().
triggerManagerHandle:registerGenericTrigger("write", "fwd", "com.mycompany.myapplication.triggers.DemoGenericTrigger", No).
triggerManagerHandle:registerGenericTrigger("assign", "fwd", "com.mycompany.myapplication.triggers.DemoGenericTrigger", "isbn", No).

ON ASSIGN OF book.isbn OLD original DO:
   MESSAGE "Session Assign (Book.isbn)" rowid(book) book.isbn original.
END.

ON ASSIGN OF book.book-id OLD original DO:
   MESSAGE "Session Assign (Book.book-id)" rowid(book) book.book-id original.
END.

ON WRITE OF book OLD original DO:
   DEFINE VARIABLE hbook AS HANDLE. 
   DEFINE VARIABLE horiginal AS HANDLE. 

   hbook = BUFFER book:HANDLE.
   horiginal = BUFFER original:HANDLE.

   MESSAGE "Session Write (Book)" hbook::book-id
           hbook:BUFFER-FIELD("isbn"):BUFFER-VALUE() 
           horiginal:BUFFER-FIELD("isbn"):BUFFER-VALUE().
END.

CREATE book.
book.isbn = STRING(TODAY).
book.book-id = INT(TODAY).
RELEASE book.

FIND book WHERE book.isbn = STRING(TODAY).
DELETE book.

triggerManagerHandle:deregisterGenericTrigger("write", "fwd").
triggerManagerHandle:deregisterGenericTrigger("assign", "fwd", "isbn").

MESSAGE "No more generic triggers active!".

CREATE book.
book.isbn = STRING(TODAY).
book.book-id = INT(TODAY).
RELEASE book.

FIND book WHERE book.isbn = STRING(TODAY).
DELETE book.

and the expected result:

Generic ASSIGN(isbn): [book] 0x0000000000003e9c 09/28/23 0 was: 
Session Assign (Book.book-id) 0x0000000000003e9c 2460217 0
Generic WRITE After: [book] 0x0000000000003e9c 09/28/23 2460217
              Before:  [Book] 0x0000000000003e9c  0
No more generic triggers active!
Session Assign (Book.isbn) 0x0000000000003e9d 09/28/23 
Session Assign (Book.book-id) 0x0000000000003e9d 2460217 0
Session Write (Book) 2460217 09/28/23 
Notes:
  • there are two iterations of same operations: create, change/assign, release, find, delete;
  • to simplify the scenario, there are no schema triggers.
  • the generic WRITE hides the session Write while the former is registered. After deregistration, the latter is active back again.
  • the same stands for ASSIGN triggers, but the difference is that the generic assign is configured only for isbn field. There is no trigger registered for book-id so the session triggers acts in both iterations.

© 2023 Golden Code Development Corporation. ALL RIGHTS RESERVED.