Join The Queue With Open Access
August 21, 2013 Jon Paris
Note: The code accompanying this article is available for download here.
It has been a while since my last tip on RPG Open Access and now that everyone running IBM i V6 or 7 has access to it for no additional cost, I thought it was time to revisit the subject.
OA handlers operate in one of two modes: buffer and names/values. The buffer option performs better, but requires the handler to know the layout of the data in the buffer in advance. Names/values (NV) handlers, such as the one I introduced in the previous article, do not need to know the layout of data, because RPG supplies them with the full details of each field. As a result, it is much easier to write a truly generic handler using the NV approach.
After working with OA for awhile, I came to realize that there are situations where generic handlers can indeed be written using the buffer option. The simplest way to describe this is to say that this approach can be used when the handler neither needs to know, nor cares about, the content or layout of the data it is processing. An example that fits this model, and the one I will use in this tip, is a handler to process data queues. Many RPGers know how to use the QSNDDTAQ and QRCVDTAQ APIs to read and write to data queues, but wouldn’t it be nice to simply READ and WRITE to them, as you would with a file? I thought so, and so I set about writing the handlers to make it possible.
I had originally intended to write a single handler that would cater for both reading and writing the data queues. Later I decided that, since the reading and writing functions would rarely be used in the same program, developing separate input and output handlers would make more sense, as it would simplify the logic and therefore make it easier to explain. I also decided to restrict the initial version to non-keyed data queues. In later tips we will add that capability. In this tip we present the write handler. We will study the read handler later.
How do we identify the queue to be used? I decided in this instance to assume that the queue name was the same as the external file name and that it was in the same library as the file. You will see how this information is obtained by the handler when we study it in a moment.
Since the XML handler from the earlier tip did not cater for the handler being used by multiple “files” in a single program (or indeed in a single job for that matter), I decided to implement that capability in this example. As you will see, the mechanism OA provides for retaining the necessary data is both simple and elegant. Since a data queue doesn’t need to be “opened” and “closed” per se, we could have just made the open and close operations no-ops, but as you’ll see, there is some useful processing that can be done, particularly on the open.
Our design needs to include a method for handling errors. OA allows for two methods to notify RPG when a handler detects an error. You can either set a specific RPG status code or send an exception message. In this handler I opted to take the easy way out and set the RPG status. But, as you will see, before returning control to RPG I also use the DSPLY op-code to place an informational message in the job log to aid in problem resolution. We will look at the exception message approach in a later tip.
Time To Begin
To test the handler I wrote a simple program, which you can see below. The single most important part of this program is what you don’t see! No data queue APIs. Nothing but a simple output disk file. This file supplies the field layout for the message we want to write to the data queue. Here’s the code:
H DftActGrp(*No) Option(*NoDebugIO : *SrcSTmt) FTestDQ1 O E DISK Handler('SNDDQHAND1') D forever s n Inz(*Off) D count s 5i 0 /Free DoU forever; Dsply ('Enter char(20) test value') ' ' char1; If (char1 = 'end'); Leave; EndIf; count += 1; pckDec1 = count; date1 = (%Date() + %Days(count)); Write TestDQ1R; EndDo; *InLr = *On;
As in our previous example, the only notable part of the code is the Handler keyword on the F-spec. The rest of the code simply asks for test input, generates some additional data, and writes records to the file: TestDQ1. Nothing indicates that this program isn’t simply writing to a conventional disk file.
The Handler Logic
I’m going to focus on describing those aspects of the handler that are new and/or different from the previous example. If you haven’t yet studied that article, now would be a good time to do so.
Let’s start with the new data definitions.
(A) d stateInfo ds LikeDS(dQStateInfo_T) d Based(info.stateInfo) (B) d queueEntry s 64512a Based(info.outputBuffer)
The DS stateInfo is defined as being like the DS dQStateInfo_T, which is included in the program via a /Copy directive. It will be used to hold the information that is unique to a specific file (i.e., it is the state information) that we wish to retain from one call to the handler to the next. It is based on the pointer info.stateInfo, which is passed to us by OA. You will see when we discuss the processing of the Open op-code where this pointer originates. The RPG run time ensures that whenever the handler is called, it gets the correct pointer for the specific file being processed. This is the mechanism that allows a single handler to be used to process multiple files in a single job.
The second item (B) is the actual data that we will write to the data queue. By the time the handler is called, RPG has already done the hard work and built the fields in this buffer. By associating the field with the RPG-supplied pointer to the output buffer (info.outputBuffer), the handler has access to the data.
Processing Open Requests
The first thing to remember when writing a handler is that you will have to process an open, even if there is no explicit open coded in the RPG program (as is the case in our example). Hopefully, most of the code is fairly self-explanatory, but there are a couple of points to be made.
First at (C), we test to see if any state information has been created for the file by testing the pointer for null. Since we are opening the file, this is the expected situation. We then allocate enough dynamic memory to hold the information that we want to preserve for the file. The resulting pointer is directly placed in the OA stateInfo field and, as I noted earlier, RPG guarantees that it will return this pointer to us whenever we process operations for this specific file. To ensure that there is no “junk” left lying around in the allocated memory, we issue a clear op-code to reset the content of the memory to its RPG-defined defaults.
The next thing is to check if the data queue being used actually exists. This is done by calling the function GetDQInfo(), which is basically a wrapper for the QMHQRDQD API that retrieves information about the data queue. That information is placed in the variable queueInfo (D), which is part of the state information DS stateInfo. One part of this information is used at (E), where we check to see if the queue in question is keyed. The current version of the handler is not set up to use keyed queues, and so we fail the open if one is used.
// If no state info area yet allocate now and initialize (C) If info.stateInfo = *null; info.stateInfo = %Alloc(%Size(stateInfo)); Clear stateInfo; EndIf; // Validate that requested DQ exists status = GetDQInfo( info.externalFile.name : info.externalFile.library (D) : stateInfo.queueInfo : errorInfo ); If ( status <> statusOK ); Dsply ( 'Queue ' + info.externalFile.name + ' in library ' + info.externalFile.library + ' not available' ); info.rpgStatus = errImpOpenClose; // Issue error message if attempting to use a keyed queue (E) ElseIf ( stateInfo.queueInfo.keyLength > 0 ); Dsply ('Handler does not support the use of keyed queues'); info.rpgStatus = errIO; Else; info.rpgStatus = stsOK; Dsply ( 'File ' + %TrimR(info.externalFile.name) + '/' + %TrimR(info.externalFile.library) + ' now open'); Endif;
The first check is to ensure that the data will fit into the data queue (F). If the data is not too long, I call the API to write it to the queue. Note that the data is supplied by the queueEntry field we mentioned earlier, which is mapped to RPG’s output buffer. If it doesn’t fit, we issue an error message. I had originally intended this length validation to be part of the open processing, but since individual records in some kinds of files can have different lengths, RPG only supplies the record length on an individual write operation. I am hopeful that in future the RPG team might consider enhancing OA to provide a “longest record in file” value that could be checked on the open.
// Signal error if buffer length exceeds queue size (F) If ( stateInfo.queueInfo.msgLength >= info.outputBufferLen ); SendToDQ( info.externalFile.name : info.externalFile.library : info.outputBufferLen : queueEntry ); Else; Dsply ('Length of queue message exceeds buffer length'); info.rpgStatus = errIO; EndIf;
There’s really not a lot to the close operation, but there is a little “housekeeping” that needs to be done. The first thing to do is to release the dynamic memory that retains the state information. Having done that, we should then set the state information pointer to null just in case. Why bother? Because de-allocating the memory has no impact on the pointer (i.e., it is still valid, just not pointing to any memory that we own), and this can cause problems if the programmer attempts to issue an open again for the same file.
// Release storage holding state information and set pointer null Dealloc info.stateInfo; info.stateInfo = *null;
That is really all there is to it. As you may be realizing by now, the most difficult part of designing and coding an OA handler is the design portion. The basic coding to interface with RPG is quite straightforward and tends to be much the same from one handler to another. The design is the hard part.
In this example we had to determine how to derive the queue name and whether a buffer that exceeded the length of the queue data should be treated as an error (my choice) or simply truncated. We could, of course, add an option for the programmer to supply queue and library names to override the defaults. Similarly we could have parameterized the behavior relating to the length of the data. You get the idea.
In the next part of this series, we will look at the read handler and discuss the design points that arise when deciding just what constitutes an end of file condition when the “file” is a data queue. Until then, please download the code for this tip here, play with it and let me know if you have any questions or suggestions for future handlers.
Jon Paris is one of the world’s most knowledgeable experts on programming on the System i platform. Paris cut his teeth on the System/38 way back when, and in 1987 he joined IBM’s Toronto software lab to work on the COBOL compilers for the System/38 and System/36. He also worked on the creation of the COBOL/400 compilers for the original AS/400s back in 1988, and was one of the key developers behind RPG IV and the CODE/400 development tool. In 1998, he left IBM to start his own education and training firm, a job he does to this day with his wife, Susan Gantner–also an expert in System i programming. Paris and Gantner, along with Paul Tuohy and Skip Marchesani, are co-founders of System i Developer, which hosts the new RPG & DB2 Summit conference. Send your questions or comments for Jon to Ted Holt via the IT Jungle Contact page.