Im letzten Teil der Serie haben wir xmlservice-ts mit einer Klasse namens „Connection“ erweitert. Die Klasse stellt einen xmlservice-cli-Prozess dar und ermöglicht es uns per eleganter Syntax den XMLSERVICE-Job zu bedienen.
Nun wollen wir aber auch zustandsvolle Prozesse ausführen. Im ersten Teil der Serie haben wir herausgefunden, dass wir dafür xmlservice-cli mit den Parametern „-c“ und „-i“ aufrufen müssen.
Das heißt der Befehl müsste so lauten:
xmlservice-cli -c “*sbmjob“ -i “/tmp/xmlservice-test.ipc”
Wie wir uns erinnern, stellt die xmlservice-test.ipc Datei das Socket für die Interprozesskommunikation dar.
Quellcodeanpassungen
Damit wir zwischen zustandsvollen und zustandslosen Aufrufen unterscheiden können, erweitern wir die Parameter für den Konstruktor:
export interface ConnectionConfig {
xmlservicePath?: string;
stateful?: boolean;
ipcPath?: string;
}
(Das Fragezeichen im Namen bedeutet, dass es sich um optionale Parameter handelt.)
Standardmäßig wollen wir das Socket vorerst im /tmp-Ordner erstellen, aber man soll die Möglichkeit bekommen auch einen eigenen Pfad anzugeben. Damit wir mehrere IPC-Sockets gleichzeitig verwenden können, werden wir im Namen der Datei eine UUID anhängen, so dass der vollständige Pfad am Ende dieses Format hat:
/tmp/xmlservice- 111df48d-567e-4f07-847d-c8356fccd06d.ipc
Hierfür werden wir die randomUUID-Funktion aus dem “crypto“-Modul von Node.js verwenden. Das tun wir so:
import { randomUUID } from ‘crypto’;
Und passen die Connection-Klasse wie folgt an:
export class Connection {
#xmlservicePath: string;
#stateful: boolean;
#ipcPath: string;
// die spawn-Funktion kann dem xmlservice-Aufruf Parameter als ein Array übergeben
#xmlserviceParams: string[];
constructor(config?: ConnectionConfig) {
this.#xmlservicePath = config?.xmlservicePath || ‘/QOpenSys/pkgs/bin/xmlservice-cli’;
this.#ipcPath = ”;
this.#stateful = config?.stateful || false;
this.#xmlserviceParams = [];
if (this.#stateful) {
this.#ipcPath = config?.ipcPath || `/tmp/xmlservice-${randomUUID()}.ipc`;
this.#xmlserviceParams.push(‘-c’, ‘*sbmjob’, ‘-i’, this.#ipcPath);
}
}
// … ausgeblendeter Code
}
In der „exec“-Methode müssen wir diese Parameter übergeben:
const xmlservice = spawn(this.#xmlservicePath, this.#xmlserviceParams);
Jetzt können wir unsere neue Connection-Klasse in unserer Werkbank mit folgendem Code ausprobieren:
const { Connection } = require(“xmlservice-ts”);
async function main() {
try {
const xmlservice = new Connection({
stateful: true,
});
let result = await xmlservice.execute(
`<?xml version=’1.0′?>
<script>
<cmd>ADDLIBLE GUENEY<cmd>
</script>`
);
if (result.code === 0) {
result = await xmlservice.execute(
`<?xml version=’1.0′?>
<script>
<pgm name=’HELLOKERIM’>
</pgm>
</script>`
);
}
} catch (error) {
console.error(“Error when calling xlmservice-cli”, error);
}
}
main();
Wie wir sehen, rufen wir xmlservice-cli in getrennten Schritten auf. Dabei geben wir beim PGM-Aufruf das „lib“-Attribut im XML nicht mehr mit. D. h. wir rufen das PGM nicht mehr vollqualifiziert auf. Wir erinnern uns aus Teil eins, dass dies bei einem zustandslosen Aufruf zu einem Fehler führen würde.
In diesem Fall wurde xmlservice-cli korrekt zustandsvoll aufgerufen, denn in unserem /tmp-Verzeichnis sehen wir die IPC-Datei:
Wie im ersten Teil der Serie gezeigt, gehört diese Datei dem jeweiligen XTOOLKIT Job:
Damit wir den Job aber auch programmatisch beenden können, müssen wir eine Methode erstellen, die den folgenden Befehl ausführt:
xmlservice-cli -c ‘*immed’ -i ‘/tmp/ xmlservice-bb58e9e1-21a1-4e16-abcb-f85d2f292254.ipc’
Wir nennen sie die „end“-Methode und sie funktioniert so ähnlich wie die „exec“:
end(): Promise<XmlserviceResult> {
return new Promise((resolve, reject) => {
const result: XmlserviceResult = {
output: null,
signal: null,
code: null,
};
// nur wenn stateful, dann IPC-Datei löschen
if (this.#stateful) {
const xmlservice = spawn(this.#xmlservicePath, [‘-c’, ‘*immed’, ‘-i’, this.#ipcPath]);
xmlservice.on(‘close’, (code, signal) => {
result.code = code;
result.signal = signal;
if (code === 0) {
resolve(result);
} else {
reject(result);
}
});
xmlservice.stdin.end();
} else {
result.code = 0;
resolve(result);
}
});
}
Da wir den IPC-Pfad in dem Klassenfeld #ipcPath intern festhalten, können wir unseren IPC-Socket elegant wieder schließen.
Jetzt können wir unseren Testcode wie folgt abändern:
let result = await xmlservice.execute(
`<?xml version=’1.0′?>
<script>
<cmd>ADDLIBLE GUENEY</cmd>
</script>`
);
if (result.code === 0) {
result = await xmlservice.execute(
`<?xml version=’1.0′?>
<script>
<pgm name=’HELLOKERIM’>
</pgm>
</script>`
);
}
xmlservice.end();
Und stellen damit sicher, dass die IPC-Verbindung nach Gebrauch wieder geschlossen wird.
Ergebnis
Sehr schön! Wir haben nun effektiv ein Modul, welches uns erlaubt in Node.js einen zustandsvollen XMLSERVICE-Job zu bedienen. Natürlich sind wir noch nicht fertig, denn xmlservice-ts soll in Zukunft noch mindestens folgende Features unterstützen:
- XML-Übergabe abstrahieren
- PGM
- CL
- SQL
- Mit dem System über SSH oder ODBC kommunizieren
Die weitere Entwicklung des Projektes kann man auf GitHub verfolgen.
Der Autor Kerim Güney schreibt regelmäßig für den TechKnowLetter.
Sie erreichen ihn unter:
kerim[at]gueney.io
Sechs Ausgaben des TechKnowLetters erhalten Sie hier für 88 Euro.