• The Four Hundred
  • Subscribe
  • Media Kit
  • Contributors
  • About Us
  • Contact
Menu
  • The Four Hundred
  • Subscribe
  • Media Kit
  • Contributors
  • About Us
  • Contact
  • Getting the Message, Part 1

    October 14, 2009 Paul Tuohy

    When we look at modernizing (or writing new) applications, one of the basic principles is to tier the application, i.e., separate the interface, the business logic, and the database processing. The concept is that any of the components can be changed without affecting the others and, more importantly, you can have multiple interfaces making use of the same business logic and database routines.

    That’s all well and good, but there are a couple of minor hiccups that have to be handled. What happens when a business logic or database routine hits an error? How does it notify the interface that an error has occurred?

    In other words, how do we send messages between the different components when the components have no knowledge of each other?

    In our traditional green-screen world, messaging was tightly integrated between the process logic and the screen. We may have been using the ERRMSG or ERRMSGID keywords in DDS, or making use of program message queues and message subfiles. But will a program message queue technique work with a Web request or an SQL subprocedure?

    In this series of articles, I will take a look at a technique that allows for handling messages between any interface. A library containing the code used in these articles is available for download here.

    But Before We Begin…

    I have always been an enormous fan of message files and I intend to keep using them in this “new” structure. I really like the ability to define second level message text, severity codes, and variable parameters.

    But one of the things I don’t like about message files is when I see the message ID hard-coded in an RPG program. Of course you have to use the message ID in the program, but I prefer to define my message IDs as named constants and place them in a copy member that is included in every program. Therefore, every program has a list of all available message IDs. Figure 1 shows an example of some of these message IDs. I use the convention (common in most programming languages) of all uppercase for constant names.

    D ERR_NOTFOUND    C                   'ALL9001'
    D ERR_CHANGED     C                   'ALL9002'
    D ERR_DUPLICATE   C                   'ALL9003'
    D ERR_CONSTRAINT  C                   'ALL9004'
    D ERR_TRIGGER     C                   'ALL9005'
    D ERR_UNKNOWN     C                   'ALL9006'
    D ERR_NOT_NUMBER  C                   'ALL9007'
    D ERR_NOT_DATE    C                   'ALL9008'
    

    Figure 1: Defining message IDs as constants.

    Storing Messages

    Bearing in mind that each component of our application cannot have any knowledge of another component, it is not possible to send messages between the components. Instead, messages are stored and subprocedures are provided to indicate how many messages are currently stored and corresponding subprocedures to retrieve the messages.

    How do we store the messages? First inclinations might lead us toward a message queue or a database file; but neither of these is necessary. We can simply store our messages in a data structure array.

    All of the message subprocedures will be coded in a single module and the message format data structure array will be maintained in the same module.

    Figure 2 shows the format of the message data structure. The data structure is defined in a copy member that is included in all programs. In V6R1, use the TEMPLATE keyword as opposed to the QUALIFIED and BASED keywords.

    The message data structure contains the message ID, the first level message text, the message severity, the second level message text, and the name of the field for which the message was stored.

      // Format in which error messages are stored.
    D def_MsgFormat   Ds                  Qualified
    D                                     Based(dummy_Ptr)
    D  msgId                         7a
    D  msgText                      80a
    D  severity                     10i 0
    D  help                        500a
    D  forField                     25a 
    

    Figure 2: The message format.

    The Message Module

    Let’s have a look at the global definitions and each of the message subprocedures.

    Global Definitions

    The global definitions in the message module are shown in Figure 3. The global definitions consist of:

    • An array of message formats and a count of how many messages are currently stored. I think room for 200 messages is more than enough, but feel free to increase the size of the array as required.
    • A data structure that identifies the name of the message file that contains our messages.
    • Prototypes for the Retrieve Message from Message File (QMHRTVM) and Receive Message from Message Queue (QMHRCVPM) APIs.
    • Prototypes for internal procedures.
     /Copy QCpySrc,StdHSpec
    H NoMain
    
         // To create the required service program...
         //    Current library set to MESSAGES
         //    CRTRPGMOD MODULE(UTILMSGS)
         //    CRTSRVPGM SRVPGM(UTILITY) MODULE(UTIL*)
    
     /Copy QCpySrc,BaseInfo
    
    D messages        DS                  LikeDS(Def_MsgFormat)
    D                                     Dim(200)
    D                                     Inz
    
    D msgCount        S             10i 0
    
       // Message File used for retrieving message
    D msgF            Ds
    D  msgFile                      10a   Inz('APPMSGF')
    D  msgFileLib                   10a   Inz('MESSAGES')
    
      // Prototype for QMHRTVM API
    D RetrieveMessageFromMsgF...
    D                 PR                  ExtPgm('QMHRTVM')
    D  msgInfo                    3000a   Options(*VarSize)
    D  msgInfoLen                   10i 0 Const
    D  formatName                    8a   Const
    D  msgId                         7a   Const
    D  msgF                         20a   Const
    D  replacement                 500a   Const
    D  replacementLen...
    D                               10i 0 Const
    D  replaceSubVals...
    D                               10a   Const
    D  returnFCC                    10a   Const
    D  usec                        256a
    
      // Prototype for QMHRCVPM API
    D receiveMsg      PR                  ExtPgm('QMHRCVPM')
    D  msgInfo                    3000a   Options(*VarSize)
    D  msgInfoLen                   10i 0 Const
    D  formatName                    8a   Const
    D  callStack                    10a   Const
    D  callStackCtr                 10i 0 Const
    D  msgType                      10a   Const
    D  msgKey                        4a   Const
    D  waitTime                     10i 0 Const
    D  msgAction                    10a   Const
    D  errorForAPI                        Like(APIError)
    
      // Prototype for internal send constraint message
      // procedure
    D sendConstraintMsg...
    D                 PR
    

    Figure 3: Global definitions.

    Clear Messages

    The clearMessages() subprocedure (shown in Figure 4) simply does what it says on the box–it clears the message format data structure array and sets the message count to zero.

    P clearMessages   B                   Export
    D                 PI
     /free
      msgCount = 0;
      clear messages;
     /end-Free
    
    P                 E
    

    Figure 4: The clearMessages() subprocedure.

    Add Messages

    The addMessage() subprocedure (shown in Figure5) adds the required message to the message format data structure array and increments the message count. The subprocedure accepts three parameters but only the first (message ID) is required. The second parameter identifies the name of the field to which the message relates, and the third parameter contains any variable data for the message.

    The subprocedure uses the QMHRTVM API with the RTVM0300 format to retrieve the indicated message from the message file (identified in the msgF data structure in the global definitions). If message data was supplied on the call to addMessage(), then the message data is automatically inserted during the retrieve. As you can see, a little bit of pointer manipulation is required to retrieve the message text and the second level text.

    Of course, the routine checks to ensure that the requested message exists (not that anyone would ever request a nonexistent message ID).

    P addMessage      B                   Export
    D                 PI
    D  msgId                         7a   Const
    D  forFieldIn                   25a   Const
    D                               Options(*Omit:*NoPass)
    D  msgData                     500a   Const
    D                                     Options(*NoPass)
    
       // Format RTVM0300 for data returned from QMHRTVM
    D RTVM0300        Ds                  Qualified
    D  bytesreturned                10i 0
    D  bytesAvail                   10i 0
    D  severity                     10i 0
    D  alertIndex                   10i 0
    D  alertOption                   9a
    D  logIndicator                  1a
    D  messageId                     7a
    D                                3a
    D  noSubVarFmts                 10i 0
    D  CCSIDIndText                 10i 0
    D  CCSIDIndRep                  10i 0
    D  CCSIDTextRet                 10i 0
    D  dftRpyOffset                 10i 0
    D  dftRpyLenRet                 10i 0
    D  dftRpyLenAvl                 10i 0
    D  messageOffset                10i 0
    D  messageLenRet                10i 0
    D  messageLenAvl                10i 0
    D  helpOffset                   10i 0
    D  helpLenRet                   10i 0
    D  helpLenAvl                   10i 0
    D  SVFOffset                    10i 0
    D  SVFLenRet                    10i 0
    D  SVFLenAvl                    10i 0
    D*** reserved
    D*** defaultReply
    D*** message
    D*** messageHelp
    
      // Based variable used to retrieve text from RTVM0300
    D textPtr         S               *
    D text            S            500a   Based(textPtr)
    
    D repData         S            500a
    D forField        S                   like(forFieldIn)
    
     /free
      if %Parms() > 2;
        repData = msgData;
      endIf;
    
      if %Parms()> 1;
        if %Addr(ForFieldIn) <> *Null;
          forField = forFieldIn;
        endIf;
      endIf;
    
      retrieveMessageFromMsgF(RTVM0300:%Len(RTVM0300)+350:
                              'RTVM0300':MsgId:MsgF:
                              RepData:%Len(%Trim(RepData)):
                              '*YES':'*YES':APIError);
    
      msgCount += 1;
      messages(msgCount).msgId = msgId;
      messages(msgCount).forField = forField;
      if (APIError.bytesAvail = 0);
         messages(msgCount).severity = RTVM0300.severity;
         if (RTVM0300.messageLenRet > 0);
            textPtr = %Addr(RTVM0300) + RTVM0300.messageOffset;
            messages(msgCount).msgText = %SubSt(text: 1:
                                       RTVM0300.messageLenRet);
         endIf;
         if (RTVM0300.helpLenRet > 0);
            textPtr = %Addr(RTVM0300) + RTVM0300.helpOffset;
            messages(msgCount).help = %SubSt(Text: 1:
                                         RTVM0300.helpLenRet);
         endIf;
      else;
         messages(msgCount).severity = 99;
         messages(msgCount).msgText = 
                      '*** Expected Message Not Found ***';
      endIf;
     /end-Free
    P                 E
    

    Figure 5: The addMessage() subprocedure.

    Get Message Count

    The messageCount() subprocedure (shown in Figure 6) simply returns the number of currently stored messages.

    P messageCount    B                   Export
    D                 PI            10i 0
     /free
      return msgCount;
     /end-Free
    P                 E
    

    Figure 6: The messageCount() subprocedure.

    Get a Stored Message

    The getMessage() subprocedure (shown in Figure 7) retrieves the required message indicted by the first parameter. The data returned is a message format data structure. Of course, the subprocedure checks that a valid stored message is being requested.

    P getMessage      B
    Export               PI
    D  forMessage                   10i 0 Const
    D  msgFormat                          LikeDs(def_MsgFormat)
    
     /free
    
      if forMessage > 0 and forMessage <= msgCount;
        msgFormat = messages(forMessage);
      else;
        clear msgFormat;
        msgFormat.msgText = '*** Message Not Found ***';
      endIf;
      return;
    
     /end-Free
    P                 E
    

    Figure 7: The getMessage() subprocedure.

    To Be Continued

    In part two of this series, I will demonstrate how these message subprocedures may be used in an application. I will also look at a couple of other message subprocedures that may be useful.

    Paul Tuohy is CEO of ComCon, an iSeries consulting company, and is one of the co-founders of System i Developer, which hosts the RPG & DB2 Summit conferences. He is an award-winning speaker who also speaks regularly at COMMON conferences, and is the author of “Re-engineering RPG Legacy Applications,” “The Programmers Guide to iSeries Navigator,” and the self-study course called “iSeries Navigator for Programmers.” Send your questions or comments for Paul to Ted Holt via the IT Jungle Contact page.



                         Post this story to del.icio.us
                   Post this story to Digg
        Post this story to Slashdot

    Share this:

    • Reddit
    • Facebook
    • LinkedIn
    • Twitter
    • Email

    Tags:

    Sponsored by
    Maxava

    Migrate IBM i with Confidence

    Tired of costly and risky migrations? Maxava Migrate Live minimizes disruption with seamless transitions. Upgrading to Power10 or cloud hosted system, Maxava has you covered!

    Learn More

    Share this:

    • Reddit
    • Facebook
    • LinkedIn
    • Twitter
    • Email

    Sponsored Links

    Vision Solutions:  Free software for measuring overall i5/OS system health
    Manta Technologies:  Fall Sale on i training courses! Order by October 15 and SAVE 25%
    S4i Systems:  Express manages System i, mainframe, and any LINUX, UNIX, AIX document files

    IT Jungle Store Top Book Picks

    Easy Steps to Internet Programming for AS/400, iSeries, and System i: List Price, $49.95
    The iSeries Express Web Implementer's Guide: List Price, $49.95
    The System i RPG & RPG IV Tutorial and Lab Exercises: List Price, $59.95
    The System i Pocket RPG & RPG IV Guide: List Price, $69.95
    The iSeries Pocket Database Guide: List Price, $59.00
    The iSeries Pocket SQL Guide: List Price, $59.00
    The iSeries Pocket Query Guide: List Price, $49.00
    The iSeries Pocket WebFacing Primer: List Price, $39.00
    Migrating to WebSphere Express for iSeries: List Price, $49.00
    Getting Started With WebSphere Development Studio Client for iSeries: List Price, $89.00
    Getting Started with WebSphere Express for iSeries: List Price, $49.00
    Can the AS/400 Survive IBM?: List Price, $49.00
    Chip Wars: List Price, $29.95

    Sparxent Issues First Update of XAware Data Integration Tool Gartner Ranks the Managed File Transfer Products

    Leave a Reply Cancel reply

Volume 9, Number 32 -- October 14, 2009
THIS ISSUE SPONSORED BY:

Help/Systems
Profound Logic Software
Manta Technologies

Table of Contents

  • Getting the Message, Part 1
  • How Do I Find What’s Not There?
  • Admin Alert: Locking Down i5/OS System Security Values

Content archive

  • The Four Hundred
  • Four Hundred Stuff
  • Four Hundred Guru

Recent Posts

  • POWERUp 2025 –Your Source For IBM i 7.6 Information
  • Maxava Consulting Services Does More Than HA/DR Project Management – A Lot More
  • Guru: Creating An SQL Stored Procedure That Returns A Result Set
  • As I See It: At Any Cost
  • IBM i PTF Guide, Volume 27, Number 19
  • IBM Unveils Manzan, A New Open Source Event Monitor For IBM i
  • Say Goodbye To Downtime: Update Your Database Without Taking Your Business Offline
  • i-Rays Brings Observability To IBM i Performance Problems
  • Another Non-TR “Technology Refresh” Happens With IBM i TR6
  • IBM i PTF Guide, Volume 27, Number 18

Subscribe

To get news from IT Jungle sent to your inbox every week, subscribe to our newsletter.

Pages

  • About Us
  • Contact
  • Contributors
  • Four Hundred Monitor
  • IBM i PTF Guide
  • Media Kit
  • Subscribe

Search

Copyright © 2025 IT Jungle