You are currently on IBM Systems Media’s archival website. Click here to view our new website.

IBM i > DEVELOPER > RPG

Open Access Made Easy With Templates


Since we wrote our first articles on RPG Open Access (OA) many months ago, we have taught the topic to hundreds of RPGers. The more people we taught, the more obvious it became that the apparent complexity of writing an OA handler turned many people off. That is a pity because while there are some unusual aspects to writing handlers, the basic setup is always the same! That led us to further refine our OA template programs and to teach the topic from that perspective. That has resulted in a far greater uptake of the technique among students, so we thought we’d share the latest iteration of the handler with our readers.

We are using fixed-form declarations in the template for our explanations. This is in deference to those of you who have not moved on to V7 yet, and therefore do not have access to its free-form file and data declaration support. We will, however, be making both free- and fixed-form versions of the latest templates available on our website.

Developing a Handler

We will describe the template in the context of developing an extended version of the CSV writer handler that we first introduced in the article "Getting a Handle on RPG’s Open Access." If you have not read that article, it would be a good idea to do so now since we won't be discussing OA basics here. In addition to the basic IFS output capability, this new handler will add the ability to output column headings at the beginning of the file and allow the specification of an alternative separator in place of the comma, which will remain the default.

OK, time to begin. You may find it easier to follow along if you download the source and open it in your editor of choice. We will occasionally refer to some code in the downloaded source.

The first thing we need to change in the template is the name of the handler we are building. That is done by changing the name of the prototype and the ExtPgm keyword as shown at (A). The name on the procedure interface must also be changed (which is shown in the downloaded version of the source at D).

       // Change to PR to use ExtProc etc. if required
(A)  D IFSHandlerV2    Pr                  ExtPgm('IFSHANDLR2')
     D   info                              likeds(QrnOpenAccess_T)

Next step is to design the layout of the parameter that will contain the IFS file name, separator and header specification. Here is the layout that we will be using:

       // This is the layout for the IFS parameters that will be used
       //   by both the handler and the user program. /Copy it in
       //   after the actual DS specification
     D  headerRow                     1a
     D  separator                     1a
     D  IFSfileName                1024a   Varying

That source code is placed in member IFSPARMDTL and then a /Copy added to the appropriate spot in the template (B) like so:

       // The following is the definition of the additional user parameter.
       //   Add any fields etc here as required. Good idea to have the actual
       //   definitions t in a /Copy so the user program can use the same ones.
     D userData        ds                  Based(info.userArea) Qualified
(B)   /Copy ExtraSrc,IFSPARMDTL 

Since this is a Name/Value handler, the next step is to delete the lines, which define the inputBuffer and outputBuffer variables, as they will not be needed. It won't do any harm to leave them in the program, as they are based variables and therefore do not take up any memory, but we won't be using them.

We next move on to the definition of state information. This is an important step, since our handler may potentially be processing multiple files at one time. As in the previous IFS handler, we need to retain the IFS file handle in the state area so we define that as a four-byte integer. It is also a good idea to retain the additional parameter data because it could cause problems if the user program were to change the values part way through the processing. It’s not likely, but why take a chance? For simplicity, we are going to /Copy in the same definitions as we used before. See the code indicated by C in the downloaded source.

With the data definition part completed, it’s time now to move on to the logic. We begin by adding the WHEN clause for the WRITE operation (E) and set up the call for our WriteFile() subprocedure. Note that this is added in the indicated position in the template—i.e. after the SELECT and before the Open and Close options. Since the logic we are using is fundamentally the same as in the previous version of this handler, we will not discuss it further here. Later we will discuss the changes that need to be made to the WriteFile() logic to accommodate the headers and alternate separators.

(E)         When info.rpgOperation = QrnOperation_WRITE;

              // Write error is unlikely but signal it if it occurs
              If ( WriteFile(fileHandle) = ioError );
                 dsply ('Error on IFS file write');
                 info.rpgStatus = errIO;
              EndIf;

Our next step (F) is to add logic to the OpenFile() routine to store the user parameters if supplied. One slight change we also made for this version was to demonstrate how we could provide a default CSV file name by combining the external name of the file with a predefined directory and type. You can see that here:

       // Check if user parm was supplied and any set defaults if needed
       // Store parm values in state info area otherwise if caller
       //   changes the values it could really mess you up
(F)    If info.userArea = *Null;
         stateinfo.headerRow = 'Y';  // Default to producing header row
         stateinfo.separator = ',';  //   Comma for separator
         stateinfo.IFSfileName = '/Partner400/' +
                                 info.externalFile + 
                                 '.csv';  // Default from external file name
       Else;  // Copy supplied definitions into state info area
         Eval-Corr stateinfo = userData;  
       EndIf; 

As you can see, we have also set up defaults for the header row and item separator. If we were writing this as production code, this is where we would want to validate the user-supplied options. For example, there are probably a limited number of separator characters that we would want to allow. We wouldn't want double-quotes to be used.

Next we need to add the logic for producing the header row to the WriteFile() routine (G). We admit to having "cheated" a little here by using the header control flag (headerRow) to identify that headers had been output. We also had to add the definition of a field (header) in which to build the header row.

In our handler we have simply used the external names of the fields as the column headers. It would also be possible to add an array of "pretty" names to the user parameter and use those instead. We could use APIs to look up the column headings or another option. The coding of these options, however, is left as an exercise for the reader.

In the code we added, the rest of the logic is as it was in the earlier example, except we used the supplied separator in place of the hard coded comma that we used before.

(G)      If ( stateInfo.headerRow = 'Y' );
           // Use field name as header
           header += %Trimr(nvInput.field(i).externalName); 
           If i <> nvInput.num; // Add separator after every field except the last
             header += stateInfo.separator;
           Else;                // Last field so write out header
             header += CRLF; // Add record termination
             reply = write ( handle: %Addr(header: *Data): %Len(header) );
             stateInfo.headerRow = 'N'; // And turn off header output
           EndIf;
         EndIf;

And that is all there is to it. As you have seen, the majority of the more complex definitions and logic were already in place in the template.

Using the Handler

The test program below uses our handler for two different files. In the first case (1) it specifies the additional parameter to supply the file name and separator. At (2) you can see where we /Copy'd the parameter definitions that we used in the handler into the program. The values for the file name, etc., are set in the logic at (3).

The second file is defined at (4) and will use the default file name, separator and headings settings. In other words, it will produce a file named IFS_OUT2.csv with comma separators and a heading row.


     H dftactgrp(*no) option(*NoDebugIO : *SrcStmt)

(1)  FIFS_OUT1  o    e             Disk    Handler('IFSHANDLR2': IFSParms1)
     F                                     UsrOpn

(4)  FIFS_OUT2  o    e             Disk    Handler('IFSHANDLR2')
     F                                     UsrOpn

     FTEST1     if   e           K Disk

(2)  D IFSParms1       DS                  qualified
      /copy ExtraSrc,IFSPARMDTL

      /free
(3)       IFSParms1.IFSfileName = '/Partner400/IFS_OUT1_Pipe.csv';
          IFSParms1.headerRow = 'Y';
          IFSParms1.separator = '|';

          open IFS_OUT1;
          open IFS_OUT2;

          read TEST1;

          dow not %eof(TEST1);
             Write IFS_OUT1R;
             Write IFS_OUT2R;

             // read next record
             read TEST1;
          enddo;

          *inlr = *On;

Downloading the Files

Our OA templates (fixed- and free-form data declaration version), together with both versions of the handler described in this article and all of the other files involved (except for those supplied by IBM) can be downloaded from our website.

Hopefully you will find this CSV writer a useful tool. If you have questions or suggestions for other handlers, please let us know via the comments section.

Jon Paris is a technical editor with IBM Systems Magazine and co-owner of Partner400.

Susan Gantner is a technical editor with IBM Systems Magazine and co-owner of Partner400.



Like what you just read? To receive technical tips and articles directly in your inbox twice per month, sign up for the EXTRA e-newsletter here.



Advertisement

Advertisement

2019 Solutions Edition

A Comprehensive Online Buyer's Guide to Solutions, Services and Education.

New and Improved XML-INTO

Namespace support makes the opcode a viable option

Authenticating on the Web

The finer points of OpenRPGUI, Part 1

The Microphone is Open

Add your voice: Should IBM i include open-source RPG tools?

IBM Systems Magazine Subscribe Box Read Now Link Subscribe Now Link iPad App Google Play Store
IBMi News Sign Up Today! Past News Letters