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

    August 29, 2018 Paul Tuohy

    Author’s Note: This article was originally published in October 2009. Since then, I have worked on many modernization projects with many clients and, in every one of those projects, we have used some form of the contents of this (and the following) article. The content of the article has been updated for free-form RPG and some of the coding enhancements that have been introduced, into RPG, since 2009. The original articles also showed examples of direct calls to RPG subprocedures from PHP. Given that we now have many languages (Node.js, Python etc.) that interact with RPG, I changed the mechanism to make the calls using stored procedures.

    In Getting The Message, Part 1,we saw the definition of a number of message routines (clearMessages(), addMessage(), messageCount() and getMessage()). In this article we will see how the routines may be used in both an RPG and a PHP environment. We will also look at another couple of message routines that may be useful.

    Remember, a library containing the code used in these articles may be downloaded at http://www.systemideveloper.com/downloads/messagesV2.zip

    A Test Procedure

    This is a small subprocedure (fillMessages()) used to demonstrate that messages may be added/stored at any level in the job. The subprocedure simply adds a filler message the number of times requested on the passed parameter.

    dcl-Proc d_fill_Messages export;
      dcl-Pi *n;
        timesToSend int(10) const;
      end-Pi;
    
      dcl-S i int(10);
    
      for i = 1 to timesToSend;
         u_add_Message(APP_FILLER : 'FILL' : %char(i));
      endFor;
      return;
    end-Proc;  
    

    In RPG

    This is an RPG program used to demonstrate the message routines. The program performs the following:

    • Calls u_clear_Messages() to clear any stored messages
    • Calls u_add_Message() twice to add two messages (note the use of the named constants to identify the required message IDs)
    • Calls d_fill_Messages() to add three filler messages
    • Based on the value returned by u_message_Count(), the program loops through calls to u_get_Message() and displays the message text returned for each message
    **free
    /include QCpySrc,StdHSpec
    
        // To create the program...
        //    Current library set to MESSAGES
        //    CRTBNDRPG PGM(SHOWMSGS)
    
    /include utility,putilMsgs
    
    dcl-ds message likeDS(def_MsgFormat);
    
    dcl-s i int(10);
    
    u_clear_Messages();
    u_add_Message(ERR_NOTFOUND : 'TEST1');
    u_add_Message(ERR_CHANGED : 'TEST2');
    
    d_fill_Messages(3);
    
    for i = 1 to u_message_Count();
       u_get_Message(i : message);
       dsply %subst(message.msgText :1 :40);
    endFor;
    *InLR = *on; 
    

    The expected results from SHOWMSGS is:

    DSPLY  An expected record was not found for upd
    DSPLY  Record already altered. Update/Delete ig
    DSPLY  This is filler message 1                
    DSPLY  This is filler message 2                
    DSPLY  This is filler message 3
    

    Of course this program simply shows the format of the calls. The real key is that the program wants to do with the messages it retrieves.

    If this were a green screen program the returned messages could be sent to a message subfile or if it were a CGIDEV2 program the returned messages might be used to populate message information on a web page.

    For example, when I am using CGIDEV2 I identify errors on a web page using two divisions/variables. ERRTEXT contains the message text to be displayed and ERRVARS (a non-display division on the page) contains the names of the fields in error – a JavaScript routine that runs on page load uses the contents of ERRVARS to highlight the fields in error. This is the setCGIMessages() routine used to set the errors on a web page.

    **free
    dcl-Proc setCGIMessages export;
       dcl-pi *n;
       end-pi;
    
       dcl-s i Int(10);
       dcl-ds msgFormat likeDs(Def_MsgFormat);
       dcl-s errVars varchar(32767);
       dcl-s errText varchar(32767);
    
       errVars = ' ';
       errText = ' ';
    
       if u_message_Count() > 0;
          for i = 1 to u_message_Count();
             u_get_Message(i: msgFormat);
             if (i > 1);
                errVars = errVars + '%%';
                errText = errText + '<br />';
             endIf;
             errVars = errVars + %trim(msgFormat.ForField);
             errText = errText + %trim(msgFormat.MsgText);
          endFor;
       endIf;
       updHTMLvar( 'ErrVars': errVars);
       updHTMLvar( 'ErrText': errText);
    end-Proc; 
    

    SQL Stored Procedures

    In order to make the message subprocedures accessible through SQL, we can wrap them as stored procedures. Unfortunately, SQL stored procedures have a very simple parameter interface and do not provide an easy means of returning values or passing complex structures (data structures). Therefore, we have to write a couple of “wrapper” subprocedures that will be called from the stored procedures.

    • SQL_message_Count() is a wrapper for u_message_Count() and returns the number of stored messages as a parameter
    • SQL_get_Message() is a wrapper for u_get_Message() and returns the contents of the message data structure as individual parameters
    dcl-Proc SQL_message_Count export;
      dcl-Pi *n;
        numMessages int(10);
      end-Pi;
      numMessages = u_message_Count();
      return;
    end-Proc;
    
    dcl-Proc SQL_get_Message export;
      dcl-Pi *n;
        forMessage int(10) const;
        msgId      char(7);
        msgText    char(80);
        severity   int(10);
        help       char(500);
        forField   char(25);
      end-Pi;
    
      if (forMessage > 0 and
          forMessage <= u_message_Count() );
          msgId    = messages(forMessage).msgId;
          msgText  = messages(forMessage).msgText;
          severity = messages(forMessage).severity;
          help     = messages(forMessage).help;
          forField = messages(forMessage).forField;
      endIf;
      return;
    end-Proc;
    

    With all of the required subprocedures in place, we can now create the required SQL stored procedures. Each stored procedure simply calls the required message subprocedure.

    --  Create Stored Procedures for Message Functions
    CREATE OR REPLACE PROCEDURE MESSAGES.D_FILL_MESSAGES (
    	IN NUMMESSAGES INTEGER )
    	LANGUAGE RPGLE
    	SPECIFIC MESSAGES.D_FILL_MESSAGES
    	NOT DETERMINISTIC
    	NO SQL
    	CALLED ON NULL INPUT
    	EXTERNAL NAME 'MESSAGES/UTILITY(d_fill_Messages)'
    	PARAMETER STYLE SQL ;
    
    GRANT EXECUTE
    ON SPECIFIC PROCEDURE MESSAGES.D_FILL_MESSAGES
    TO PUBLIC ;
    
    CREATE OR REPLACE PROCEDURE MESSAGES.U_ADD_MESSAGE (
    	IN MSGID CHAR(7) ,
    	IN FORFIELD CHAR(25) ,
    	IN MSGDATA CHAR(500) )
    	LANGUAGE RPGLE
    	SPECIFIC MESSAGES.U_ADD_MESSAGE
    	NOT DETERMINISTIC
    	NO SQL
    	CALLED ON NULL INPUT
    	EXTERNAL NAME 'MESSAGES/UTILITY(u_add_Message)'
    	PARAMETER STYLE SQL ;
    
    GRANT EXECUTE
    ON SPECIFIC PROCEDURE MESSAGES.U_ADD_MESSAGE
    TO PUBLIC ;
    
    CREATE OR REPLACE PROCEDURE MESSAGES.U_CLEAR_MESSAGES ( )
    	LANGUAGE RPGLE
    	SPECIFIC MESSAGES.U_CLEAR_MESSAGES
    	NOT DETERMINISTIC
    	NO SQL
    	CALLED ON NULL INPUT
    	EXTERNAL NAME 'MESSAGES/UTILITY(u_clear_Messages)'
    	PARAMETER STYLE SQL ;
    
    GRANT EXECUTE
    ON SPECIFIC PROCEDURE MESSAGES.U_CLEAR_MESSAGES
    TO PUBLIC ;
    
    CREATE OR REPLACE PROCEDURE MESSAGES.U_GET_MESSAGE (
    	IN FORMESSAGE INTEGER ,
    	OUT MSGID CHAR(7) ,
    	OUT MSGTEXT CHAR(80) ,
    	OUT SEVERITY INTEGER ,
    	OUT HELP CHAR(500) ,
    	OUT FORFIELD CHAR(25) )
    	LANGUAGE RPGLE
    	SPECIFIC MESSAGES.U_GET_MESSAGE
    	NOT DETERMINISTIC
    	NO SQL
    	CALLED ON NULL INPUT
    	EXTERNAL NAME 'MESSAGES/UTILITY(SQL_get_Message)'
    	PARAMETER STYLE SQL ;
    
    GRANT EXECUTE
    ON SPECIFIC PROCEDURE MESSAGES.U_GET_MESSAGE
    TO PUBLIC ;
    
    CREATE OR REPLACE PROCEDURE MESSAGES.U_MESSAGE_COUNT (
    	OUT NUMMESSAGES INTEGER )
    	LANGUAGE RPGLE
    	SPECIFIC MESSAGES.U_MESSAGE_COUNT
    	NOT DETERMINISTIC
    	NO SQL
    	CALLED ON NULL INPUT
    	EXTERNAL NAME 'MESSAGES/UTILITY(SQL_message_Count)'
    	PARAMETER STYLE SQL ;
    
    GRANT EXECUTE
    ON SPECIFIC PROCEDURE MESSAGES.U_MESSAGE_COUNT
    TO PUBLIC ; 
    

    In PHP

    The first step was to write PHP functions that would issue calls to the corresponding subprocedures in the UTILTY service program. Each of these functions is simply a PHP function that issues a call to the corresponding stored procedure. In other words, these are PHP wrappers to SQL stored procedures to call the RPG subprocedures.

    This is the content of the PHP script func_messages.php.

    <?php
    
    define('APP_ERR_NOTFOUND','ALL9001');
    define('APP_ERR_CHANGED','ALL9002');
    define('APP_ERR_DUPLICATE','ALL9003');
    define('APP_ERR_CONSTRAINT','ALL9004');
    define('APP_ERR_TRIGGER','ALL9005');
    define('APP_ERR_UNKNOWN','ALL9006');
    define('APP_ERR_NOT_NUMBER','ALL9007');
    define('APP_ERR_NOT_DATE','ALL9008');
    
    function clearMessages($conn) {
        
        $sql = 'CALL messages.u_clear_Messages()';
        $stmt = db2_prepare($conn, $sql);
    	
        db2_execute($stmt);
    
    }
    
    function addMessage($conn, $msgId, $forField = " ", $msgData = " ") {
    
        $sql = 'CALL messages.u_add_Message(?, ?, ?)';
        $stmt = db2_prepare($conn, $sql);
       
        db2_bind_param($stmt, 1, "msgId", DB2_PARAM_IN);
        db2_bind_param($stmt, 2, "forField", DB2_PARAM_IN);
        db2_bind_param($stmt, 3, "msgData", DB2_PARAM_IN);
        
        db2_execute($stmt);
    	
    }
    
    
    function messageCount($conn) {
    
        $msgCount = 0;
        $sql = 'CALL messages.u_message_Count(?)';
        $stmt = db2_prepare($conn, $sql);
         
        db2_bind_param($stmt, 1, "msgCount", DB2_PARAM_OUT);
        
        db2_execute($stmt);
    	
    	return $msgCount;
    }
    
    
    function getMessage($conn, $forMessage) {
    	
        $msgId = '';
        $msgText = '';
        $severity = 0;
        $help = '';
        $forField = '';
        
        $sql = 'CALL messages.u_get_Message(?, ?, ?, ?, ?, ?)';
        $stmt = db2_prepare($conn, $sql);
        
        db2_bind_param($stmt, 1, "forMessage", DB2_PARAM_IN);
        db2_bind_param($stmt, 2, "msgId", DB2_PARAM_OUT);
        db2_bind_param($stmt, 3, "msgText", DB2_PARAM_OUT);
        db2_bind_param($stmt, 4, "severity", DB2_PARAM_OUT);
        db2_bind_param($stmt, 5, "help", DB2_PARAM_OUT);
        db2_bind_param($stmt, 6, "forField", DB2_PARAM_OUT);
        
        db2_execute($stmt);
    	
    	return $msgText;
    }
    
    
    
    
    function fillMessages($conn, $numMessages) {
    	
        $sql = 'CALL messages.d_fill_Messages(?)';
        $stmt = db2_prepare($conn, $sql);
         
        db2_bind_param($stmt, 1, "numMessages", DB2_PARAM_IN);
        
        db2_execute($stmt);
    	
    	return;
    }
    
    
    ?> 
    

    Note that each of the functions is passed a parameter, $conn, which identifies the connection to be used on the call to the subprocedure. This parameter identifies the job that calls the subprocedures. Also note the definition of constants for the message IDs.

    This is the PHP script phpmessage.php that corresponds to the SHOWMSGS program. The script performs the following:

    • Makes a connection to the i ($conn)
    • Calls clearMessages() to clear any stored messages
    • Calls addMessage() twice to add two messages (note the use of the named constants to identify the required message IDs)
    • Calls fillMessages() to add three filler messages
    • Based on the value returned by messageCount(), the script loops through calls to getMessage() and display the message text returned for each message
    • Closes the connection to the i
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
    	<title>Playing with PHP Program Calls</title>
    </head>
    
    <body>
    
    <h1>Playing with PHP Messages</h1>
    
    <p>This example demonstrates use of the message routines</p>
    
    <?php
    error_reporting(E_ALL);
    ini_set("display_errors", 1);
    
    
    require 'func_messages.php';
    
    $schema="MESSAGES";
    $toDatabase = "";            // Set Database Name
    $profile = "";               // Set Required Profile and Password
    $profilePW = "";
    $libl = "MESSAGES";
    $conn="";			
    
    $options = array("i5_lib"=>$schema, "i5_commit"=>DB2_I5_TXN_NO_COMMIT,
                     "i5_naming"=>DB2_I5_NAMING_ON,);
    if (!$conn = db2_pconnect($toDatabase, $profile, $profilePW, $options)) {
        echo 'connection failed.<br />';
        die(db2_conn_errormsg().'<br />');
    }
    
    
    echo "Clear Messages <br />";
    clearMessages($conn);
    
    echo "Add Messages <br />";
    addMessage($conn, APP_ERR_NOTFOUND, 'TEST1');
    addMessage($conn, APP_ERR_CHANGED, 'TEST2');
    
    echo "Fill 3 Messages <br />";
    fillMessages($conn, 3);
    
    echo "Get Message count <br />";
    $msgCount = messageCount($conn);
    echo "Returned message count is ".$msgCount." <br />";
    
    echo "Get Messages <br />";
    
    for ($i = 1; $i <= $msgCount; $i++) {
    	echo getMessage($conn, $i)." <br />";
    }
    
    db2_close($conn);
    ?>
    
    <p> Page complete </p>
    
    </body>
    </html>
    

    The expected results from phpmessage.php are:

    Other Message Subprocedures

    But we don’t have to stop with just these subprocedures. These are some other message subprocedures that you might find useful:

    • u_add_MessageText() allows you to directly store message text as opposed to using a message ID/message file
    • u_set_Message_File() allows you to change the default message file and/or library being used
    • u_send_File_Error() sends file messages based on a status code passed as a parameter. This subprocedure would be called based on an I/O error being received on a file operation and being trapped with an error extender
    dcl-Proc u_add_MessageText export;
      dcl-Pi *n;
        msgText    char(80) const;
        forFieldIn char(25) const
                            options(*Omit:*noPass);
        severity   int(10)  const
                            options(*noPass);
      end-Pi;
    
      dcl-S forField like(forFieldIn);
    
      msgCount += 1;
      messages(msgCount).msgText = msgText;
      if %parms()> 2;
        messages(msgCount).severity = severity;
      endIf;
      if %parms() > 1;
        if %Addr(forFieldIn) <> *null;
          forField = forFieldIn;
        endIf;
      endIf;
      messages(msgCount).forField = forField;
    end-Proc;
    
    
    dcl-Proc u_set_MessageFile export;
      dcl-Pi *n;
        newMsgf   char(10) const;
        newMsgLib char(10) const
                           options(*noPass);
      end-Pi;
    
      msgFile = newMsgF;
      if %parms()> 1;
        msgFileLib = newMsgLib;
      endIf;
      return;
    
    end-Proc;
    
    
    dcl-Proc u_send_FileError export;
    
      dcl-Pi *n ind;
        status int(5) const;
      end-Pi;
    
          // Duplicate
      if (status = STAT_DUPLICATE);
        u_add_Message(ERR_DUPLICATE);
    
          // Referential constraint
    
      elseIf (status = STAT_constRAINT_1  or
              status = STAT_constRAINT_2);
        send_constraintMsg();
    
          // Trigger
      elseIf (status = STAT_TRIGGER_1  or
              status = STAT_TRIGGER_2);
        u_add_Message(ERR_TRIGGER);
    
          // Other
      else;
        u_add_Message(ERR_UNKNOWN);
        return *On;
      endIf;
    
      return *Off;
    
    end-Proc;
    

    There You Have It

    Hopefully these two articles have given you some food for thought. Your RPG code does not have to be confined to RPG only applications – all that great code can  be opened up to wider use.

    Paul Tuohy, IBM Champion and author of Re-engineering RPG Legacy Applications, is a prominent consultant and trainer for application modernization and development technologies on the IBM Midrange. He is currently CEO of ComCon, a consultancy firm in Dublin, Ireland, and partner at System i Developer. He hosts the RPG & DB2 Summit twice per year with partners Susan Gantner and Jon Paris.

    RELATED STORY

    Getting The Message, Part 1

    Share this:

    • Reddit
    • Facebook
    • LinkedIn
    • Twitter
    • Email

    Tags: Tags: FHGC, Four Hundred Guru Class, IBM i, PHP, RPG, SQL

    Sponsored by
    UCG Technologies – Vault400

    Do the Math When Looking at IBM i Hosting for Cost Savings

    COVID-19 has accelerated certain business trends that were already gaining strength prior to the start of the pandemic. E-commerce, telehealth, and video conferencing are some of the most obvious examples. One example that may not be as obvious to the general public but has a profound impact on business is the shift in strategy of IBM i infrastructure from traditional, on-premises environments to some form of remote configuration. These remote configurations and all of their variations are broadly referred to in the community as IBM i hosting.

    “Hosting” in this context can mean different things to different people, and in general, hosting refers to one of two scenarios. In the first scenario, hosting can refer to a client owned machine that is housed in a co-location facility (commonly called a co-lo for short) where the data center provides traditional system administrator services, relieving the client of administrative and operational responsibilities. In the second scenario, hosting can refer to an MSP owned machine in which partition resources are provided to the client in an on-demand capacity. This scenario allows the client to completely outsource all aspects of Power Systems hardware and the IBM i operating system and database.

    The scenario that is best for each business depends on a number of factors and is largely up for debate. In most cases, pursuing hosting purely as a cost saving strategy is a dead end. Furthermore, when you consider all of the costs associated with maintaining and IBM i environment, it is typically not a cost-effective option for the small to midsize market. The most cost-effective approach for these organizations is often a combination of a client owned and maintained system (either on-prem or in a co-lo) with cloud backup and disaster-recovery-as-a-service. Only in some cases of larger enterprise companies can a hosting strategy start to become a potentially cost-effective option.

    However, cost savings is just one part of the story. As IBM i expertise becomes scarce and IT resources run tight, the only option for some firms may be to pursue hosting in some capacity. Whatever the driving force for pursing hosting may be, the key point is that it is not just simply an option for running your workload in a different location. There are many details to consider and it is to the best interest of the client to work with an experienced MSP in weighing the benefits and drawbacks of each option. As COVID-19 rolls on, time will tell if IBM i hosting strategies will follow the other strong business trends of the pandemic.

    When we say do the math in the title above, it literally means that you need to do the math for your particular scenario. It is not about us doing the math for you, making a case for either staying on premises or for moving to the cloud. There is not one answer, but just different levels of cost to be reckoned which yield different answers. Most IBM i shops have fairly static workloads, at least measured against the larger mix of stuff on the public clouds of the world. How do you measure the value of controlling your own IT fate? That will only be fully recognized at the moment when it is sorely missed the most.

    CONTINUE READING ARTICLE

    Please visit ucgtechnologies.com/IBM-POWER9-systems for more information.

    800.211.8798 | info@ucgtechnologies.com

    Article featured in IT Jungle on April 5, 2021

    Share this:

    • Reddit
    • Facebook
    • LinkedIn
    • Twitter
    • Email

    HelpSystems Nabs MPG for Performance Management Guru: An Introduction to Processing XML With RPG, Part 2

    Leave a Reply Cancel reply

TFH Volume: 28 Issue: 57

This Issue Sponsored By

  • RPG & DB2 Summit
  • RPG & DB2 Summit
  • RPG & DB2 Summit

Table of Contents

  • Guru: Three Little Words That Simplify Debugging
  • Guru: An Introduction to Processing XML With RPG, Part 2
  • Guru: Getting the Message, Part 2

Content archive

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

Recent Posts

  • Query Supervisor Gives Database Engineers New Power
  • IBM Unveils New and Improved IBM i Services
  • 3 Takeaways from the 2021 PowerTech Security Report
  • Four Hundred Monitor, April 14
  • IBM i PTF Guide, Volume 23, Number 15
  • Big Blue Unveils Spring 2021 IBM i Technology Refreshes
  • Thoroughly Modern: Innovative And Realistic Approaches To IBM i Modernization
  • Guru: Web Services, DATA-INTO and DATA-GEN, Part 2
  • Back To The Future With A New IBM i Logo
  • IBM i PTF Guide, Volume 23, Number 14

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 © 2021 IT Jungle

loading Cancel
Post was not sent - check your email addresses!
Email check failed, please try again
Sorry, your blog cannot share posts by email.