メニューボタン
IBMi海外記事2018.11.08

メッセージを取得する、パート2

Paul Tuohy 著

著者まえがき:この記事の元の記事は2009年10月に掲載されたものです。それ以降、私は数多くのクライアントの数多くのモダナイゼーション プロジェクトに携わってきましたが、どのプロジェクトでも、今回(および前回)の記事に記した内容を何らかの形で使用してきました。記事の内容は、フリーフォームRPG用に、また、2009年以降にRPGに導入された、いくつかのコーディングの機能強化に合わせるべく、アップデートを行っています。元の記事でも、PHPからRPGサブプロシージャーを直接呼び出す例を示しました。現在は、RPGとの対話処理が可能な言語(Node.js、Python等)が数多くあることから、ストアード プロシージャーを使用して呼び出しを行う方式に変更を加えています。

メッセージを取得する、パート1」では、数多くのメッセージ ルーチン(clearMessages()、addMessage()、messageCount()、およびgetMessage())の定義について見てきました。この記事では、それらのルーチンが、RPGおよびPHP環境の両方でどのように使用されるかについて見て行きます。また、有用と思われる、他のいくつかのメッセージ ルーチンについても見て行きます。

改めて記しますが、これらの記事で使用されているコードが含まれているライブラリーは、http://www.systemideveloper.com/downloads/messagesV2.zipでダウンロードできます。

テスト プロシージャー

以下の短いサブプロシージャー(fillMessages())は、メッセージを任意のジョブ レベルで追加/格納できることを示しています。このサブプロシージャーは、渡されたパラメーターで要求された回数だけ、フィラー メッセージを追加するだけです。

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;  

RPG

以下に示すRPGプログラムを使用して、メッセージ ルーチンを説明して行きます。このプログラムは、次の処理を実行します。

  • u_clear_Messages()を呼び出して、格納されているメッセージをすべて消去します。
  • u_add_Message()を2回呼び出して、2つのメッセージを追加します(必要なメッセージIDを識別するための名前付き定数の使い方に注目してください)。
  • d_fill_Messages()を呼び出して3つのフィラー メッセージを追加します。
  • u_message_Count()によって返される値に応じて、プログラムはu_get_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; 

SHOWMSGSから予期される結果は、次のようになります。

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

もちろん、このプログラムは、呼び出しのフォーマットを表示するだけです。重要なのは、プログラムが取得したメッセージをどう処理するかということです。

これがグリーン スクリーン プログラムだったとしたら、返されたメッセージはメッセージ サブファイルに送信されるのかもしれません。あるいは、CGIDEV2プログラムだったとしたら、返されたメッセージはWebページでのメッセージ情報の入力に使用されるのかもしれません。

たとえば、CGIDEV2を使用していて、2つのディビジョン/変数を使用しているWebページでエラーを特定したとします。ERRTEXTには表示されるメッセージ テキストが格納され、ERRVARS(ページの非表示ディビジョン)にはエラーがあるフィールドの名前が格納されます。ページ ロード時に実行されるJavaScriptルーチンは、ERRVARSの内容を使用してエラーがあるフィールドをハイライトします。以下に示すsetCGIMessages()ルーチンは、Webページでのエラーのメッセージ設定に使用されます。

**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ストアード プロシージャー

SQLを通じてメッセージ サブプロシージャーにアクセスできるようにするために、それらのメッセージ サブプロシージャーをストアード プロシージャーとしてラッピングすることができます。残念ながら、SQLストアード プロシージャーのパラメーター インターフェースは非常にシンプルであり、値を返す、または複雑な構造(データ構造)を渡す簡単な手段を提供していません。そのため、ストアード プロシージャーから呼び出される2つの「ラッパー」サブプロシージャーを書く必要があります。

  • SQL_message_Count()は、u_message_Count()のラッパーであり、格納されているメッセージの数をパラメーターとして返します。
  • SQL_get_Message()は、u_get_Message()のラッバーであり、メッセージ データ構造の内容を個別のパラメーターとして返します。
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;

これで必要なすべてのサブプロシージャーが整ったので、必要なSQLストアード プロシージャーを作成できるようになりました。それぞれのストアード プロシージャーは、必要なメッセージ サブプロシージャーを呼び出すだけです。

--  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 ; 

PHP

最初の手順は、UTILTYサービス プログラム内の対応するサブプロシージャーの呼び出しを発行するPHP関数を書くことでした。これらの関数のそれぞれは、対応するストアード プロシージャーの呼び出しを発行するだけのPHP関数です。言い換えれば、RPGサブプロシージャーを呼び出すためのSQLストアード プロシージャーのPHPラッパーがあるということになりです。

PHPスクリプト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;
}


?> 

各関数には、サブプロシージャーの呼び出しで使用される接続を識別するパラメーター、$connが渡されることに注目してください。このパラメーターは、サブプロシージャーを呼び出すジョブを識別します。また、メッセージIDの定数の定義にも注目してください。

以下は、SHOWMSGSプログラムに対応するPHPスクリプト、phpmessage.phpです。このスクリプトは以下の処理を実行します。

  • i($conn)へ接続します。
  • clearMessages()を呼び出して、格納されているメッセージをすべて消去します。
  • addMessage()を2回呼び出して、2つのメッセージを追加します(必要なメッセージIDを識別するための名前付き定数の使い方に注目してください)。
  • fillMessages()を呼び出して3つのフィラー メッセージを追加します。
  • messageCount()によって返される値に応じて、スクリプトはgetMessage()の呼び出しをループし、それぞれのメッセージに対して返されるメッセージ テキストを表示します。
  • 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>

phpmessage.phpから予期される結果は、次のようになります。

技術01

その他のメッセージ サブプロシージャー

しかし、これらのサブプロシージャーだけで終わりにすることはありません。他にも有用と思われるメッセージ サブプロシージャーがいくつかありますので、以下で見て行きましょう。

  • u_add_MessageText()は、メッセージID/メッセージ ファイルを使用するのではなく、メッセージ テキストを直接格納できるようにします。
  • u_set_Message_File()は、使用されているデフォルト メッセージ ファイルまたはライブラリーを変更できるようにします。
  • u_send_File_Error()は、パラメーターとして渡されるステータス コードに応じて、ファイル メッセージを送信します。このサブプロシージャーは、ファイル操作で受け取られていて、エラー エクステンダーでトラップされているI/Oエラーに応じて呼び出されます。
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;

これでお分かりでしょうか

これらの2つの記事が読者の皆さんにとって、何か参考になるところがあったのだとすれば幸いです。皆さんが書かれてきたRPGコードを、RPG専用アプリケーションの世界に閉じ込めておくことはありません。素晴らしいコードは、より広い世界で使用できるからです。

IBM Championにして『Re-engineering RPG Legacy Applications』の著者であるPaul Tuohy氏は、IBMミッドレンジ界におけるアプリケーションのモダナイゼーションおよび開発テクノロジーの分野で非常に著名なコンサルタント兼トレーナーです。現在は、アイルランドのダブリンのコンサルタント会社、ComCon社のCEOを務める傍ら、「System i Developer」コンソーシアムの運営にもパートナーとして参画しています。Susan Gantner氏およびJon Paris氏とともに、年2回、RPG & DB2 Summit を主催しています。

あわせて読みたい記事

PAGE TOP