J. Brisbin

Just another Wordpress.com weblog

Using Java and RabbitMQ for AS/400 Physical File Triggers

with one comment

Integrating RPG and Java can often be a real pain. Since the CLASSPATH has to be set upon the first invocation of anything Java-related and persists throughout the lifetime of that activation group (in the case of *CALLER, until the user logs off), setting the CLASSPATH to properly include all your code’s third-party dependencies can often create problems with other code that might have a different CLASSPATH requirement. Writing an RPG trigger that uses the JNI to manipulate Java then, has an additional requirement on how it can run. This may be very undesirable, particularly in the context of a database trigger, which is meant to be run no matter what else is going on in the user’s environment. I’ve tried to solve this problem by invoking Java-language classes from RPG-language triggers using the Qshell.

The Java Trigger Lifecycle

The first thing I felt I needed was a fixed and known lifecycle for triggers. The TriggerProgramLifecycle interface defines this lifecycle, which the TriggerProgramInvoker honors:

public interface TriggerProgramLifecycle {

  /**
   * Initialize all internal objects for use.
   */
  public void init();

  /**
   * Implementations should specify their command-line options here.
   */
  public void setOptions();

  /**
   * Implementations should set their own options and connect to the
   * RabbitMQ server here.
   */
  public void setMqOptions();

  /**
   * Finish process and close all resources.
   */
  public void complete();
}

In addition to the lifecycle interface, trigger programs implement (via inheiritance from the AbstractTriggerProgram abstract class) the java.lang.Runnable interface. I don’t run these programs in their own thread, but it made sense to use the Runnable interface in case I wanted to add that functionality in the future.

Calling the Trigger

The TriggerProgramInvoker is a standard Java class with a static “main” method, which processes command-line options and switches using the commons-cli package. Each trigger program can define its own switches, depending on the mode of operation and what data you’re passing to it.

This also works very, very well for testing, since you’re simply calling a Java class from the command line. You can test to your heart’s content off the 400 then upload your jar file to its home on the IFS and your RPG code never knows the difference.

To invoke this trigger, I wrote a QSH script which uses a special trigger program invoker that calls the lifecycle and run methods in the following order:

// Create an instance of the trigger
Class clazz = Class.forName(clazzName);
TriggerProgram pgm = (TriggerProgram) clazz.newInstance();

// Set command-line options
pgm.setOptions();

// Pass the trigger program options we were passed
pgm.setCmdArgs(cmdArgs);

// Set RabbitMQ options
pgm.setMqOptions();

// Initialize program
pgm.init();

// Perform the real work
pgm.run();

// Complete/close resources
pgm.complete();

RPG Code

To run this trigger from an RPG-language trigger program, you need to set up several data structures that point to the system-defined trigger structure and the file description for the physical file to which the trigger is attached. In this case, CALIB/SRC,TRIGGERDS is just a generic trigger data structure the RPG folks use for any and all triggers. The structure “AfMYFILE” bases its data structure on the physical file and, since we’re only an *AFTER trigger, we cloud leave the “Af” prefix off. I chose to keep it, just for the sake of consistency with our other trigger programs:


 /COPY CALIB/SRC,TRIGGERDS
D  NulTypePtr     S               *
D  TypeBin4       S              9B 0 Based( NulTypePtr )
D  TypeChr        S              1A   Based( NulTypePtr )
D  TypeSysNam     S             10A   Based( NulTypePtr )
D  TypePtr        S               *   Based( NulTypePtr )
D  TgBufLen       S                   Like( TypeBin4 )
D  TgBfrPtr       S                   Like( TypePtr )
D  TgAftPtr       S                   Like( TypePtr )
D  TgBufSiz       C                   Const( %Size( TgBufChr ) )
D*
D AfMYFILE      E DS                  ExtName( MYFILE )
D                                     Prefix( Af )
D                                     Based( TgAftPtr )

This particular trigger program is an *AFTER trigger, so we’re only concerned with mapping onto the TgAftPtr pointer, which maps the trigger data structure over the file description for MYLIB/MYFILE.

QSH Call

Since we’re going to be submitting this trigger into its own job, we need to use the QCMDEXC program.


D* ***************************************************************
D Runcmd          PR                  ExtPgm('QCMDEXC')
D  Cmdstr                     3000A   Const OPTIONS(*VARSIZE)
D  Cmdlen                       15P 5 Const
D  Cmddbcs                       3A   Const OPTIONS(*NOPASS)
D*
D cmdString       S           3000A   Varying
D q               C                   ''''
D*
D* ***************************************************************

This gives us enough room to create a QCMDEXC string that calls our trigger QSH script, passing all the command-line options the trigger program requires.

Getting the Row Data

Since triggers pass all the data for the row from both before the operation and afterward, we need to map our data structure over the system-defined trigger data structures:


C     *ENTRY        PLIST
C     TgBufDs       PARM                    TgBufDs
C     TgBufLen      PARM                    TgBufLen
C*
C* GET FIELDS FROM BEFORE CHANGE
C                   Eval      TgBfrPtr = %Addr(TgBufAry(TgBfrOfs + 1))
C                   Eval      TgAftPtr = %Addr(TgBufAry(TgAftOfs + 1))
C*

These are standard to almost every RPG-language trigger program.

Building the QCMDEXC String

In this trigger, we’ll be submitting a job to run outside the context of this trigger. This will make the trigger run faster, with the risk that our submitted job might not run correctly. In this case, that’s not a big issue, so submitting a job will work fine.


 /free
  cmdString = 'SBMJOB CMD(QSH CMD(''/path/to/trigger.sh ' +
              'com.npci.cloud.iseries.mylib.MySpecialTrigger '+
              '-f ' + TgFile +
              AfCONO + ' ' +
              AfSTONO + ' ' +
              %trim(%char(AfEFFDA8)) + ' ' +
              'JOBD(WEBLIB/WEBSVCD) JOBQ(WEBLIB/WEBSVCQ) MSGQ(*NONE)';

  Runcmd(CmdString:%LEN(%TRIMR(cmdString)));
 /end-free

This block of free RPG code is what we’ve been building up to. This is the meat of calling a Java-based trigger from RPG code. This SBMJOB does several things:

  1. Calls a QSH script–/path/to/trigger.sh–which is a special script that runs our trigger program invoker, which in turn calls all the right methods on our trigger program, as described above.
  2. Passes QSH the class name of the trigger (provided by the Java developer).
  3. Passes the trigger program a -f switch, which tells the trigger program on which physical file it’s being invoked (NOTE: TgFile is defined in the system-wide trigger data structure COPY’d in D specs, above).
  4. The “after” trigger data is prefixed with “Af”, so to pass the trigger program our row data, we pass the fields “CONO”, “STONO”, and “EFFDA8″.
  5. In this case, we’re also specifying a job description and queue in which to run and suppressing output to prevent the SBMJOB completion notification from popping up on the user’s screen as a break message.

Finish

At this point, the Java trigger program is operating on the data and the RPG code can exit:

C* END OF PROGRAM
C                   SETON                                        LR
C                   RETURN

The RabbitMQ Integration

Now that we’ve passed the data from our RPG code into our Java-language trigger, we can craft a JSON message to send to a RabbitMQ queue:

{
  "company": 1,
  "storeNumber": 1134,
  "effectiveDate": 20100413
}

There’s no magic at this point. It’s all very straightforward RabbitMQ Java API calls.

Why do it this way?

As with anything, this is just one of many ways to accomplish the goal of integrating RPG and Java code. We also do a fair amount of JNI programming in RPG, doing direct manipulation of Java objects. But in this case, I wanted a way to run Java-based trigger programs that don’t introduce any new dependencies into the calling programs. I didn’t want CLASSPATH issues interfering with the user’s green screen apps. I also wanted something I could test locally, then upload to the 400 when I was finished with it.

With this system, our RPG code can send messages into my RabbitMQ servers, where waiting consumers are ready to go do subsequent updates based on the results of the RPG trigger calls.

About these ads

Written by J. Brisbin

April 13, 2010 at 6:51 pm

Posted in RabbitMQ, The Virtual Cloud

Tagged with , ,

One Response

Subscribe to comments with RSS.

  1. […] This post was mentioned on Twitter by alexis richardson, J. Brisbin. J. Brisbin said: Updated the blog with two new HOWTOs: http://bit.ly/bQ3CLp and http://bit.ly/c8CNF6 #vcloud #rabbitmq #systemi […]


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: