Problem : Trigger program to log changes and inserts

Problem : Trigger program to log changes and inserts

I really need to stop Windows type programming and get back to RPG….
That being said, we are a JDE shop and my boss told me he wants me to put a “Trigger” on a file to log inserts and changes.
(Years ago, I added a trigger to another file to do something different.)

So I start remembering my old program (code gotten from this very web site) and well I am just stuck on one part. (I think)
The File name is F4102.. I made a copy of the file and called it FG4102AUD.

I’m stuck on the last part.. In the Sub SWRTF I am just trying to copy the “After” image into the DLOG data structure so I can then write the record. (dont laugh, I have been trying for hours to figure this out again)

Help!!

Code Snippet:
0001.00 H/TITLE PG4102TRG – F4102 TRIGGER PROGRAM FOR AUDIT LOG
0002.00 H* —————————————————————
0003.00 H*
0004.00 H* PROGRAM REVISION LOG
0005.00 H* ——————–
0006.00 H*
0007.00 H* Date Programmer Nature of Revision
0008.00 H* ——– ———- ———————————
0009.00 H* 12/21/07 MAISAD22 Creation of Program.
0010.00 ***********************************************************
0011.00 H
0012.00 H Option(*NODEBUGIO)
0013.00 H DftActGrp(*No)
0014.00 H ActGrp(‘Trigger’)
0015.00 ***********************************************************
0016.00 FFG4102AUD UF A E DISK USROPN
0016.01 D DSLOG E DS EXTNAME(FG4102AUD)
0016.02 D PREFIX(L_)
0017.00 ***********************************************************
0018.00 * THE FOLLOWING WORKS LIKE AN *ENTRY PLIST:
0019.00 D MAINLINE PR EXTPGM(‘F4102’)
0020.00 D BUF LIKEDS(PARM1)
0021.00 D LENGTH 10I 0
0022.00 D MAINLINE PI
0023.00 D BUF LIKEDS(PARM1)
0024.00 D LENGTH 10I 0
0025.00 ***********************************************************
0026.00 D CMDTEXT DS 80
0027.00 D LIB1 7 16A
0028.00 D FILE1 26 35A
0029.00 D CMDTEXT2 DS 80
0030.00 D LIBFILE2 30 50A
0031.00 D CMDTEXT3 DS 80
0032.00 D FILE2 39 47A
0033.00 D FILE3 60 68A
0034.00 ***********************************************************
0035.00 ** STANDARD SPECIFICATION FOR ALL TRIGGER PROGRAMS
0036.00 ***********************************************************
0037.00 D PARM1 DS QUALIFIED
0038.00 D FILE 10A
0039.00 D LIBRARY 10A
0040.00 D MEMBER 10A
0041.00 D EVENT 1A
0042.00 D TIME 1A
0043.00 D COMMITLOCK 1A
0044.00 D RES1 3A
0045.00 D CCSID 10I 0
0046.00 D DBRRN 10I 0
0047.00 D RES2 4A
0048.00 D BOFFSET 10I 0
0049.00 D BLEN 10I 0
0050.00 D BNULLOFFSET 10I 0
0051.00 D BNULLLEN 10I 0
0052.00 D AOFFSET 10I 0
0053.00 D ALEN 10I 0
0054.00 D ANULLOFFSET 10I 0
0055.00 D ANULLLEN 10I 0
0056.00 **——————————————
0057.00 ** CONSTANTS
0058.00 D BEFOREEVENT C CONST(‘2’)
0059.00 D AFTEREVENT C CONST(‘1’)
0060.00 D INSERT C CONST(‘1’)
0061.00 D UPDATE C CONST(‘3’)
0062.00 D DELETE C CONST(‘2’)
0063.00 D READ C CONST(‘4’)
0064.00 ** STANDARD WORK FIELDS
0065.00 D ERROR S N
0066.00 ***********************************************************
0067.00 ** USE ORIGINAL FILE TO DEFINE BEFORE AND AFTER IMAGE
0068.00 **
0069.00 D PTR_BEFORE S *
0070.00 D DBDSB E DS EXTNAME(F4102)
0071.00 D BASED(PTR_BEFORE)
0072.00 D QUALIFIED
0073.00 D PRT_AFTER S *
0074.00 D DBDSA E DS EXTNAME(F4102)
0075.00 D BASED(PTR_AFTER)
0076.00 D QUALIFIED
0077.00 ** ORIGINAL DB DEFINITION TO POPULATE
0078.00 D DBDS E DS EXTNAME(F4102)
0079.00 ***********************************************************
0080.00 DARY S 80A DIM(5) CTDATA
0081.00 ***********************************************************
0082.00 ** IF CALL WITH NO PARMS THEN EXIT
0083.00 C IF %PARMS = 0
0084.00 C EVAL *INLR=*ON
0085.00 C RETURN
0086.00 C ENDIF
0087.00 ** POPULATE DATA STRUCTURES USING MEMORP POINTERS
0088.00 ** BEFORE IMAGE
0089.00 C IF BUF.EVENT = UPDATE OR
0090.00 C BUF.EVENT = DELETE OR
0091.00 C BUF.EVENT = READ
0092.00 C EVAL PTR_BEFORE = %ADDR(BUF) + BUF.BOFFSE
0093.00 C ENDIF
0094.00 ** AFTER IMAGE
0095.00 C IF BUF.EVENT = UPDATE OR
0096.00 C BUF.EVENT = INSERT
0097.00 C EVAL PTR_AFTER = %ADDR(BUF) + BUF.AOFFSET
0098.00 ********
0099.00 C EXSR SWRTF
0100.00 C ENDIF
0101.00 ********
0102.00 ** SET FIELDS
0103.00 C** EVAL DBDSA.ZAALPH = ‘MY VALUE’
0104.00 C** EVAL DBDSA.ZADSC1 = ‘MY VALUE’
0105.00 ** & EXIT
0106.00 C EVAL *INLR=*ON
0107.00 C RETURN
0108.00 C*
0109.00 C* END MAINLINE PROGRAM
0110.00 C* ——————–
0111.00 **********************************************************
0112.00 * SUBROUTINE SWRTF
0114.00 **********************************************************
0115.00 C SWRTF BEGSR
0126.00 *
0126.01 C MOVE DBDSA DSLOG
0126.03 C IF NOT %OPEN(FG4102AUD)
0126.04 C OPEN FG4102AUD
0126.05 C ENDIF
0126.06 C WRITE IG4102AUD
0126.07 C IF %OPEN(FG4102AUD)
0126.08 C CLOSE FG4102AUD
0126.09 C ENDIF
0189.00 C ENDSR
0190.00 C*****************************************************************
Solution : Trigger program to log changes and inserts

A journal monitoring function generally consists of two programs. One program sets up the environment with the RCVJRNE command; the other program is the ‘exit program’ that actually receives the entries. The exit program is indicated with the RCVJRNE EXITPGM() parameter.

The controlling program sets up the parms on the RCVJRNE command. Those are set according to whatever rules you choose. It also receives control back whenever the RCVJRNE command ends. The command ends essentially when the *exit* program tells it to end.

The exit program can decide to end for a number of reasons. The RCVJRNE command has a DELAY() parm, for example, that is used as a potential escape mechanism. If you set DELAY(30) and no new entries appear on the journal within 30 seconds, then the exit program is called anyway. The parms for the exit program contain an indicator that tells the exit program that this is merely an empty ‘delay’ cycle. The exit program could, for example, take the opportunity to check job status to see if anyone had requested ENDJOB. If ending was requested, the exit program sets the indicator parm to the ending value and the RCVJRNE command returns control to the outer program. Otherwise, the indicator is left alone and the next delay cycle starts.

For whatever reason, the exit program might decide that it’s time to end. In my programs, I’ll usually take the name of the journal, the name of the receiver, the journal entry timestamp and the entry sequence number from the last entry that came through and stash it someplace, perhaps as an entry on a user index. (Before *USRIDXs were easily available, I would’ve stashed it all in a *DTAARA.)

I stash that info so that the outer, control program can retrieve it the next time things start up. The info is used for a kind of ‘checkpoint restart’. You can code it to always start with the next entry to arrive or to start at some point in the past (the checkpoint). A checkpoint lets you end and restart the job without missing entries that are added while the job’s not running.

You can use a storage container such as a *USRIDX/*DTAARA to communicate between the control and exit programs. A *USRIDX lets you pass all kinds of info back and forth with a single object, but *DTAARAs can make it easier to change the data manually if you want to exercise some external control.

For external control, though, I usually use a *DTAQ. My exit program would receive entries from the *DTAQ to see if there was an entry with a value of ‘*ENDMONITOR’ or whatever. Using a *DTAQ leaves open the possibility of sending all kinds of future commands as the need might arise. The control program would clear the *DTAQ as one of its first steps upon startup to ensure that no entries were left over from any previous power failure or other disaster.

Hmmm… getting too far from the topic here…

Time to mention again that a trigger program can do what you want to do. Dave already has covered that according to the question that was asked and should be given credit. If journal monitoring takes over the thread, it _should_  be done in a new topic; that becomes a topic that later members can search for and find.

But general answers for your remaining questions…

The RCVJRNE command can be prompted (and actually even run) from a command line. This lets you review the available parms an [help] to get a picture of how narrow or broad you can make your selection criteria. In particular, you can name a single specific file or *ALLFILE or list up to 300 files for a given monitor process. Since you’re really interested in one file, the exit program will only fire when that file causes an entry.

You can have one program per file or have all files handled by a single program; it depends on how you do your coding. By running it in its own activation group and not closing it down when it returns, there shouldn’t be much of a performance hit.

It _shouldn’t_ affect existing journal processing at all as long as you don’t establish locks on the journal objects that conflict with iTera. (I can’t think of why you’d want to.)

Actually, I’d probably look at iTera to see if they provide any kind of interface for just this kind of thing. It’d be irrational of such a vendor not to accommodate this if there was an likely chance of interference. If such an interface is supplied, iTera documentation would be the place to look.

There shouldn’t be any impact on journaling itself.

You can determine what journal is involved by running DSPFD over the file you’re interested in. Scan down to see the name of the journal plus info on what types of journal entries are created. When you know the name of the journal, you can use the WRKJRNA command to find current details about receivers. However, you’ll often simply use the special values *CURRENT and/or *CURCHAIN rather than needing to know an actual receiver name.

If iTera is running, you shouldn’t need to worry about receivers at all for this (except potentially for checkpoint restart stuff, at which points your programming will have kept track anyway). If iTera was installed and configured correctly and journaling is running correctly, then that part is already handled.

I generally create a subsystem specifically for this kind of ‘never-ending program’ function. I don’t like using the standard subsystems because I want to have total control over all of the work management aspects. But you can use default elements without much trouble. You could, for example, submit the job to the standard QSYSNOMAX *JOBQ. That usually feeds into the QSYSWRK subsystem. Better would probably be the QUSRNOMAX *JOBQ for the QUSRWRK subsystem.

I’m into wringing as much as I can out of systems, so I avoid running my own stuff in the same subsystem as IBM-supplied server functions. I create shared memory pools and private pools and control all work routing to keep things in there dedicated areas — including all IBM-supplied functions. That takes a lot of setup work, though, and few want to go that route. You can do fine by using QSYSNOMAX and running in QSYSWRK or going to any further level.