PCML : Gérer l’usage des paramètres lors déploiements de web service par script
Nous avons vu dans le premier article sur le PCML, que la gestion de l’usage des paramètres ne peut se faire dans le source d’un programme. On peut au mieux déclarer les paramètres en entrée, par le mot clé « const ».
Tous les autres paramètres seront déclarés en entrée/sortie dans le PCML.
Lors de déploiements de web service par script, il faut pouvoir gérer ces paramètres, de façon à générer des WSDL (SOAP) ou des SWAGGER (REST) corrects.
Cet article recense plusieurs méthodes possibles pour cette gestion.
PCML généré dans l’IFS – ajustement manuel :
Prérequis : génération du PCML dans l’IFS, options de compilation PGMINFO et INFOSTMF à renseigner.
On peut éditer le fichier et le modifier manuellement.
Il faudra faire attention à bien respecter le formalisme du PCML.
Avantages :
- Mise en œuvre minimaliste.
Inconvénients :
- Il faut penser après génération du PCML à le modifier, risque d’oubli comme pour toutes procédures peu usitées.
- Risque d’écraser le PCML modifié par le PCML standard lors d’une prochaine compilation.
- Risque d’erreur dans la modification, rendant inopérant le PCML.
- Ce n’est pas sport !
Cette méthode peut être utilisée si vous n’exposez que peu de web service, contenant que quelques paramètres. Même si nous la déconseillons fortement.
PCML généré dans l’IFS – ajustement programmatique :
Prérequis : génération du PCML dans l’IFS, options de compilation PGMINFO et INFOSTMF à renseigner.
Plutôt que d’éditer un PCML dans l’IFS, on peut prévoir de le modifier par programme, en se basant sur un formalisme de nommage des paramètres.
Par exemple, en suffixant le paramètre en entrée par « _I » et les paramètres en sortie par « _O ». Les paramètres en entrée/sortie n’auront pas besoin de suffixe, leurs usages dans le PCML est déjà correct.
Le programme devra mettre à jour le PCML dans l’IFS, en transformant l’usage des paramètres selon le suffixe.
Pour exemple, dans l’extrait de code ci-dessous, nous utilisons les API « C » pour modifier le PCML :
Si vous le souhaitez, nous pouvons sur demande fournir le code complet.
// Ouverture du fichier en mise à jour fd = OpenFile(Path : O_RDWR + O_TEXTDATA + O_CCSID : S_IRGRP : 37 ) ; if (fd < 0) ; // fichier non ouvert : non trouvé ou vérouillé ou ... // gestion personnalisée à faire *inlr = *on ; return ; endif ; // Boucle de lecture infinie - sortie explicite via le leave // lecture du fichier totale, avec recherche de chaîne Pos = 0 ; dow (1 = 1) ; // récupération de la longueur du fichier entre la position actuelle // et la fin de fichier au premier passage, longueur totale du PCML Length = ReadFile(fd:%addr(Data):%size(Data)) ; // Longueur à 0 --> fin de fichier on sort de la boucle if (Length = 0); leave; else; // Recherche du tag de déclaration d'un paramètre // on récupère sa position Debut = %scan('<data name="' : Data) ; if Debut > 0; // Extraction de la position du suffixe // En fin de nom paramètre sur 2 Debut += 12 ; // longueur chaîne '<data name="' DebSuf = %scan('"' : Data : Debut) - 2; // Extraction de la position de l'usage du paramètre DebUsage = %scan('usage="' : Data : Debut) + 6; // + lng'usage="' // détermination de l'usage en fonction du suffixe select; when %scan('_I' : Data : DebSuf : 2) > 0; UsageIO = 'input" />' ; when %scan('_O' : Data : DebSuf : 2) > 0; UsageIO = 'output" />' ; other; UsageIO = 'inputoutput" />' ; endsl; // positionnement sur l'adresse de début de modification Pos += DebUsage; Data = UsageIO ; lseek(fd: Pos: SEEK_SET) ; // modification du fichier WriteOK = write(fd:%addr(data): 15) ; Pos += 15 ; endif; endif; enddo;
PCML issu de la compilation :
<pcml version="6.0"> <!-- RPG program: TESTWS --> <!-- created: 2018-03-12-11.09.56 --> <!-- source: DT/QRPGLESRC(TESTWS) --> <!-- 3 --> <program name="TESTWS" path="/QSYS.LIB/DT.LIB/TESTWS.PGM"> <data name="VARIN_I" type="char" length="15" usage="inputoutput" /> <data name="VAROUT_O" type="char" length="30" usage="inputoutput" /> </program> </pcml>
PCML après traitement par le PGM :
<pcml version="6.0"> <!-- RPG program: TESTWS --> <!-- created: 2018-03-12-11.09.56 --> <!-- source: DT/QRPGLESRC(TESTWS) --> <!-- 3 --> <program name="TESTWS" path="/QSYS.LIB/DT.LIB/TESTWS.PGM"> <data name="VARIN_I" type="char" length="15" usage="input" /> <data name="VAROUT_O" type="char" length="30" usage="output" /> </program> </pcml>
Avantages :
- Automatisation possible, à la création du web service, en masse sur un répertoire de l’IFS, … Au gré des besoins !
- Si vous avez l’utilité du PCML pour d’autres applications que l’exposition des web services, vous pouvez par ce biais ne gérer qu’une fois cette problématique.
Inconvénients :
- Risque d’oubli de génération du PCML dans l’IFS.
- Risque d’écraser le PCML modifié par le PCML standard lors d’une prochaine compilation.
- Risque de conflit entre les normes de nommage, ou de non utilisation de la norme.
- Si vous avez plusieurs partitions, il faudra soit déployer le PCML dans l’IFS de chaque partition, soit le générer dans l’IFS puis le modifier via le programme.
Extraction des informations du PCML d’un objet :
Prérequis : génération du PCML dans l’objet, les options de compilation peuvent être dans le source.
Cette méthode consiste à :
- Extraire les informations du PCML, via l’API QBNRPII. Les informations relatives aux paramètres seront stockés dans fichier.
- Gérer l’usage des paramètres trouvés, par un interactif.
- Générer le paramètre -parameterUsage à ajouter à la commande shell de création de web service.
Programme d’extraction des informations PCML :
**free /if defined(*crtbndrpg) ctl-opt actgrp(*NEW) ; /endif ctl-opt option(*NODEBUGIO : *SRCSTMT); //-------------------------------------------------------------------------------------------------/ // Paramètre PGM Dcl-Pi *N; P_Objet Char(10) const; P_Lib Char(10) const; P_Type Char(10) const; P_Error Char(110); End-Pi; //-------------------------------------------------------------------------------------------------/ // DS objet et module pour appel APi dcl-ds qualname qualified based(TypeDef); Obj Char(10); Lib Char(10); end-ds; dcl-ds Qbn_Interface_Entry_t qualified based(TypeDef); // Offset from start of receiver Offset_Next_Entry int(10) ; Module_Name Char(10) ; Module_Library Char(10) ; Interface_Info_CCSID int(10) ; Interface_Info_Type int(10) ; Offset_Interface_Info int(10) ; Interface_Info_Length_Ret int(10) ; Interface_Info_Length_Avail int(10) ; end-ds; dcl-ds Qbn_PGII0100_t qualified based(TypeDef); Bytes_Returned int(10) ; Bytes_Available int(10) ; Obj_Name Char(10) ; Obj_Lib_Name Char(10) ; Obj_Type Char(10) ; Reserved3 Char(2) ; Offset_First_Entry int(10) ; Number_Entries int(10) ; end-ds; dcl-ds errcode qualified; bytesprov int(10) inz(0); bytesavail int(10) ; end-ds; dcl-ds psds psds ; codmsg char(7) pos(40); Libmsg char(100) pos(91); end-ds; // Define the initial storage for the first call to the API dcl-ds tempRcvr likeds(Qbn_PGII0100_t); dcl-ds rcvr likeds(Qbn_PGII0100_t) based(pRcvr); dcl-ds entry likeds(Qbn_Interface_Entry_t) based(pEntry); dcl-ds NomObj likeds(qualname); dcl-ds NomMod likeds(qualname); //-------------------------------------------------------------------------------------------------/ // Constantes dcl-c AllMod '*ALLBNDMOD' ; dcl-c LibModPGM 'QTEMP' ; dcl-c Format_retour 'RPII0100' ; // tailleXml doit avoir la même valeur que la variable XMLVALUE dcl-c tailleXml 8192 ; //-------------------------------------------------------------------------------------------------/ // Variables dcl-s pRcvr pointer inz(*null); dcl-s data Char(tailleXml) based(pData); dcl-s pcml Varchar(tailleXml); dcl-s len int(10); dcl-s i int(10); dcl-s entry_off int(10); //-------------------------------------------------------------------------------------------------/ // Prototype d'appel de l'API QBNRPII (Retrieve Program Interface Information) dcl-pr QBNRPII extpgm('QBNRPII') ; Receiver_variable likeds(Qbn_PGII0100_t); Length_of_receiver_variable int(10) const; Format_name Char(8) const; Qualified_object_name likeds(qualname) const; Object_Type Char(10) const ; Qualified_bound_module_name likeds(qualname) const; Error_code likeds(errcode); end-pr; // Prototype d'initialisation SQL dcl-pr initSQL end-pr; // Prototype déallocation mémoire dcl-pr cleanup end-pr; // Prototype extraction paramètre dcl-pr rtvparam ind end-pr; //-------------------------------------------------------------------------------------------------/ initSQL(); // Construction des DS objet et module pour l'appel de l'API NomObj.Obj = P_Objet ; NomObj.Lib = P_Lib ; if P_Type = '*PGM' ; // Dans le cas d'un programme, nom module = nom PGM et bib = QTEMP NomMod.Obj = P_Objet ; NomMod.Lib = LibModPGM ; else; // Dans le cas d'un programme de service, appel de l'API pour tous les modules NomMod.Obj = AllMod ; NomMod.Lib = *blanks ; endif; // Appel de l'API - détermination de la taille du retour contenant l'ensemble des PCML callp(e) QBNRPII (tempRcvr : %size(tempRcvr) : Format_retour : NomObj : P_Type : NomMod : errcode); // Non trouvé : objet ou programme ou bibliothèque if %error; P_Error = codmsg + ' - ' + libmsg ; cleanup() ; return; endif; // Pas de PCML if tempRcvr.Bytes_Available <= tempRcvr.Bytes_Returned; P_Error = 'Aucun PCML trouvé dans l''objet ' + P_Objet + ' de la bibliothèque ' + P_Lib ; cleanup() ; return; endif; pRcvr = %alloc(tempRcvr.Bytes_Available); callp(e) QBNRPII (rcvr : tempRcvr.Bytes_Available : Format_retour : NomObj : P_Type : NomMod : errcode); // Normalement, aucune erreur à ce niveau, mais par sécurité on refait les tests if %error or rcvr.Number_Entries = 0 ; if codmsg = *blanks ; P_Error = 'Erreur API - aucune donnée retournée'; else; P_Error = codmsg + ' - ' + libmsg ; endif; cleanup(); return; endif; // Lecture de la variable retour par PCML // --> Pour les *PGM, un seul PCML, pour les *SRVPGM, autant de PCML que de modules entry_off = rcvr.offset_First_Entry; for i = 1 to rcvr.Number_Entries; pEntry = pRcvr + entry_off; entry_off = entry.Offset_Next_Entry; pData = pRcvr + entry.Offset_Interface_Info; len = entry.Interface_Info_Length_Ret; // Si le PCML récupéré est plus grand que la taille de la variable prévue --> erreur if len > tailleXml ; P_Error = 'Taille PCML : ' + %char(len) + ' trop grande pour la variable retour : ' + %editc(tailleXml : 'X') ; cleanup() ; return; endif; pcml = %subst(data : 1: len); if not rtvparam() ; return; endif; endfor; cleanup(); P_Error = *blanks ; return; //----------------------------------------------------------------------------------------// // Procédure d'initialisation SQL // //----------------------------------------------------------------------------------------// dcl-Proc initSQL; Dcl-Pi *n end-Pi; // initialisation des options de compilation sql EXEC SQL Set Option Naming = *Sys, Commit = *None, UsrPrf = *User, DynUsrPrf = *User, Datfmt = *iso, CloSqlCsr = *EndMod; // création de la table de travail dans QTEMP, ou remise à blanc si elle existe déjà EXEC SQL create or replace table qtemp.wsparam (PROCNAME varchar(128), ENTRPT varchar(128), PARNAME varchar(128), USAGEIO varchar(11)) on replace delete rows; end-proc; //----------------------------------------------------------------------------------------// // Procédure de désallocation de la mémoire // //----------------------------------------------------------------------------------------// dcl-Proc Cleanup; Dcl-Pi *n end-Pi; if pRcvr <> *null and pRcvr <> %addr(tempRcvr); dealloc(n) pRcvr; endif; end-proc; //----------------------------------------------------------------------------------------// // Procédure d'extraction des paramètres // //----------------------------------------------------------------------------------------// dcl-Proc rtvparam; Dcl-Pi *n ind end-Pi; dcl-s XMLVALUE sqltype(XML_CLOB : 8192); // conversion du texte PCML en un champs XML EXEC SQL set :XMLVALUE = xmlparse(document :pcml) ; if sqlcode <> 0 ; P_Error = 'Erreur de conversion XML, SQLCODE = ' + %char(sqlcode); cleanup() ; return *off; endif; // Extraction du XML du nom du module, du nom de point d'entrée, du nom des paramètres et de leur usage, // dans une table temporaire de QTEMP EXEC SQL insert into qtemp.wsparam select x.* from xmltable('$d/pcml/program/data' passing :XMLVALUE as "d" columns PROCNAME varchar(128) path '../@name', ENTRPT varchar(128) path '../@entrypoint', PARNAME varchar(128) path '@name', USAGEIO varchar(11) path '@usage') as X ; if sqlcode <> 0 ; P_Error = 'Erreur insertion WSPARAM, SQLCODE = ' + %char(sqlcode); cleanup() ; return *off; endif; return *on ; END-PROC;
A partir du fichier généré, vous pouvez gérer l’usage des paramètres :
Il ne reste plus qu’à créer le paramètre de la commande shell.
Extrait de code de la génération du paramètre -parameterUsage :
formalisme : -parameterUsage PROC1:i,o:PROC2:i,i,io
Attention à la casse des points d’entrée (PROC1 / PROC2…).
dcl-Proc gener_Opt; Dcl-Pi *n end-Pi; Dcl-s LProc char(128); Dcl-s LEntrpt char(128); Dcl-s LParam char(128); Dcl-s LProcSh char(128); Dcl-s LUsage char(11); Dcl-s LProcSV char(30) inz(*blanks); Dcl-s param1 ind; //------------------------------------// // Constantes dcl-c Deb_Opt '-parameterUsage '; dcl-c SRVPGM '*SRVPGM' ; //------------------------------------// // Création d'un curseur récupérant les données du fichier de travail QTEMP/WSPARAM EXEC SQL declare curs02 cursor for SELECT PROCNAME, ENTRPT, PARNAME, USAGEIO FROM WSPARAM; EXEC SQL open curs02; // Lecture du fichier WSPARAM, pour alimentation de la variable retour dou sqlcode <> 0; EXEC SQL fetch from curs02 into :LProc, :LEntrpt, :LParam, :LUsage; if sqlcode = 0; //Entête module if LProc <> LProcSV ; // détermination du nom de la procédure pour la commande Shell (casse sensitive) if P_Type = SRVPGM ; LProcSh = LEntrpt ; else ; LProcSh = LProc ; endif; // Insertion du nom de la procédure dans la commande if LProcSV = *blanks; P_Opt = Deb_Opt + %trim(LProcSh) +':'; // initialisation commande - 1ière proc else; P_Opt = %trim(P_Opt) + ':' + %trim(LProcSh) +':'; // Ajout 2ième procédure et suivan endif; LProcSV = LProc; param1 = *on ; endif; //détail paramètre - construction de l'usage des paramètres if param1 ; param1 = *off ; else; P_Opt = %trim(P_Opt) + ','; endif; select ; when LUsage = Input; P_Opt = %trim(P_Opt) + 'i'; when LUsage = Output; P_Opt = %trim(P_Opt) + 'o'; other; P_Opt = %trim(P_Opt) + 'io'; endsl; endif; enddo; EXEC SQL close curs02; end-proc;
Dans le cas de web service de type REST, il faudra alimenter le fichier de propriété avec les bons usages des paramètres au lieu de construire le paramètre -parameterUsage.
Avantages :
- Gestion de l’usage des paramètres au déploiement du web service.
- Intégration simple au scénario de déploiement de web service ou autre traitement si besoin.
- Dans le cas de sauvegarde du script de déploiement, il est directement utilisable sur une autre partition.
Inconvénients :
- Aucun lorsqu’on a le source à disposition !
Cette dernière méthode sera intégrée à la prochaine version de notre outils WEBCONSOLE.