When the solution matters*

| Clientes | Historias de éxito | Buscador de soluciones | Perfiles |

Compañía General de Esencias

Servicios Web Avanzados
Por Tito Ciuro

En mi artículo anterior explicaba a nível básico como empezar a
trabajar
con los Servicios Web. En este artículo nos adentraremos de lleno en el terreno avanzado. Veremos cuatro ejemplos que nos permitirán enviar y recibir estructuras complejas:

Para terminar, la sección “Obteniendo valores de un Bag” mostrará como acceder a los valores asociados con una etiqueta

 

 

Agradecimientos

Este artículo, al igual que los programas que he escrito en 4D, se basan en una tecnología llamada GenRoTools.

Sin la ayuda de Bags, la implementación de soluciones via Servicios Web pueden ser realmente complicadas, por lo que quiero agradecer a Jeff Edwards y a Giovanni Porcari por haber producido esta herramienta de gran calidad. La licencia es gratuita, tanto a nivel de dearrollo como de instalación. La página de descarga se hará pública proximamente, pero mientras tanto, Jeff y Giovanni me han permitido distribuir GenRoTools junto con los ejemplos incluídos en este artículo.

 

Prerequisitos

Para poderle sacar partido al artículo, el lector debería leer mi artículo anterior.

 

Primer ejemplo: Servicio Web que facilita una lista de estados en E.E.U.U. en los que se encuentran los doctores

La intención de este ejemplo es la de crear un Servicio Web que devuelva un Bag con los estados americanos codificados de la siguiente manera:

 

El método del servidor que genera esta lista es “WS_ListaDeEstados”:

 

 

 

  `Declaración del tipo de variable que devolveremos al cliente

SOAP DECLARATION(SOAPBLOB;Is BLOB ;SOAP Output ;"SOAPBLOB")

 

  `Variable temporal utilizada para recoger ls lista de estados

ARRAY STRING(2;EstadosDeEEUU;0)

 

DEFAULT TABLE([Doctores])

ALL RECORDS

DISTINCT VALUES([Doctores]Estado;EstadosDeEEUU)

 

C_LONGINT($i;$Records)

$Records:=Size of array(EstadosDeEEUU)

 

For ($i;1;$Records)

    GNT_Bags ("SetItem";->SOAPBLOB;"Estado."+EstadosDeEEUU{$i};->EstadosDeEEUU{$i})

End for

 

La línea de código más importante es:

GNT_Bags ("SetItem";->SOAPBLOB;"Estado."+EstadosDeEEUU{$i};

->EstadosDeEEUU{$i})

 

Añadimos un elemento al Bag mediante el primer parámetro, “SetItem”.

Nota: GenRoTools reemplazará la etiqueta y su valor correspondiente por la especificada mediante “SetItem”. Dicho de otra manera, si se desea añadir al Bag todos lo valores seleccionados, habrá que asegurarse de que la etiqueta es única.

El método del cliente que obtiene el Bag con los Estados es “Probar_ListaDeEstados”:

C_BLOB(vDebugBag)

vDebugBag:=WS_ListaDeEstados

 

C_STRING(75;vEtiquetaDeLista)

vEtiquetaDeLista:="Lista de Estados en E.E.U.U. disponibles:"

 

If (BLOB size(vDebugBag)#0)

    C_LONGINT($width;$height)

    GET FORM PROPERTIES([Table 1];"BagDebugList";$width;$height)

    

    OpenCenteredWindow ($width;$height;Plain window ;"Estados")

    DIALOG([Table 1];"BagDebugList")

    CLOSE WINDOW

Else

    ALERT("No se han podido obtener los Estados del servidor.")

End if

 

- El método WS_ListaDeEstados se ha creado automaticamente mediante el “Asistente Servicios Web” de 4D.

 

- El formulario “BagDebugList” contiene los siguientes elementos:

En el método de objeto “hBagDebugList” pondremos el siguiente método:

Case of

    : (Form event=On Load )

        C_LONGINT(hBagDebugList)

        GNT_Bags ("BuildList";->vDebugBag;"hBagDebugList")

        

        If (Is a list(hBagDebugList))

            REDRAW LIST(hBagDebugList)

        End if

        

    : (Form event=On Clicked )

        C_LONGINT($vlItemPos;$vlItemRef;$hSublist)

        C_STRING(255;$vsItemText)

        C_BOOLEAN($vbExpanded)

        

        $vlItemPos:=Selected list item(hBagDebugList)

        

        If ($vlItemPos>0)

            C_STRING(2;vEstado)

            

            GET LIST ITEM(hBagDebugList;$vlItemPos;$vlItemRef;$vsItemText;$hSublist;

$vbExpanded)

            If (Is a list($hSublist))

                DISABLE BUTTON(vOK)

            Else

                vEstado:=$vsItemText

                ENABLE BUTTON(vOK)

            End if

        End if

        

    : (Form event=On Unload )

        If (Is a list(hBagDebugList))

            CLEAR LIST(hBagDebugList;*)

        End if

End case

 

GenRoTools nos ofrece un método muy útil que convierte un Bag en una lista jerárquica:

GNT_Bags ("BuildList";->vDebugBag;"hBagDebugList")

 

Si todo sale correctamente, veremos el resultado siguiente:

 

Segundo ejemplo: Servicio Web que facilita una lista de doctores localizados en una zona

Basándonos en el primer ejemplo, solicitaremos los doctores pertenecientes a un Estado. La ventaja de utilizar la lista generada por el servidor en el primer ejemplo es que el usuario no ha de adivinar qué Estados están disponibles. Esto facilita el trabajo al usuario y mejora su forma de trabajar. El método es el siguiente:

C_BLOB(vDebugBag)

vDebugBag:=WS_ListaDeEstados

 

C_STRING(75;vEtiquetaDeLista)

vEtiquetaDeLista:="Lista de Estados en E.E.U.U. disponibles:"

 

If (BLOB size(vDebugBag)#0)

    C_LONGINT($width;$height)

    GET FORM PROPERTIES([Table 1];"BagDebugList";$width;$height)

 

    OpenCenteredWindow ($width;$height;Plain window ;"Estados")

    DIALOG([Table 1];"BagDebugList")

    CLOSE WINDOW

 

    If (OK=1)

        vDebugBag:=WS_ObtenerDoctores (vEstado)

        

        C_STRING(75;vEtiquetaDeLista)

        vEtiquetaDeLista:="Doctores en el estado de: "+vEstado

 

        If (BLOB size(vDebugBag)#0)

            C_LONGINT($width;$height)

            GET FORM PROPERTIES([Table 1];"BagDebugList";$width;$height)

            

            OpenCenteredWindow ($width;$height;Plain window ;"Doctores")

            DIALOG([Table 1];"BagDebugList")

            CLOSE WINDOW

        Else

            ALERT("No se han podido obtener los doctores del estado “"+vEstado+"”.")

        End if

    End if

Else

    ALERT("No se han podido obtener los Estados del servidor.")

End if

 

- En primera instancia, obtenemos la lista de Estados disponibles. El método del objeto BagDebugList detecta el click en el valor (no la etiqueta), lo cual nos permite guardar el valor en la variable vEstado.

- Si cerramos la ventana mediante un click en OK, procedemos a obtener los doctores, pasando el valor de vEstado como parámetro. Al igual que en ejemplo 1, utilizamos el Asistente Servicios Web para generar el método WS_ObtenerDoctores. Éste llama al método con el mismo nombre en el servidor, que contiene lo siguiente:

 

COMPILER_WEB

 

SOAP DECLARATION(SOAPString5;Is String Var ;SOAP Input ;"SOAPString5")

SOAP DECLARATION(SOAPBLOB;Is BLOB ;SOAP Output ;"SOAPBLOB")

 

DEFAULT TABLE([Doctores])

QUERY([Doctores]Estado=SOAPString5)

 

C_LONGINT($i;$Records)

$Records:=Records in selection

 

If ($Records>0)

    FIRST RECORD

    For ($i;1;$Records)

        GNT_Bags ("SetItem";->SOAPBLOB;"Doctores."+String([Doctores]ID)+".ID";

->[Doctores]ID)

        GNT_Bags ("SetItem";->SOAPBLOB;"Doctores."+String([Doctores]ID)+".Nombre";

->[Doctores]Nombre)

        GNT_Bags ("SetItem";->SOAPBLOB;"Doctores."+String([Doctores]ID)+".Apellido";

->[Doctores]Apellido)

        GNT_Bags "SetItem";->SOAPBLOB;"Doctores."+String([Doctores]ID)

+".Especialidad";->[Doctores]Especialidad)

        GNT_Bags ("SetItem";->SOAPBLOB;"Doctores."+String([Doctores]ID)+".Direccion";

->[Doctores]Direccion)

        GNT_Bags ("SetItem";->SOAPBLOB;"Doctores."+String([Doctores]ID)+".Ciudad";

->[Doctores]Ciudad)

        GNT_Bags ("SetItem";->SOAPBLOB;"Doctores."+String([Doctores]ID)+".Estado";

->[Doctores]Estado)

        NEXT RECORD

    End for

End if

- El método COMPILER_WEB contiene las siguientes declaraciones:

 

ARRAY LONGINT(aSOAPLongint;0)

ARRAY TEXT(aSOAPText;0)

 

C_LONGINT(SOAPLongint)

C_STRING(5;SOAPString5)

C_BLOB(SOAPBLOB)

 

- Seleccionamos los doctores pertenecientes al Estado pasado en la variable SOAPString5 (el valor de la variable vEstado enviada por el cliente) y añadimos los valores de cada cliente al Bag.

Nota: es importante destacar que la etiqueta es común por cada grupo de valores, mientras que la etiqueta de cada valor es única, lo que permite agruparlas en un mismo nodo. Por ejemplo:

Doctores. 5000063.ID --> 5000063

Doctores. 5000063.Nombre --> Kevin

Doctores. 5000063.Apellido --> Nguyen

Doctores. 5000063.Especialidad --> Podiatry

Doctores. 5000063.Dirección --> 28785 Via Pasa Tiempo

Doctores. 5000063.Ciudad --> Laguna Niguel

Doctores. 5000063.Estado --> CA

Resulta en la siguiente estructura jerárquica:

- En cada iteración, el código del doctor cambia, lo cual genera otro grupo de valores, y así sucesivamente.

 

Tercer ejemplo: Servicio Web que facilita una lista de doctores localizados en una zona (versión optimizada)

El método “WS_ObtenerDoctores” del servidor realiza su función, pero genera mucho tráfico ya que utiliza NEXT RECORD por cada iteración. Si hay pocos clientes no es un problema, pero si la selección es mucho mayor, la pérdida de velocidad puede ser importante. La versión mejorada contiene los siguientes cambios:

COMPILER_WEB

 

SOAP DECLARATION(SOAPString5;Is String Var ;SOAP Input ;"SOAPString5")

SOAP DECLARATION(SOAPBLOB;Is BLOB ;SOAP Output ;"SOAPBLOB")

 

DEFAULT TABLE([Doctores])

QUERY([Doctores]Estado=SOAPString5)

 

ARRAY LONGINT($aDoctorID;0)

ARRAY STRING(75;$aDoctorNombre;0)

ARRAY STRING(75;$aDoctorApellido;0)

ARRAY STRING(75;$aDoctorEspecialidad;0)

ARRAY STRING(75;$aDoctorDireccion;0)

ARRAY STRING(75;$aDoctorCiudad;0)

ARRAY STRING(5;$aDoctorEstado;0)

 

SELECTION TO ARRAY([Doctores]ID;$aDoctorID;[Doctores]Nombre;$aDoctorNombre;

[Doctores]Apellido;$aDoctorApellido;[Doctores]Especialidad;$aDoctorEspecialidad;

[Doctores]Direccion;$aDoctorDireccion;[Doctores]Ciudad;$aDoctorCiudad;

[Doctores]Estado;$aDoctorEstado)

C_LONGINT($i;$Records)

$Records:=Records in selection

 

For ($i;1;$Records)

    GNT_Bags ("SetItem";->SOAPBLOB;"Doctores."+String($aDoctorID{$i})+".ID";

->$aDoctorID{$i})

    GNT_Bags ("SetItem";->SOAPBLOB;"Doctores."+String($aDoctorID{$i})+".Nombre";

->$aDoctorNombre{$i})

    GNT_Bags ("SetItem";->SOAPBLOB;"Doctores."+String($aDoctorID{$i})+".Apellido";

->$aDoctorApellido{$i})

    GNT_Bags ("SetItem";->SOAPBLOB;"Doctores."+String($aDoctorID{$i})

+".Especialidad";->$aDoctorEspecialidad{$i})

    GNT_Bags ("SetItem";->SOAPBLOB;"Doctores."+String($aDoctorID{$i})+".Direccion";

->$aDoctorDireccion{$i})

    GNT_Bags ("SetItem";->SOAPBLOB;"Doctores."+String($aDoctorID{$i})+".Ciudad";

->$aDoctorCiudad{$i})

    GNT_Bags ("SetItem";->SOAPBLOB;"Doctores."+String($aDoctorID{$i})+".Estado";

->$aDoctorEstado{$i})

End for

 

- La copia de valores a arrays está optimizado en 4D Server, lo cual permite generar un Bag mucho más rapidamente que mediante iteraciones via NEXT RECORD.

 

Cuarto ejemplo: Servicio Web que codifica información relacionada con un doctor

En este ejemplo recogeremos en un Bag toda la información relacionada con un doctor: datos personales y visitas realizadas. El método “Probar_InfoDeUnDoctor” en el cliente es el siguiente:

C_BLOB(vDebugBag)

vDebugBag:=WS_InfoDeUnDoctor (12226)

 

C_STRING(75;vEtiquetaDeLista)

vEtiquetaDeLista:="Información del doctor 12226:"

 

If (BLOB size(vDebugBag)#0)

    C_LONGINT($width;$height)

    GET FORM PROPERTIES([Table 1];"BagDebugList";$width;$height)

    

    OpenCenteredWindow ($width;$height;Plain window ;"Doctor 12226")

    DIALOG([Table 1];"BagDebugList")

    CLOSE WINDOW

Else

    ALERT("No se ha podido obtener información del doctor 12226.")

End if

 

- El código del doctor se pasa directamente como parámetro para simplificar el ejemplo. Idealmente, el usuario debería poder seleccionar un doctor de la lista y obtener toda la información. Dejo ese ejercicio para el lector.

El método “WS_InfoDeUnDoctor” del servidor contiene lo siguiente:

COMPILER_WEB

 

SOAP DECLARATION(SOAPLongint;Is LongInt ;SOAP Input ;"aSOAPLongint")

SOAP DECLARATION(SOAPBLOB;Is BLOB ;SOAP Output ;"SOAPBLOB")

 

DEFAULT TABLE([Doctores])

QUERY([Doctores]ID=SOAPLongint)

 

ARRAY LONGINT(aDoctorID;0)

ARRAY STRING(75;aDoctorNombre;0)

ARRAY STRING(75;aDoctorApellido;0)

ARRAY STRING(75;aDoctorEspecialidad;0)

ARRAY STRING(75;aDoctorDireccion;0)

ARRAY STRING(75;aDoctorCiudad;0)

ARRAY STRING(5;aDoctorEstado;0)

 

SELECTION TO ARRAY([Doctores]ID;aDoctorID;[Doctores]Nombre;aDoctorNombre;

[Doctores]Apellido;aDoctorApellido;[Doctores]Especialidad;aDoctorEspecialidad;

[Doctores]Direccion;aDoctorDireccion;[Doctores]Ciudad;aDoctorCiudad;

[Doctores]Estado;aDoctorEstado)

 

C_LONGINT($i;$Records)

$Records:=Records in selection

 

For ($i;1;$Records)

    GNT_Bags ("SetItem";->SOAPBLOB;"Doctor."+String(aDoctorID{$i})+".ID";

->aDoctorID{$i})

    GNT_Bags ("SetItem";->SOAPBLOB;"Doctor."+String(aDoctorID{$i})+".Nombre";

->aDoctorNombre{$i})

    GNT_Bags ("SetItem";->SOAPBLOB;"Doctor."+String(aDoctorID{$i})+".Apellido";

->aDoctorApellido{$i})

    GNT_Bags ("SetItem";->SOAPBLOB;"Doctor."+String(aDoctorID{$i})+".Especialidad";

->aDoctorEspecialidad{$i})

    GNT_Bags ("SetItem";->SOAPBLOB;"Doctor."+String(aDoctorID{$i})+".Direccion";

->aDoctorDireccion{$i})

    GNT_Bags ("SetItem";->SOAPBLOB;"Doctor."+String(aDoctorID{$i})+".Ciudad";

->aDoctorCiudad{$i})

    GNT_Bags ("SetItem";->SOAPBLOB;"Doctor."+String(aDoctorID{$i})+".Estado";

->aDoctorEstado{$i})

 

    ARRAY DATE(aVisitaFecha;0)

    ARRAY LONGINT(aVisitaHora;0)

    ARRAY TEXT(aVisitaProposito;0)

    ARRAY BOOLEAN(aVisitaPagado;0)

 

    DEFAULT TABLE([Visitas])

    QUERY([Visitas]ID=aDoctorID{$i})

    SELECTION TO ARRAY([Visitas]Fecha;aVisitaFecha;[Visitas]Hora;aVisitaHora;

[Visitas]Proposito;aVisitaProposito;[Visitas]Pagado;aVisitaPagado)

 

    C_LONGINT($j;$Visitas)

    $Visitas:=Records in selection

 

    C_BLOB(BagVisitas)

 

    For ($j;1;$Visitas)

        C_TIME(vTime)

        vTime:=aVisitaHora{$j}

        C_STRING(25;vTimeString)

        vTimeString:=String(vTime;HH MM SS )

 

        GNT_Bags ("SetItem";->BagVisitas;String($j)+".Fecha";->aVisitaFecha{$j})

        GNT_Bags ("SetItem";->BagVisitas;String($j)+".Hora";->vTimeString)

        GNT_Bags ("SetItem";->BagVisitas;String($j)+".Proposito";->aVisitaProposito{$j})

        GNT_Bags ("SetItem";->BagVisitas;String($j)+".Pagado";->aVisitaPagado{$j})

    End for

 

    GNT_Bags ("SetItem";->SOAPBLOB;"Doctor."+String(aDoctorID{$i})+".Visitas";

->BagVisitas)

End for

 

- Este método está basado en el tercer ejemplo.

- Cabe destacar la inclusión de las visitas en un Bag secundario (BagVisitas) que luego procedemos a añadir en el Bag que contiene el resto de la información perteneciente al doctor.

Si todo sale correctamente, veremos el resultado siguiente:

 

Obteniendo valores de un Bag

La utilidad de GenRoTools que nos permite convertir un Bag en una lista jerárquica es de fenomenal ayuda, ya que nos permite estudiar el Bag que recibimos del servidor. A la hora de depurar, esta opción nos puede ahorrar muchísimo tiempo.

Ahora bien, una vez tenemos los datos que queremos codificados en un Bag, ¿cómo podemos extraerlos? Mediante el selector “GetItem”:

GNT_Bags ("GetItem";Bag;Etiqueta;VariableDeDestino)

 

Bag -> Puntero al Bag

Etiqueta -> Referencia de la que queremos obtener el valor

VariableDeDestino <- Valor asociado a la etiqueta

 

Centrémonos en el Bag devuelto por el servidor en el cuarto ejemplo.

Para extraer el valor de “Nombre”:

C_TEXT(vNombre)

GNT_Bags ("GetItem";->vDebugBag;”Doctor.12226.Nombre”;->vNombre)

 

Para extraer el Bag “Visitas” que contiene las visitas:

C_BLOB(vVisitas)

GNT_Bags ("GetItem";->vDebugBag;”Doctor.12226.Visitas”;->vVisitas)

Para obtener el número de visitas:

C_TEXT(vNumeroItems)

GNT_Bags ("CountItems";->vDebugBag;”Doctor.12226. Visitas”;->vNumeroItems)

 

Para obtener el número de valores pertenecientes al doctor:

C_TEXT(vNumeroItems)

GNT_Bags ("CountItems";->vDebugBag;”Doctor.12226”;->vNumeroItems)

 

GenRoTools automaticamente convierte el valor guardado en el Bag al tipo de la variable de destino. Por ejemplo, el valor de la etiqueta “ID” es Longint, pero podemos convertirlo a String muy facilmente mediante:

C_TEXT(vStringID)

GNT_Bags ("GetItem";->vDebugBag;”Doctor.12226.ID”;->vStringID)

 

Conclusión

La utilización de Bags simplifica enormemente la codificación de datos para ser traspasados mediante Servicios Web. Además, el código es de fácil lectura y la transmisión es óptima ya que se envía todo de una vez. La extracción de los valores es simple y limpia, ya que los datos están organizados jerarquicamente. GenRoTools provee de una herramienta muy útil a la hora de depurar: la conversión de un Bag en una lista jerárquica permite analizar el contenido del Bag sin tener que escribir rutinas de extracción.

Me gustaría comentar un pequeño detalle: la conversión de un Bag con muchos registros a una lista jerárquica es lenta. Durante el desarrollo esto no es quizá muy importante, ya que al fin y al cabo se trata de un elemento de depuración. Sin embargo, si se desea implementar esta opción en la versión final que el usuario manipulará, se recomienda compilar el programa.

Un último comentario. La documentación distribuida actualmente no es muy buena, pero con un poco de paciencia se puede salir adelante. En mi caso, decidí escribir unos métodos propios de apoyo para no tener que memorizar el order de parámetros. Por ejemplo, el método “GetBagWithLabelFromBag”:

C_POINTER($1;$DestinationBagPtr;$3;$SourceBagPtr)

C_TEXT($2;$LabelToExtract)

 

$DestinationBagPtr:=$1

$LabelToExtract:=$2

$SourceBagPtr:=$3

 

  `Obtain the desired nested bag to the destination BLOB

GNT_Bags ("GetItem";$SourceBagPtr;$LabelToExtract;$DestinationBagPtr)

 

Este método permite obtener un Bag dentro de otro Bag. Lo utilizo de la siguiente manera:

C_BLOB(vVisitas)

GetBagWithLabelFromBag(->vVisitas;”Doctor.12226.Visitas”;->vDebugBag)

 

Una última nota acerca de la instalación: GenRoTools ha de instalarse mediante 4D Insider, pues se trata de un componente. Luego, en la estructura donde se desee utilizar ha de ponerse la siguiente línea en en método de la base de datos On Startup:

GNT_GenRoTools ("OnDatabaseStart")

Aunque he intentado cubrir las partes más significativas, es imposible cubrirlo todo. Espero que este artículo haya servido como ejemplo y permita al lector adentrarse en el genial mundo de los Bags. ¡Adelante y ánimo!

 

                                                                           Tito Ciuro
 
                                                                         Miami – Junio 2005

 

 



Internacional | Empresa | Contactar 4D | Mapa del Sitio | © 4D, S.A. 2008 | Tamaño de fuente: [A] [A] [A] *Cuando la solución es lo que importa