Rowing with Your OAR
October 20, 2010 Jon Paris
Note: The code referenced in this article is available for download here.
In my first Open Access for RPG (OAR) tip, I discussed the basic mechanics of an OAR handler from a conceptual perspective. In this tip, I am going to walk you through the implementation of a simple handler that generates XML files derived from the definition of the file specifying the handler. As you will see, its capabilities are very limited but it will introduce the basic coding principals. You’ll need to understand some of the details from my first OAR tip to follow this one.
Because it is the more flexible of the two operating modes of OAR, in this tip I am going to focus on Names/Values mode as it is the one that facilitates generic handlers.
Modifying the “User” Program
I started my handler with a very basic RPG program that read through a product file applying simple record selection logic. For selected records, it retrieves additional data from a second file and writes the result to a work file. The task I set myself was to modify this program to use an OAR handler to output the data instead as an XML file in the Integrated File System (IFS). The XML file will have this basic format:
<filename> <recordname> <fieldName1>field 1 contents</fieldName1> ... <fieldNamen>field n contents</fieldNamen> </recordname> </filename>
The first change required is to add the HANDLER keyword to the F-spec for the output file.
FXML_Out1 O E Disk
FXML_Out1 O E Disk Handler('ITJOAR_H1')
I chose for this example to use a program as the handler (ITJOAR_H1), but could have used a subprocedure in which case the Handler keyword might have looked something like this:
FXML_Out1 O E Disk Handler('ITJOAR_H(XMLHandler1)')
What else needs to be changed? Nothing. This is the only change that needs to be made–one of the many joys of the OAR approach. The only other thing that we might need to add in the future would be an additional parameter in which to pass (say) the name of the IFS output file to use. More on this later, but for now this simple addition is all we need.
Writing the Handler
Before we begin, download the code supplied with this article here to follow along with the references in this section.
As I noted in the first tip, the handler receives its information from the RPG run time as a single parameter in the form of a Data Structure (DS). So one of the first things my handler needs to do is to /COPY the standard IBM definitions into the program (A), as seen in the code available in the download here.
Once that is done I can reference the required template (QrnOpenAccess_T) in my procedure interface (B) by using the LIKEDS keyword. This is followed by the definition of the DS nvInput (C), which will hold the field name/value information. I will discuss this DS in more detail later when describing its use. Note that all of the IBM supplied structures use the TEMPLATE keyword (a 6.1 feature) and have the characters “_T” at the end of their names to identify them as templates.
The definitions that begin at (D) are related to the IFS file that will be created. Both the file name and buffer area can simply be increased in size if they are too small for your needs. These are followed by the definitions of the two RPG status codes (E) I am using in this program.
The actual handler logic begins at (F) with the SELECT operation that determines the actual I/O operation being requested. Because I named the input parameter info, the field that contains the requested operation code is info.rpgOperation. This is a numeric value and is tested against IBM supplied constants. Since the first request will (hopefully) be to open the file, let’s begin with that section which starts at (I).
Opening the File
I want to use the Names/Values option in this handler. That is requested by turning on the indicator useNamesValues. Once set this should not be changed while the file is open.
The next task is to form the IFS file name from the RPG file name which I do at (J). The file is then opened.
If the open fails, then I return an appropriate error number to the RPG run time by storing it in the parameter field rpgStatus (K). If the open is successful, I simply format the text for the XML root element name and write it to the file. That’s all there is to the open processing. The real action takes place on the write requests, the logic for which begins at (G).
Handling Write Requests
Before I can do anything I need to gain access to the Names/Values data that the RPG run time has supplied me. I do this by using the pointer supplied in the parameter data (namesValues) and use it to set the basing pointer for the structure nvInput that I defined at (C).
Before processing the fields, I create text for the XML element that marks the beginning of the record and add it to the buffer. The actual element name is formed from the record name (info.recordName). Since the purpose of this handler is to format and output all the fields in the record, I then set up a FOR loop based on the number of fields received (nvInput.num) and proceed to loop though each one in turn.
The data for the field will vary in length and so it is made available via a pointer (nvInput.field(i).value), which I use as the basing pointer for the field value (N). I have defined its length as 32,766 characters, the maximum supported for a DDS defined field. The actual length of the data is in the variable nvInput.field(i).valueLenBytes. This length is used in the %SUBST BIF (P) to ensure that only valid data is extracted. Note that the field values are always supplied to the handler in human-readable form, i.e., the way that you would see it on a screen or printer report.
After processing all the fields, I just need to close the record element and send the length of the data back to the requestor. That’s really all there is to processing the write requests.
What’s Left To Do?
The process for handling the close request is really just the reverse of the open–you can see the code at (L) but hopefully it needs no explanation.
The only other thing my logic has to handle is the possibility that a request other than open/write/close could be received. The RPG compiler would take care of any attempt to read from a file defined as output, but what if the programmer mistakenly specifies a “write only” handler (such as this one) for an input file? There is no way the compiler can catch that, and so I have to defend against it at run time. This is done at (M) by the OTHER clause of the SELECT. I simply set the appropriate status code and the RPG run time will take care of the rest.
This handler will work as-is, but is primarily intended as a teaching vehicle for the basics and as such has a number of limitations:
The capabilities for handling the first two of these scenarios are built-in to the OAR design. The others are a simple matter of programming. Addressing these four items will be the topic of a future tip.
Hopefully I have demonstrated here that writing OAR handlers can be a relatively painless task once the mechanics of the process are understood.
What else can OAR handlers do for you?
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.