Java on z/OSからSDSFを操作する

# SDSFって何？
z/OSというメインフレームOSを一度でもお使いになったことのある方であれば、独特の”パネル式”インターフェースに戸惑われたことでしょう。その中でも、ジョブを管理するためのユーティリティーがSDSF (System Display and Search Facility)であり、ジョブの状態やリソースの使用状況等をリアルタイムに確認することが可能です。
このSDSF、通常は画面（パネル）で操作するものと思いますが、プログラムから操作することも可能です。しかもJavaから。

# 何故JavaからSDSF？
もともとは、z/OS上のJOBの終了コードを外部メッセージングツール（具体的にはSlack）に送付したい、というのがあり、SDSFにアクセスする方法をしらべたのがきっかけです。REXXでアクセスできるのは知っていたので、REXXで終了コードを取得した後何らかの方法でSlackに送信しようかと思っていたのですが、z/OS V1.12 からJavaでもSDSFにアクセスできるとのことで、じゃあ情報の取得も送信もJavaでできそうだ、ということで調べ始めました。自分自身REXXはあんまり知らないというのもあるし。
ただ、JavaのSDSFアクセスに関する記事はネットを検索してもほとんどありませんでした。まあ当然といえば当然ですが。そういうわけで、せっかくなので記事にしてやろうというのがこの記事になります。
マニュアル、Javadoc、付属のサンプルコードと、わりと充実しているので、ネットに情報がなくてもコードを書くのはそんなに困りませんでした。


# 動かすための設定
大前提として、z／OS上でしか動きません。USS上でもJavaバッチででも動くのですが、とりあえずUSSで動かしてみましょう。CLASSPATHとLIBPATHに以下の設定が必要です。
```sh
export CLASSPATH=/usr/include/java_classes/isfjcall.jar:$CLASSPATH
export LIBPATH=/usr/lib/java_runtime64:$LIBPATH
```
なお、環境に問題がなければ、以下のコマンドで環境情報が出力されます。
```sh
java -cp classpath -jar /usr/include/java_classes/isfjcall.jar
```

# ドキュメントとサンプル
* 最初はやはりz/OSのマニュアルを参照しましょう。z/OS V2.5では以下のURLに記載されています。お使いのz/OSバージョンに合わせて該当のページを見てください。
Using SDSF with the Java Programming Language
https://www.ibm.com/docs/en/zos/2.5.0?topic=guide-using-sdsf-java-programming-language

* z/OS USS上の /usr/lpp/sdsf/java/classes/isfjcallDoc.jar というファイルに、JavaDocが含まれていますので、ダウンロードしてPCで見てみましょう。

* z/OS USS上の /usr/lpp/sdsf/java/samples に、サンプルのJavaプログラムがあります。(上記isjfcallDoc.jarにも含まれています）。以下のサンプルがあります。
<dl>
<dt>ISFBrowseJobDataSetSample.java</dt>
<dd>ISFStatusオブジェクトからJobデータ・セットをブラウズするサンプル</dd>
<dt>ISFBrowseSample.java</dt>
<dd>Spoolデータ・セットをブラウズするサンプル</dd>
<dt>ISFBrowseStatusJobSample.java</dt>
<dd>ISFStatusオブジェクトからSpoolデータ・セットをブラウズするサンプル</dd>
<dt>ISFChangeJobPrioritySample.java</dt>
<dd>Jobプライオリティーを変更するサンプル</dd>
<dt>ISFGetJobStepsSample.java</dt>
<dd>一つのJobのJobステップ情報をリストするサンプル</dd>
<dt>ISFGetJobsSample.java</dt>
<dd>Queue上のJobをリストするサンプル</dd>
<dt>ISFHealthCheckSample.java</dt>
<dd>Health Checkをブラウズするサンプル</dd>
<dt>ISFLineResultsSample.java</dt>
<dd>追加情報を含むOperlogレコードを示すサンプル</dd>
<dt>ISFSearchOperlogSample.java</dt>
<dd>Operlogをブラウズするサンプル</dd>
<dt>ISFSearchSyslogSample.java</dt>
<dd>Spoolデータ・セットをブラウズするサンプル</dd>
<dt>ISFSearchSyslogSample2.java</dt>
<dd>Spoolデータ・セットをブラウズするサンプル</dd>
<dt>ISFSlashCommandSample.java</dt>
<dd>MVSシステム・コマンドを発行するサンプル</dd>
<dt>ISFWhoCommandSample.java</dt>
<dd>SDSF WHOコマンドを呼び出すサンプル</dd>
</dl>


# 動かしてみよう
たとえば、SDSFのDA(Active User)で、ZC*で始まるJOBを見てみるとします。こんな感じ。

```text
SDSF DA ETP1     ETP1     PAG  0  CPU/L     1/  1      LINE 1-3 (3)            
NP   JOBNAME  StepName ProcStep JobID    Owner    C Pos DP Real Paging    SIO  
     ZC30ANGL ZC30ANGL STEP1    STC02826 LIBSRV     NS  FE  410   0.00   0.00  
     ZC30CI01 ZC30CI01 ZCON     STC03150 LIBSRV     IN  F4 156T   0.00   0.00  
     ZC30MQ03 ZC30MQ03 ZCON     STC03008 LIBSRV     IN  F4  59T   0.00   0.00  
```

以下のプログラムを使うと、ZC* で始まるJOBNAMEとJOBIDのリストを取得できます。
```java:TestDA.java
import java.io.* ;
import java.util.* ;
import com.ibm.zos.sdsf.core.* ;

public class TestDA {
  public static void main(String[] args) throws Exception {

    ISFRequestSettings settings = new ISFRequestSettings();
    settings.addISFPrefix("**");
    settings.addISFOwner("*");
    settings.addISFFilter("jname eq ZC*") ;

    ISFActiveRunner runner = new ISFActiveRunner(settings);
    List<ISFActive> objs = runner.exec() ;

    System.out.println("objs.size = " + objs.size()) ;
    if (objs != null) {
      for (ISFActive statObj : objs) {
        System.out.println(statObj.getJName() + " " + statObj.getJobID()) ;
      }
    }
  }
}
```

これを実行すると、以下のような結果が得られます。
```
$ java TestDA
objs.size = 3
ZC30ANGL STC02826
ZC30CI01 STC03150
ZC30MQ03 STC03008
```
期待通り、JOBNAMEとJOBIDが得られました。

ちなみに、これはActive Users(DA)の例ですが、
* ISFActiveRunner -> ISFStatusRunner
* ISFActive -> ISFStatus

と置き換えると、Status of jobs(ST)の結果と同じになります。簡単ですね！

# 大まかな流れ
このサンプルプログラムのおおまかな流れは、
1. フィルター設定をする(ISFRequestSettings)
ここでは `jname eq ZC*` というフィルターを設定しています。JOBIDで特定したければ `jobid eq STC02826` みたいな指定も可能です。ne(not equal)やlt(less than)のような演算子も使えるようです。 
2. 上記の設定をもとにrunnerを作る(ISFStatusRunner/ISFActiveRunner)
このrunnerオブジェクトを使ってSDSF操作を実行して結果を保持します。
3. runnerを実行し、ステータスオブジェクトを取得する(ISFStatus/ISFActive)
複数の結果が返るかもしれません。

# つぎに、JOBの中身を覗いてみよう

まずは、実際にSDSFで見てみます。さきほどのZC30ANGLの先頭でS(選択）を実行すると、こんな画面になります。
```
********************************* TOP OF DATA **********************************
                         J E S 2  J O B  L O G  --  S Y S T E M  E T P 1  --  N

10.12.12 STC02826 ---- FRIDAY,    24 NOV 2023 ---- 
10.12.12 STC02826  IEF695I START ZC30ANGL WITH JOBNAME ZC30ANGL IS ASSIGNED TO U
10.12.12 STC02826  $HASP373 ZC30ANGL STARTED
10.12.12 STC02826  CWWKB0079I THE ANGEL BUILD LEVEL IS 23.0.0.9 cl230920230904-1
   888             2023.9.0.0 cl230920230904-1158
10.12.12 STC02826  CWWKB0084I ANGEL HAS SUCCESSFULLY REGISTERED AS ELEMENT NAME 
   889             WITH THE AUTOMATIC RESTART MANAGER (ARM).
10.12.12 STC02826  CWWKB0069I INITIALIZATION IS COMPLETE FOR THE ZC30ANGL ANGEL 
        1 //ZC30ANGL JOB MSGLEVEL=1
        2 //STARTING EXEC ZC30ANGL
        3 XXBBGZANGL PROC
```
ちなみに画面をコピペしただけなので右端は切れています。

```java:TestMSG.java
import java.io.* ;
import java.util.* ;
import com.ibm.zos.sdsf.core.* ;

public class TestMSG {
  public static void main(String[] args) throws Exception {

    ISFRequestSettings settings = new ISFRequestSettings();
    settings.addISFPrefix("**");
    settings.addISFOwner("*");
    settings.addISFFilter("jname eq ZC30ANGL") ;

    ISFActiveRunner runner = new ISFActiveRunner(settings);
    List<ISFActive> objs = runner.exec() ;
    ISFRequestResults reqres = runner.getRequestResults() ;

    System.out.println("objs.size = " + objs.size()) ;
    if (objs != null) {
      ISFActive statObj = objs.get(0) ;
      List<ISFJobDataSet> list1 = statObj.getJobDataSets() ;

      for (ISFJobDataSet ds : list1) {
        ds.browse() ;
        List<String> response = reqres.getResponseList() ;
        System.out.println(">>>>> DDNAME = "  + ds.getDDName()) ;
        for(String str : response) {
          System.out.println(str) ;
        }
      }
    }
  }
}

```
これを実行すると、つぎのような結果になります。

```
$ java TestMSG
objs.size = 1
>>>>> DDNAME = JESMSGLG
1                         J E S 2  J O B  L O G  --  S Y S T E M  E T P 1  --  N O D E  N 1
0
 10.12.12 STC02826 ---- FRIDAY,    24 NOV 2023 ----
 10.12.12 STC02826  IEF695I START ZC30ANGL WITH JOBNAME ZC30ANGL IS ASSIGNED TO USER LIBSRV  , GROUP SYS1
 10.12.12 STC02826  $HASP373 ZC30ANGL STARTED
 10.12.12 STC02826  CWWKB0079I THE ANGEL BUILD LEVEL IS 23.0.0.9 cl230920230904-1158 /  888
    888             2023.9.0.0 cl230920230904-1158
 10.12.12 STC02826  CWWKB0084I ANGEL HAS SUCCESSFULLY REGISTERED AS ELEMENT NAME ZC30ANGL  889
    889             WITH THE AUTOMATIC RESTART MANAGER (ARM).
 10.12.12 STC02826  CWWKB0069I INITIALIZATION IS COMPLETE FOR THE ZC30ANGL ANGEL PROCESS.
>>>>> DDNAME = JESJCL
         1 //ZC30ANGL JOB MSGLEVEL=1                                               STC02826
         2 //STARTING EXEC ZC30ANGL
         3 XXBBGZANGL PROC
             :
```

どうでしょう、うまくとれていますね。
実際には、各DD名毎に文字列を取得しているのがわかるでしょうか。
これで、JOBIDがわかればJOBLOGを自由に読めるようになりました。

当初の目標だった、バッチの終了コードをSlackに送るということに関しては、そのJOBの最後にJavaのステップをくっつけて、「自分自身のJOBIDを取得して、そのJESMSGLGからReturn Codeに関係する部分のみ抜き出して送付」ということで実現しました。


# リンク
Usinf SDSF with the Java Programming Language
https://www.ibm.com/docs/en/zos/2.5.0?topic=guide-using-sdsf-java-programming-language
zOS_Joblog_to_Slack
https://github.com/Tomo-92/zOS_Joblog_to_Slack/blob/master/README.ja.md