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 anAssignevent is needed. On callback,bufis the modified buffer, theoldValueCopyis the value before the assign took place and thefieldNameis 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 receiveCreateevents. Thebufparameter will contain the new record.public void delete(Buffer buf)
To be implemented to receiveDeleteevents. Thebufparameter will contain the deleted record.public void find(Buffer buf)
To be implemented to receiveFindevents. Thebufparameter will contain the record loaded from database.public void write(Buffer newBuf, Buffer oldBuf)
To be implemented to receiveWriteevents. 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)
{
externalProcedure(DemoGenericTrigger.this, new Block((Body) () ->
{
RecordBuffer.openScope((DataModelObject) buffer);
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(final Buffer newBuf, final Buffer oldBuf)
{
externalProcedure(DemoGenericTrigger.this, new Block((Body) () ->
{
RecordBuffer.openScope((DataModelObject) newBuf, (DataModelObject) 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 theeventNameevents of all tables in the specificdatabase. ThetriggerClassNameparameter denotes the full name of the trigger class. The last parameter, iftruewill suppress the execution of any existing schema trigger. This form of the method can be used only withcreate,delete,findandwriteevents.public String registerGenericTrigger(String eventName, String database, String triggerClassName, String property, Boolean override)
Register a generic trigger forassignevents of apropertyon all tables in the specificdatabase. ThetriggerClassNameparameter denotes the full name of the trigger class. The last parameter, iftruewill suppress the execution of any existing schema trigger. This form of the method can be used only withassignevents.public String deregisterGenericTrigger(String eventName, String database)
Deregister a generic trigger foreventNamein adatabase. This form of the method can be used only with triggers defined forcreate,delete,findandwriteevents.public String deregisterGenericTrigger(String eventName, String database, String property)
Deregister a generic trigger forAssignevents ofpropertyin adatabase. This form of the method can be used only withassignevents.
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
isbnfield. There is no trigger registered forbook-idso the session triggers acts in both iterations.
© 2023 Golden Code Development Corporation. ALL RIGHTS RESERVED.