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 classGenericDatabaseTrigger
. This class exposes the following overridable methods:
public void assign(Buffer buf, BaseDataType oldValueCopy, character fieldName)
To be implemented if anAssign
event is needed. On callback,buf
is the modified buffer, theoldValueCopy
is the value before the assign took place and thefieldName
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 receiveCreate
events. Thebuf
parameter will contain the new record.public void delete(Buffer buf)
To be implemented to receiveDelete
events. Thebuf
parameter will contain the deleted record.public void find(Buffer buf)
To be implemented to receiveFind
events. Thebuf
parameter will contain the record loaded from database.public void write(Buffer newBuf, Buffer oldBuf)
To be implemented to receiveWrite
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 acom.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 theeventName
events of all tables in the specificdatabase
. ThetriggerClassName
parameter denotes the full name of the trigger class. The last parameter, iftrue
will suppress the execution of any existing schema trigger. This form of the method can be used only withcreate
,delete
,find
andwrite
events.public String registerGenericTrigger(String eventName, String database, String triggerClassName, String property, Boolean override)
Register a generic trigger forassign
events of aproperty
on all tables in the specificdatabase
. ThetriggerClassName
parameter denotes the full name of the trigger class. The last parameter, iftrue
will suppress the execution of any existing schema trigger. This form of the method can be used only withassign
events.public String deregisterGenericTrigger(String eventName, String database)
Deregister a generic trigger foreventName
in adatabase
. This form of the method can be used only with triggers defined forcreate
,delete
,find
andwrite
events.public String deregisterGenericTrigger(String eventName, String database, String property)
Deregister a generic trigger forAssign
events ofproperty
in adatabase
. This form of the method can be used only withassign
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/23Notes:
- 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 forbook-id
so the session triggers acts in both iterations.
© 2023 Golden Code Development Corporation. ALL RIGHTS RESERVED.