メッセージを取得する、パート2
著者まえがき:この記事の元の記事は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から予期される結果は、次のようになります。
その他のメッセージ サブプロシージャー
しかし、これらのサブプロシージャーだけで終わりにすることはありません。他にも有用と思われるメッセージ サブプロシージャーがいくつかありますので、以下で見て行きましょう。
- 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 を主催しています。