Java-Methoden aus RPGLE aufrufen

Image licensed by Ingram Image/adpic

Hallo liebe Leserinnen und Leser,

in den vorangegangenen Blog-Artikeln haben wir uns  damit beschäftigt, wie Java-Programme auf einer AS/400 (System-i) deployed und ausgeführt werden können. Angefangen bei der einfachen Ausführung über die QSH (Qshell) haben wir uns bis zu komplexeren Szenarien vorgearbeitet, bei denen wir ein Start-Skript und danach ein RPGLE-Programm zur Ausführung unseres Java-Programms verwendet haben. Jetzt gehen wir einen Schritt weiter und schauen uns an, wie wir Java-Methoden direkt aus einem RPGLE-Programm heraus aufrufen können.

Was ist Prototyping?

Bevor wir uns in den Code vertiefen, ein kurzer Exkurs, was „Prototyping“ eigentlich ist. In RPGLE ermöglicht uns das Prototyping, Funktionen oder Prozeduren mit einer definierten Schnittstelle aufzurufen. Damit können wir sicherstellen, dass wir die richtige Anzahl und Art von Parametern übergeben und zurückbekommen. Wenn wir dies auf externe Programme oder, wie in unserem Fall, Java-Methoden anwenden, sprechen wir ebenfalls von Prototyping. Es ist sozusagen die „Vertragsdefinition“ zwischen den beiden unterschiedlichen Welten von RPGLE und Java.

Was macht der Java-Code?

Der Java-Code gehört zum Paket `parmtest` und definiert eine Klasse namens `ParmTest`. Die Hauptfunktion `main` ist für die Verarbeitung von Kommandozeilenargumenten zuständig. Wenn das Argument „list“ ist, werden alle System-Properties ausgegeben. Andernfalls wird die angeforderte System-Property zurückgegeben. Zusätzlich gibt es Methoden wie `getProperty`, `getProperties`, und `getArrayLength`, die auch von RPGLE aus aufgerufen werden können:

  • `getProperty(String key)`: Gibt eine Zeichenkette zurück, die den Schlüssel und den zugehörigen Wert aus den Systemeigenschaften enthält.
  • `getProperties()`: Liefert alle System-Properties als String-Array zurück.
  • `getArrayLength(Object[] pArray)`: Ermittelt die Länge eines übergebenen Objektarrays.

Wie werden Java-Methoden in RPGLE eingebunden und aufgerufen?

Jetzt zum interessanten Teil: der RPGLE-Code. Wir sehen mehrere Prototypen für die Java-Methoden:

  • `getBytes`: Holt die Byte-Repräsentation eines Java-Strings.
  • `getArrayLength`: Ruft die Methode `getArrayLength` aus der `ParmTest`-Klasse auf.
  • `makeString`: Erzeugt ein Java-String-Objekt.
  • `createParmTest`: Erzeugt ein Objekt der Java-Klasse `ParmTest`.
  • `getProperty`: Ruft die Methode `getProperty` in `ParmTest` auf.
  • `getProperties`: Ruft die Methode `getProperties` in `ParmTest` auf.

Arbeitsablauf im RPGLE-Code:

  • Klassenpfad und Umgebung: Mit `setCLASSPATH` wird die Umgebungsvariable `CLASSPATH` für Java gesetzt.
  • Parameterverarbeitung: Der Schlüsselparameter `pKey` wird verarbeitet und als Java-String-Objekt `keyObj` gespeichert.
  • Objekterstellung: Ein `ParmTest`-Objekt wird mit `createParmTest` erzeugt.
  • Bedingungsprüfung und Methodenaufruf:
    – Wenn der Schlüssel „list“ ist, wird `getProperties` aufgerufen, und alle System-Properties werden ausgegeben.
    – Andernfalls wird `getProperty` aufgerufen, um eine bestimmte System-Property auszugeben.

Durch die Verwendung von Prototypen wird der Aufruf der Java-Methoden stark vereinfacht, und wir können die Stärken beider Sprachen in einem integrierten Programm nutzen.

Java-Code

package parmtest;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Properties;

/**
 *
 * @author Guido Zeuner / MIT License
 */
public class ParmTest {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {

        ParmTest parmTest = new ParmTest();

        //Prüfen, ob ein Parameter übergeben wurde
        if (args.length > 0) {
            String arg = args[0].trim();
            //Alle System-Properties anzeigen, wenn Parameter = "list"
            if (arg.equalsIgnoreCase("list")) {
                String[] results = parmTest.getProperties();
                for(String result : results) {
                    System.out.println(result);
                }
            } else {
                //Gewählte System-Property anzeigen, wenn Parameter = [property.name]
                System.out.println(parmTest.getProperty(arg));
            }
        } else {
            //Mögliche Startparameter ausgeben
            System.out.println("Parameter [list] oder [property name]");
        }
    }

    // Used from RPG and Java Code
    public String getProperty(String key) {
        return String.format("%s = %s", key, System.getProperty(key));
    }

    // Used from RPG and Java Code
    public String[] getProperties() {

        ArrayList result = new ArrayList();
        Properties env = System.getProperties();
        Enumeration keys = env.keys();
        while (keys.hasMoreElements()) {
            String key = (String) keys.nextElement();
            String value = env.getProperty(key);
            result.add(String.format("%s = %s", key, value));
        }

        String[] props = new String[result.size()];
        result.toArray(props);

        return props;
    }

    // Used from RPG-Code to retrieve the length an Array
    public int getArrayLength(Object[] pArray) {

        int arrayLen = 0;
        if (pArray != null) {
            arrayLen = pArray.length;
        }
        return arrayLen;
    }
}

RPGLE-Code

      *****************************************************************************
      *   Programm     : TESTCALL1                                                *
      *   Beschreibung : Starten von Java Prozessen mit Parametern                *
      *                                                                           *
      *   erstellt am  : 13.08.2017 von Guido Zeuner  | MIT License               *
      *                                                                           *
      *   Beispiel-Aufruf                                                         *
      *   CALL PGM(TESTCALL) PARM('user.country')                                 *
      *****************************************************************************
     H DFTACTGRP(*NO)
     H ACTGRP(*CALLER)

      //Prototype-/Interface with Parameter to call this PGM
     D entry           PR                  ExtPgm('TESTCALL1')
     D  pKey                         20a   Const OPTIONS(*VARSIZE)

     D entry           PI
     D  pKey                         20a   Const OPTIONS(*VARSIZE)

      //Prototype for Calls to QCMDEXC
     D ExecCmd         Pr                  ExtPgm('QCMDEXC')
     D  Cmd                        2048a   Const
     D  CmdLen                       15  5 Const

      //Variable for Command-Output to QCMDEXC
     D CmdLength       s             15  5

      //Field to store a ShellCommand
     D shellCommand    S           1024A   varying

      //Field to store the length of a Parameter
     D parmLength      s             10I 0

      //Used for Output of the results via DSPLY
     D resultContent   s             52a   varying

      //Java-Classpath
     D CLASSPATH       s           2048a   varying

      //Prototype - get Value of s String
     D getBytes        PR         65535A   EXTPROC(*JAVA:
     D                                       'java.lang.String':
     D                                       'getBytes') VARYING

      //Prototype - get length of an Java Object Array
     D getArrayLength  PR            10I 0  EXTPROC(*JAVA:
     D                                       'parmtest.ParmTest':
     D                                       'getArrayLength')
     D arrObj                          O    CLASS(*JAVA:
     D                                      'java.lang.Object')
     D                                       DIM(500)

      //Prototype - Constructor for java String from Byte array
     D makeString      PR              O    EXTPROC(*JAVA:
     D                                       'java.lang.String':
     D                                       *CONSTRUCTOR)
     D  bytes                      1024A    CONST VARYING

      //Prototype - Constructor for java IntegerObj from INT(10)
     D makeInteger     PR              O     EXTPROC(*JAVA:
     D                                        'java.lang.Integer':
     D                                       *CONSTRUCTOR)
     D  integer                      10I 0    value

      //Prototype - Constructor for ParmTest.class
     D createParmTest  PR              O    EXTPROC(*JAVA:
     D                                       'parmtest.ParmTest':
     D                                       *CONSTRUCTOR)

      //Prototype - Method String getProperty(String key) in ParmTest.class
     D getProperty     PR              O    CLASS(*JAVA:
     D                                      'java.lang.String')
     D                                      EXTPROC(*JAVA:
     D                                      'parmtest.ParmTest':
     D                                      'getProperty')
     D keyObj                          O    CLASS(*JAVA:
     D                                      'java.lang.String')

      //Prototype - Method String[] getProperties() in ParmTest.class
     D getProperties   PR              O    CLASS(*JAVA:
     D                                      'java.lang.String')
     D                                      DIM(500)
     D                                      EXTPROC(*JAVA:
     D                                      'parmtest.ParmTest':
     D                                      'getProperties')

      //Field to store a property key as Java String Object
     D parmTestObj     S               O    CLASS(*JAVA:'ParmTest')
      //Field to store the passed in property Key
     D key             S             50A    VARYING
      //Field to store a property key as Java String Object
     D keyObj          S               O    CLASS(*JAVA:'java.lang.String')
      //Field to store the return result
     D result          S           1024A    VARYING
      //Field to store a result as Java String Object
     D resObj          S               O    CLASS(*JAVA:'java.lang.String')
      //Field to store the ArrayLength
     D arrayLength     S             10I 0
      //Index for array Elements
     D arrayElement    S             10I 0
      //Field to store results as Java String Object
     D resArrayObj     S               O    CLASS(*JAVA:'java.lang.String')
     D                                      DIM(500)

      /free

       exSr setCLASSPATH;

       parmLength = %len(%trim(pKey));
       key = %subst(pKey:1:parmLength);
       keyObj = makeString(key);
       parmTestObj = createParmTest();

       if key = 'list';
         resArrayObj = getProperties(parmTestObj);
         arrayLength = getArrayLength(parmTestObj:resArrayObj);
         for arrayElement = 1 to arrayLength;
            resObj = resArrayObj(arrayElement);
            if resObj <> *NULL;
               result = getBytes(resObj);
               resultContent = result;
               dsply resultContent;
            endif;
         endfor;
         exsr wait;
       else;
       resObj = getProperty(parmTestObj:keyObj);
       result = getBytes(resObj);
       resultContent = result;
       dsply resultContent;
       exsr wait;
       endif;

       *Inlr = *On;

        //Set Classpath ENV-VAR via QCMDEXC
       begsr setCLASSPATH;

         CLASSPATH = '/home/YOURLIB/ParmTest.jar';

         shellCommand = 'ADDENVVAR ENVVAR(CLASSPATH) VALUE(''' +
                         CLASSPATH + ''') REPLACE(*YES)';
         CmdLength = %len(%trim(shellCommand));
         ExecCmd(shellCommand:cmdLength);
       endsr;

        //DelayJob via QCMDEXC
       begsr wait;

         shellCommand = 'QSYS/DLYJOB DLY(2)';
         CmdLength = %len(%trim(shellCommand));
         ExecCmd(shellCommand:cmdLength);
       endsr;

      /end-free

ÜBERTRAGUNG AUF IBM I

Das vorhandene JAR-File (s. GitHub Repo) können wir auf einen IBM i Server übertragen. Eine einfache Möglichkeit ist die Verwendung von FTP:

  1. Öffnen Sie ein Terminal und starten FTP mit ftp .
  2. Loggen Sie sich mit Ihrem Benutzernamen und Passwort ein.
  3. Navigieren Sie auf auf der AS/400 zu Ihrem /home/ihruser Verzeichnis mit cd /home/ihruser .
  4. Navigieren Sie auf Ihrem lokalen System in das Verzeichnis der Datei  ParmTest.jar mit lcd /ihrpfad
  5. Wählen Sie den Dateiübertragungstyp bin
  6. Übertragen Sie das JAR-File mit dem Befehl put ParmTest.jar.

Compilieren des RPGLE-Programms

CRTBNDRPG PGM(YOURLIB/TESTCALL1) SRCFILE(YOURLIB/QRPGLESRC) ACTGRP(*CALLER) DFTACTGRP(*NO) OPTION(*EVENTF)

Erläuterung:

  • PGM(LIBRARY/TESTCALL1): Damit legen Sie den Namen und den Speicherort für das kompilierte Programm fest.
  • SRCFILE(LIBRARY/QSOURCE): Das ist der Speicherort der Source-Datei, die den RPG-Code enthält.
  • ACTGRP(*CALLER): Dieser Parameter ist wichtig, weil er der im Code spezifizierten Aktivierungsgruppe entsprechen muss (ACTGRP(*CALLER)).
  • DFTACTGRP(*NO): Das deaktiviert die Verwendung der Default-Aktivierungsgruppe, entsprechend der Direktive H DFTACTGRP(*NO) im Code.

Beachten Sie, dass YOURLIB ein Platzhalter für den Namen der Ihrer Bibliothek ist.
Dieser sollte natürlich angepasst werden, um CRTBNDRPG korrekt aufzurufen.

  • OPTION(*EVENTF): Dies ermöglicht die Erzeugung einer Event-Datei, die zur Fehlerbehebung nützlich sein kann.

Aufruf des Java-Programms und des RPGLE-Programms

Sie können das Java-Programm auf verschiedene Arten aufrufen, um die Funktionalität zu testen:

  1. Auflisten aller System-Properties
    • Java: java -jar ParmTest.jar list
    • RPGLE: CALL PGM(YOURLIB/TESTCALL1) PARM('list')
  2. Anzeige einer spezifischen System-Property, z.B. user.country
    • Java: java -jar ParmTest.jar user.country
    • RPGLE: CALL PGM(YOURLIB/TESTCALL1) PARM('user.country')

Beachten Sie, dass YOURLIB ein Platzhalter für den Namen der Bibliothek ist, in der Ihr RPGLE-Programm gespeichert ist. Dieser sollte natürlich angepasst werden, um das Programm korrekt aufzurufen.

Bibliotheksnamen anpassen

Ein wichtiger Hinweis ist die Anpassung des Bibliotheksnamens. Wenn Sie den Code in Ihrer eigenen Umgebung ausführen, stellen Sie sicher, dass YOURLIB durch den tatsächlichen Namen der Bibliothek ersetzt wurde, in der Ihr RPGLE-Programm abgelegt ist. Diese Anpassung ist entscheidend für die erfolgreiche Ausführung des Codes.

Alle Beispiele auf GitHub

https://github.com/gzeuner/RPG4J

Viel Spaß beim Ausprobieren.

Weitere Informationen zum Thema

RPG and Java