Portfolio de proyectos
Proyecto: Interconectar sistemas AS400 y BD en JavaServer mediante sockets
La idea del siguiente proyecto es conectar un Mainframe bajo el sistema AS/400 con un servidor montado en Java para intercambiar un fichero de texto plano alocado en el IBM i Series, mediante una subida a un FTP, una vez realizada la subida se comprobara si ha sido correcta y de ser así se enviara un mensaje de OK al servidor mediante un programa codificado en RPG ILE usando sockets, si el mensaje de OK le llega al servidor montado en JAVA, este descargara el fichero del FTP e insertara los datos en una base de datos según el esquema que tengamos.
El CL que ejecutara el proceso
PGM
DCL VAR(&MYVAR) TYPE(*CHAR) LEN(02)
CHGVAR VAR(&MYVAR) VALUE('NE')
CLRPFM FJCRUZRAM1/MIFICHERO (FTPLOG)
OVRDBF FILE(INPUT) TOFILE(FJCRUZRAM1/MIFICHERO) MBR(MIFTDATA)
OVRDBF FILE(OUTPUT) TOFILE(FJCRUZRAM1/MIFICHERO) MBR(FTPLOG)
STRTCPFTP RMTSYS('XXX.XXX.XXX.XXX')
CPYTOIMPF FROMFILE(FJCRUZRAM1/MIFICHERO FTPLOG) +
TOSTMF('/HOME/FJCRUZRAM/FTPLOG.TXT') +
RCDDLM(*CRLF) MBROPT(*REPLACE)
DLTOVR FILE(INPUT)
DLTOVR FILE(OUTPUT)
CALL PGM(READOKFTP1) PARM(&MYVAR)
SNDPGMMSG MSG('&MYVAR = ' || &MYVAR)
IF (&MYVAR *EQ 'OK') +
THEN(CALL MISOCK3 PARM('XXX.XXX.XXX.XXX' 'OK'))
ENDPGM
En el CL, definimos la variable de tipo caracter de lóngitud 2 posiciones y le asignamos el valor "NE".
Borramos con el comando CLRPFM el fichero que almacenara el log del FTP.
Sobreescribimos con OVRDBF el INPUT y el OUTPUT del cliente FTP con los miembros MIFTDATA y FTPLOG respectivamente. En MIFTPDATA almacenaramos los comandos FTP que lanzaremos a través del cliente FTP que inicializaremos con el comando STRTCPFTP, con el parametro RMTSYS pasaremos la IP del servidor FTP.
El formato del fichero MIFTPDATA será el siguiente:
0001.00 usuario contraseña 0002.00 put FJCRUZRAM1/MIFICHERO.FICTRA prueba.txt 0003.00 quit
Como vemos el miembro MIFTPDATA contendra los comandos FTP de login en el servidor, así como el comando "PUT" que subirá el archivo, y el comando "quit" que cerrara la conexión con el servidor.
Todo el Log de la conexión y la transferencia se dejará en el miembro FTPLOG. Una vez realizada la conexión y la posible transferencia copiaremos el miembro FTPLOG al sistema IFS del AS400, el sistema IFS (Integrated File System) lo que hace es evadir el actual sistema de Biblioteca/Objeto Miembro del Mainframe y usar un sistema basado en ficheros ASCII Stream semejante a los sistemas UNIX y PC actuales.
Para ello usaremos el comando CPYTOIMPF con el parametro MBROPT(*REPLACE) para hacer un borrado de los datos que ya puedan existir en el fichero FTPLOG.txt del IFS.
A continuación borramos la sobreescritura de los ficheros usados por el cliente FTP con el comando DLTOVR. Y llamamos al programa en RPG ILE: READOKFTP1 que leera el fichero del log FTP desde el IFS y detectara si la transferencia ha ido OK o no. Para ello pasaremos la variable &MYVAR como parametro, donde devolvera "OK" si la transferencia ha ido bien.
En caso de que devuelva "OK" llamará al programa en RPG ILE: MISOCK3 pasandole la IP del servidor Java que accede a la base de datos y el mensaje "OK" que será enviado mediante sockets TCP.
Leyendo el log del FTP del IFS con RPG IV
El programa READOKFTP1 que lee el fichero en el IFS del log de la transferencia del FTP es el siguiente:
H DFTACTGRP(*NO) BNDDIR('MYBNDDIR') ACTGRP(*NEW) D readline PR 10I 0 D fd 10I 0 value D text * value D maxlen 10I 0 value D open PR 10I 0 ExtProc('open') D path * value options(*string) D oflag 10I 0 value D mode 10U 0 value options(*nopass) D codepage 10U 0 value options(*nopass) D O_WRONLY C 2 D O_CREAT C 8 D O_TRUNC C 64 D O_RDONLY C 1 D RW C 6 D R C 4 D OWNER C 64 D GROUP C 8 D close PR 10I 0 ExtProc('close') D fildes 10I 0 value D fd S 10I 0 D data S 12A D Msg S 52A D rddata S 48A D len S 10I 0 D line S 52A D msgok S 21A D ret S 3I 0 D coderet S 2A c *entry plist c parm coderet c eval *inlr = *on /free //--> Apertura del fichero IFS //****************************** eval ret = 0; eval fd = open('/home/FJCRUZRAM/ftplog.txt': O_RDONLY); eval coderet = 'KO'; if fd < 0; Msg = 'open failed'; eval *inlr = *on; return; endif; eval msgok = '226 Transfer complete'; dow readline(fd:%addr(line): %size(line))>0; eval Msg = line; if %check(%subst(Msg:1:21) :msgok) = 0; eval coderet = 'OK'; ret = 1; endif; enddo; callp close(fd); //-->FIN *inlr = *on; /end-free
Los procedimientos de apertura, cierre, lectura y escritura de ficheros están sacados de funciones en C, que, a mi modo de entender, el sistema AS400 incorpora dichas funciones que podemos exportar a RPG como parte del conjunto de funcionas de UNIX.
Mencionar que en RPG, en la página H se codifican las instrucciones de cabecera (header), en la página D, las instrucciones de datos (data) relacionadas con las variables que usara el programa, y en la página C (Code) irá el código, que en este caso usaremos RPG free en su mayoría, para que nos sea más simple de codificar.
Llamamos al procedimiento "open" con dos parametros, el primero es la ruta donde se encuentra el fichero de log del FTP y el segundo parametro es un flag a modo de constante númerica que índica que abriremos el fichero en solo lectura, estas constantes las sacamos de los procedimientos en C para trabajo de ficheros en sistemas UNIX. Algunas de ellas quedan definidas en nuestro código como recodatorio para una posible reutilización. Como vemos en RPG, el paso de parametros a los procedimientos se separa por el simbolo : en vez de , de las propías funciones de C.
Damos el valor 'KO' mediante la palabra reservada "eval" a la variable coderet.
A continuación, comprobamos que el fichero se ha abierto correctamente, si el descriptor de fichero devuelto por open es mayor que 1, recordar que el descriptor de fichero es un valor entero positivo que índica al sistema operativo donde aloca el fichero en memoría una vez abierto, si el valor es menor que 0, el fichero no se ha aperturado correctamente. En cuyo caso devolveremos la variable coderet como 'KO' y guardaremos el mensaje de error en la apertura en una variable para posibles usos posterires, y finalizaremos el programa mediante "*inlr = *on;", activar el índicador LR, que índica que se leyo el último registro y que se puede salir del programa y cerrar todos los ficheros abiertos durante la ejecución del mismo, recordemos que RPG es un lenguaje para tratamientos de ficheros en sistemas AS400, por ello la salida del programa se hace mediante esta forma.
Asígnamos a la variable msgok el string que debe buscar en el log de la transferencia FTP, que es el mensaje que índica que la transferencia ha ido OK.
A continuación viene la "chicha" del asunto, y es un bucle do while que realizara llamadas consecutivas al procedimiento externo readline(), este procedimiento lo habremos definido en un programa de servicio RPG ILE y lo que hará será leer linea a linea al fichero en busca del string que índica el OK de la transferencia, esta función externa recibe 3 parametros, el primero es el descriptor de fichero devuelto por open, el segundo es un puntero a la dirección de memoría de la variable "line", esto lo hacemos mediante la función interna de RPG "%addr", el tercer parametro será el tamaño de la variable "line", lo pasamos mediante la función interna de RPG "%size", la función readline devolverá el número de caracteres por linea leidos, por lo que el bucle se repetira hasta que el número de caracteres leídos sea 0, encontrandonos así con el final del fichero.
El condicional dentro del bucle, lo que hará será comparar dos string, uno los 21 primero caracteres de la linea leida y el segundo, el mensaje que índica el OK de la transferencia, mediante la función interna "%subst", si dicha función devuelve 0, es que la cadena coincide, ya que esta función lo que índica es el número de caracteres no coincidentes entre ambas cadenas. De ser así, daremos el valor 'OK' a la variable coderet que será la devuelta por el parametro
Para finalizar, cerramos el fichero, pasandole al procedimiento externo close() el descritor de fichero, y saldremos del programa. El valor 'OK' será devuelto en caso de ser la transferencia correcta, al CL que invoco el programa.
El altoritmo de lectura de lineas en un fichero de texto plano ASCIIEl siguiente algoritmo lo he sacado de scottklement, que resuelve de la siguiente manera:
En la pagina H codificamos la palabra reservada "nomain" qué indicara que es un programa de servicio, hablaremos de los programas de servicio en RPG ILE más adelante, tras desmenuzar el siguiente algoritmo.
En la página P codificamos el nombre del procedimiento que vamos a exportar, con la opción "export", en este caso, será "readline" (leer linea).
Este procedimiento recibira un descriptor de fichero, un puntero a la dirección de memoria que contendrá el string y un entero que índicara la máxima lóngitud a leer.
En el siguiente código:
D p_retstr S * D RetStr S 32766A based(p_retstr) D len S 10I 0
Declaramos un puntero p_retstr, y una variable alfanúmerica (A) de lóngitud 32766A y hacemos que el puntero p_retstr apunte a dicha variable, con la instrucción reservada "based()".
H nomain P readline B export D readline PI 10I 0 D fd 10I 0 value D text * value D maxlen 10I 0 value D read PR 10I 0 ExtProc('read') D fildes 10I 0 value D buf * value D nbyte 10U 0 value D rdbuf S 1024A static D rdpos S 10I 0 static D rdlen S 10I 0 static D p_retstr S * D RetStr S 32766A based(p_retstr) D len S 10I 0 /free //* Leer de un fichero en AS400 eval len = 0; eval p_retstr = text; eval %subst(RetStr:1:MaxLen) = *blanks; dow 1=1; //Cargamos en el buffer if rdpos>=rdlen; eval rdpos = 0; eval rdlen=read(fd:%addr(rdbuf):%size(rdbuf)); if rdlen < 1; return -1; endif; endif; //Comprobamos si es el fin de linea eval rdpos = rdpos+1; if %subst(rdbuf:rdpos:1) = x'25'; return len; endif; //si no finlinea agregar a la cadena de texto if %subst(rdbuf:rdpos:1) <> x'0d' and len<>maxlen; eval len = len+1; eval %subst(retstr:len:1) = %subst(rdbuf:rdpos:1); endif; enddo; return len; //fin *inlr = *on; /end-free P EPonemos la lóngitud a 0, y hacemos que el puntero p_retstr apunte al puntero "text" recibido por parametro, por lo tanto tendremos que la variable text apuntará a la dirección de memoria de la variable RetStr, y todo lo que caiga en esa variable será apuntado por "text" y por "p_retstr". Por lo tanto todo lo que trabajemos con la variable "retstr" quedará reflejado por ambos punteros, de esta forma accedemos a los datos, trabajando con las direcciones de memoría.
A continuación realizamos un bucle do while, que lo recorremos siempre que se evalue la condición como TRUE e iremos saliendo de el con los return.
El procedimiento read() recibe 3 parametros, el primero es el descriptor de fichero abierto con open(), el segundo es un puntero a la dirección de memoría del buffer o de la variable rdbuff donde vamos a colocar lo leido, el tercer parametro es el tamaño de dicha variable, la función read, tratara de leer esa cantidad de bytes y lo almacenara en "redbuf".
Comprobamos si es el final de la linea comparando con el caracter EBCDIC x'25' que indica un LF en ASCII (Fin de linea), si es el fin de linea retornara la longitud de caracteres leidos y salimos del bucle. En caso de que no sea el fin de la linea, agregamos el caracter al string de la linea, tocando la variable retstr.
Avanzamos la posición de len en 1 porque indicara los caracteres agregados a la linea, y la variable rdpos la avanzamos antes para saber si el siguiente caracter índica el fin de linea.
Los programas de servicio en RPG ILE contiene procedimientos externos que pueden ser llamados desde otros programas RPG.
Como en este ejemplo, el procedimiento readline() esta alocado en un programa de servicio y puede ser invocado desde otro programa externo a este.
Para crear programas de servicio realizaremos las siguientes acciones:1º) Crearemos un fichero físico donde alocar los archivos BND, estos archivos les dirá al CRTSRVPGM que procedimientos pueden vincularse a nosotros:
CRTSRCPF FJCRUZRAM1/QSRVSRC TEXT('Bind Language ... ')
2º) El archivo BND se llamará SRVFTE y contendrá el siguiente código:
STRPGMEXP PGMLVL(*CURRENT) LVLCHK(*YES) SIGNATURE('READLINE') EXPORT SYMBOL(readline) ENDPGMEXP
Donde EXPORT SYMBOL('nombre del procedimiento en el fuente RPG de servicio'). Por cada procedimiento externo que tengamos, colocaremos una linea de EXPORT y por cada programa de servicio que tengamos, colocaremos un STRPGMEXP.
3º) Compilamos el programa de servicio con la siguiente opción:
CRTSRVPGM SRVPGM(FJCRUZRAM1/TESTSRV1) MODULE(FJCRUZRAM1/READLINE) EXPORT(*ALL) DETAIL(*BASIC)
A SRVPGM le pasaremos la libreria y el nombre del objeto del programa de servicio que queramos crear, a MODULE le pasaremos, la libreria y el nombre de nuestro fuente RPG que contendrá el programa de servicio, y en EXPORT, pondremos *ALL para que busque en todos los ficheros físicos el archivo BND.
4º) Con el siguiente comando CRTBNDDIR crearemos el directorio de enlace (de BINDEO):
CRTBNDDIR FJCRUZRAM1/MYBNDDIR TEXT('bind programas rpg')
Donde le pasamos LIBRERIA/Nombre de carpeta a crear y en TEXT una descripción breve a la carpeta de bindeo.
5º) Agregamos una entrada al directorio de enlace con el comando ADDBNDDIRE donde al parametro BNDDIR le pasamos el directorio de Bindeo antes creado y al parametro OBJ le pasamos el objeto del fuente que hemos compilado como programa de servicio con la opción *SRVPGM como segundo parametro.
IMPORTANTE: Como último paso deberemos añadir la cabecera correspondiente a cada programa RGP que invocara a los procedimientos de los programas de servicios ILE, esta cabecera será la siguiente:
H DFTACTGRP(*NO) BNDDIR('MYBNDDIR') ACTGRP(*NEW)
Contacto
Para más información, proponer o trabajar en algún proyecto conjunto:
Email: arxivelogic@proton.me