Spring BootでIBM iのRPGをコールする
はじめに
Spring Bootを使用して、IBM i上のRPGをコールする方法です。
JDBCを使用せずに直接コールします。
RPGコールの解説がメインで、Spring Bootの知識があることが前提です。
概要
webで名前・生年月日を入力したら、RPGをコールして年齢を計算してメッセージを返し、webに表示します。
フォーム入力
名前と生年月日を入力します。
生年月日は日付型ではなく数値とします。
エラーチェックをしていないので、正しい日付を指定して下さい。
結果
メッセージと年齢が表示されます。
環境
種類 | バージョン |
---|---|
OS | macOS Ventura 13.6 |
Eclipse | Pleiades 2023-09.20231005 |
Java | Java21(Pleiades内蔵) |
Spring Boot | 3.1.4 |
IBM Toolbox for Java | JTOpen 20.0.3 |
IBM i | 7.1 |
OSはWindows10でも動作確認済みです。
RPGプログラム
RPGコールにあたって、まず下記のプログラムをTEST2
の名前で、RPGLEとして作成します。
ソースのライブラリは自由です。コンパイル結果はTSTPLIB
とします。
別のライブラリにコンパイルする場合は、後のパラメータを読み替えてください。
D DS
D DS#TMDT 1 14 0 INZ(*ZERO)
D DS#TIME 1 6 0
D DS#DATE 7 14 0
C*
C *ENTRY PLIST
C PARM @NAME 50 名前
C PARM @BTDT 8 0 誕生日
C PARM @MSG 100 メッセージ
C PARM @AGE 3 0 年齢
C*
C*< START OF MAIN-PROGRAM >
C*
C TIME DS#TMDT
C*
C EVAL @AGE = %DIFF(%DATE(DS#DATE:*ISO):
C %DATE(@BTDT:*ISO):*YEARS)
C EVAL @MSG = %TRIMR(@NAME) + ' さんの年齢は '
C + %CHAR(@AGE) + ' 歳です '
C*
C*< END OF MAIN-PROGRAM >
C*
C MOVE *ON *INLR
C RETURN
RPGプログラムの説明
RPGをコールできればいいので、処理内容はシンプルにしてあります。
エラーチェック等は行っていません。
パラメータ
-
@NAME
:名前(IN) -
@BTDT
:誕生日(IN) YYYYMMDD形式 -
@MSG
:メッセージ(OUT) -
@AGE
:年齢(OUT)
処理概要
名前と誕生日を受け取り、年齢を計算してメッセージと年齢を返します。
文字と数値のパラメータをIN/OUTの両方に設けたかったので、こんな感じにしました。
Javaプログラム
EclipseでSpring Bootプロジェクト作成
タイプはGradle-Groovyを選択しました。この後の説明に影響するので、この設定にしてください。
依存関係は下記を選択
- Spring Boot DevTools
- Lombok
- Thymeleaf
- Spring Web
IBM Toolbox for Javaを依存関係に追加
IBM Toolbox for JavaのJTOpen 20.0.3を依存関係に追加します。
build.gradle
のdependencies
に下記を追加します。
implementation 'net.sf.jt400:jt400:20.0.3'
最終的に下記のようになります。
plugins {
id 'java'
id 'org.springframework.boot' version '3.1.4'
id 'io.spring.dependency-management' version '1.1.3'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
java {
sourceCompatibility = '21'
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
// https://mvnrepository.com/artifact/net.sf.jt400/jt400
implementation 'net.sf.jt400:jt400:20.0.3'
}
tasks.named('test') {
useJUnitPlatform()
}
コメントのURL(https://mvnrepository.com/artifact/net.sf.jt400/jt400)はJTOpenのリポジトリで、各バージョンとMavenやGradleへの参照設定があります。
執筆時点の最新バージョンは20.0.3でした。
webのデザインを作成
src/main/resource/templates
にrpgcall.html
を作成します。
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>RPG CALL</title>
</head>
<body>
<form name="Form" action="#" th:action="@{/rpgcall}" th:object="${form}" method="post">
<table>
<tr>
<td>名前</td>
<td><input type="text" name="name" th:field="*{name}"></td>
</tr>
<tr>
<td>生年月日</td>
<td><input type="number" name="birth" th:field="*{birth}"></td>
</tr>
</table>
<input type="submit">
</form>
<div>メッセージ:<span th:text="${msg}"/></div>
<div>年齢:<span th:text="${age}"/></div>
</body>
</html>
thymeleafを使用しています。
IBM i接続パラメータの設定
IBM iに接続するためのパラメータをapplication.properties
に設定します。
as400.hostname=AS400
as400.username=testuser
as400.password=testpw
as400.libraries=TSTDLIB,TSTPLIB
as400.programlib=TSTPLIB
プロパティの名前のas400.hostname
等は、勝手に付けた名前です。特に決まりはないので自由に付けて貰って良いのですが、この後のプログラムで使用するプロパティ名前と合わせる必要があります。
プロパティの説明
-
as400.hostname
:IBM iのホスト名です。IPアドレスでもOKです。 -
as400.username
:IBM iに接続するユーザー名 -
as400.password
:IBM iに接続するパスワード -
as400.libraries
:RPGを実行するのに必要なライブラリをカンマ区切りで指定
今回のRPGプログラムはDBにアクセスしたりサブルーチンを呼び出したりしていないのですが、通常は別ライブラリにあるDBにアクセスするのでライブラリを指定します。
内部的にはADDLIBLE
でライブラリリストに追加しています。 -
as400.programlib
:実行するRPGプログラムのライブラリ
ライブラリリストにTSTPLIB
を追加しているので別途指定する必要があるのかと思うのですが、RPGコール時にライブラリ指定が必須のようなので、プロパティとして別に設けてあります。
Webフォームからパラメータを受け取るクラスを作成
src/main/java/com.example.demo
にRpgCallForm.java
を作成
package com.example.demo;
public class RpgCallForm {
private String name;
private int birth;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getBirth() {
return birth;
}
public void setBirth(int birth) {
this.birth = birth;
}
}
RPGをコールするメインクラスを作成
package com.example.demo;
import java.math.BigDecimal;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import com.ibm.as400.access.AS400;
import com.ibm.as400.access.AS400Message;
import com.ibm.as400.access.AS400PackedDecimal;
import com.ibm.as400.access.AS400Text;
import com.ibm.as400.access.CommandCall;
import com.ibm.as400.access.ProgramCall;
import com.ibm.as400.access.ProgramParameter;
import com.ibm.as400.access.QSYSObjectPathName;
import lombok.extern.slf4j.Slf4j;
@Controller
@Slf4j
@RequestMapping("rpgcall")
public class RpgCall {
@Value("${as400.hostname}")
String hostName;
@Value("${as400.username}")
String userName;
@Value("${as400.password}")
String password;
@Value("${as400.libraries}")
String libraries;
@Value("${as400.programlib}")
String programLib;
// 日本語(5035のサブセット)
public static final int CCSID = 939;
@GetMapping
public String initialView(Model model) {
model.addAttribute("form", new RpgCallForm());
model.addAttribute("msg", "");
model.addAttribute("age", null);
return "rpgcall";
}
@PostMapping
public String mainProc(@ModelAttribute RpgCallForm form, Model model) throws Exception {
String msg;
BigDecimal age;
char[] charPass = password.toCharArray();
AS400 as400 = new AS400(hostName, userName, charPass);
// ADDLIBLE
if (!addLib(as400, libraries)) {
log.error("ADDLIBLE Error:" + libraries);
throw new Exception("ADDLIBエラー");
}
// CCSID
if (!setCCSID(as400, CCSID)) {
log.error("CCSID Error:" + CCSID);
throw new Exception("CCSIDエラー");
}
// パラメータ設定
ProgramParameter[] params = new ProgramParameter[4];
AS400Text paramName = new AS400Text(50);
AS400PackedDecimal paramBirth = new AS400PackedDecimal(8, 0);
AS400Text paramMsg = new AS400Text(100);
AS400PackedDecimal paramAge = new AS400PackedDecimal(3, 0);
params[0] = new ProgramParameter(paramName.toBytes(form.getName()), 50);
params[1] = new ProgramParameter(paramBirth.toBytes(form.getBirth()), 8);
params[2] = new ProgramParameter(paramMsg.toBytes(""), 100);
params[3] = new ProgramParameter(paramAge.toBytes(0), 3);
// プログラム設定
ProgramCall pgmCall = new ProgramCall(as400);
pgmCall.setProgram(QSYSObjectPathName.toPath(programLib, "TEST2", "PGM"), params);
// プログラム実行
if (!pgmCall.run()) {
AS400Message[] callMsgs = pgmCall.getMessageList();
for (AS400Message callMsg: callMsgs) {
log.debug(String.format("AS400 Error:%s", callMsg));
}
}
// 戻り値設定
msg = paramMsg.toObject(params[2].getOutputData()).toString();
age = (BigDecimal)paramAge.toObject(params[3].getOutputData());
as400.disconnectAllServices();
model.addAttribute("form", form);
model.addAttribute("msg", msg);
model.addAttribute("age", age);
return "rpgcall";
}
public boolean addLib(AS400 as400, String libs) throws Exception {
if (libs.length() == 0) return true;
for (String lib: libs.split(",")) {
if (!runCommand(as400, String.format("ADDLIBLE LIB(%s)", lib))) {
return false;
}
}
return true;
}
public boolean runCommand(AS400 as400, String cmdValue) throws Exception {
CommandCall cmd = new CommandCall(as400);
return cmd.run(cmdValue);
}
public boolean setCCSID(AS400 as400, int ccsid) throws Exception {
return runCommand(as400, String.format("CHGJOB CCSID(%d)", ccsid));
}
}
プログラムの説明
Spring Boot関連の説明は省略します。
IBM i 接続設定
char[] charPass = password.toCharArray();
AS400 as400 = new AS400(hostName, userName, charPass);
ここでIBM iに接続するための設定を行っています。
AS400のパスワードはchar[]
型なので、事前に変換しています。
ライブラリリスト設定
// ADDLIBLE
if (!addLib(as400, libraries)) {
log.error("ADDLIBLE Error:" + libraries);
throw new Exception("ADDLIBエラー");
}
throwは適当です。
ちゃんと例外処理をした方が良いです。
public boolean addLib(AS400 as400, String libs) throws Exception {
if (libs.length() == 0) return true;
for (String lib: libs.split(",")) {
if (!runCommand(as400, String.format("ADDLIBLE LIB(%s)", lib))) {
return false;
}
}
return true;
}
application.properties
のas400.libraries
はライブラリ名をカンマ区切りで持っているので、カンマで分割して個別にADDLIBLEコマンドを発行しています。
public boolean runCommand(AS400 as400, String cmdValue) throws Exception {
CommandCall cmd = new CommandCall(as400);
return cmd.run(cmdValue);
}
共通のコマンド実行メソッドです。
CCSIDの設定
// 日本語(5035のサブセット)
public static final int CCSID = 939;
CHGJOBに使用するので939
ではなく5035
で良いような気がしますが、試していません。
// CCSID
if (!setCCSID(as400, CCSID)) {
log.error("CCSID Error:" + CCSID);
throw new Exception("CCSIDエラー");
}
こちらもthrowは適当です。
ちゃんと例外処理をした方が良いです。
public boolean setCCSID(AS400 as400, int ccsid) throws Exception {
return runCommand(as400, String.format("CHGJOB CCSID(%d)", ccsid));
}
CHGJOBコマンドを発行しています。
パラメータ設定
// パラメータ設定
ProgramParameter[] params = new ProgramParameter[4];
AS400Text paramName = new AS400Text(50);
AS400PackedDecimal paramBirth = new AS400PackedDecimal(8, 0);
AS400Text paramMsg = new AS400Text(100);
AS400PackedDecimal paramAge = new AS400PackedDecimal(3, 0);
params[0] = new ProgramParameter(paramName.toBytes(form.getName()), 50);
params[1] = new ProgramParameter(paramBirth.toBytes(form.getBirth()), 8);
params[2] = new ProgramParameter(paramMsg.toBytes(""), 100);
params[3] = new ProgramParameter(paramAge.toBytes(0), 3);
RPGのTEST2
はパラメータは4つなので、new ProgramParameter[4]
で4つ用意しています。
RPGのパラメータに合わせて、paramName
は文字50桁、paramBirth
は数値8桁、paramMsg
は文字100桁、paramAge
は数値3桁で定義します。
定義したパラメータをprams
の0〜3にセットします。
paramName
とparamBirth
はwebフォームの名前と生年月日をセットします。
paramMsg
とparamAge
は受け取り用なので空白と0をセットします。
試行錯誤してこの方法で上手くいったので、toBytes
など処理が複雑な気がしますが、こういうものだと諦めています。
プログラム設定
// プログラム設定
ProgramCall pgmCall = new ProgramCall(as400);
pgmCall.setProgram(QSYSObjectPathName.toPath(programLib, "TEST2", "PGM"), params);
実行するプログラムを設定します。
QSYSObjectPathName.toPath(programLib, "TEST2", "PGM")
のprogramLib
でプログラムのライブラリを指定、"TEST2"
でプログラム名を指定、"PGM"
はタイプを指定します。
QSYSObjectPathName
を使用しない場合は、"QSYS.LIB/" + programLib + ".LIB/TEST2.PGM"
と指定します。
プログラム実行
// プログラム実行
if (!pgmCall.run()) {
AS400Message[] callMsgs = pgmCall.getMessageList();
for (AS400Message callMsg: callMsgs) {
log.debug(String.format("AS400 Error:%s", callMsg));
}
}
実行部はpgmCal.run()
のみですが、エラー処理としてメッセージを取得してログに出力しています。
実行してみる
プロジェクトを右クリックして…
デバッグを選択して…
3 Spring Boot アプリケーションを選択します。
コンソールにこのように表示されて、特にエラーらしきものが無ければ成功です。
ブラウザで http://localhost:8080/rpgcall を開きます。
この様に表示されたら、名前・生年月日を入力して、送信ボタンを押してメッセージと年齢が表示されたら成功です。
Discussion