martes, 4 de marzo de 2008

Acceso a Web Services desde Flex

En el anterior post vimos cómo acceder a un archivo XML guardado en nuestro disco duro, y representar la información del mismo en una matriz de datos. Pero una de las operaciones más habituales, a la hora de trabajar con interfaces gráficas, será la de extraer datos remotamente, normalmente mediante un servicio web, y exponerlos en pantalla.

Para el ejemplo que se trata aquí, se ha utilizado un Web Service realizado mediante Java, corriendo bajo Apache Tomcat 6.0, y desarrollado con Eclipse Europa, a través de AXIS 2.

El código Java es el siguiente:


package com.homatekno.ws;

import java.lang.StringBuffer;

public class CategoriesList {
public String getCategoriesList() {
StringBuffer sb = new StringBuffer();

sb.append("<?xml version='1.0' encoding='ISO-8859-1'?>\n");
sb.append("<category-list>\n");

sb.append("<category>\n");
sb.append("<id>01</id>\n");
sb.append("<name>Flores de interior</name>\n");
sb.append("<title>Flor interior</title>\n");
sb.append("<image></image>\n");
sb.append("</category>\n");

sb.append("<category>\n");
sb.append("<id>02</id>\n");
sb.append("<name>Flores de exterior</name>\n");
sb.append("<title>Flor exterior</title>\n");
sb.append("<image></image>\n");
sb.append("</category>\n");

sb.append("<category>\n");
sb.append("<id>03</id>\n");
sb.append("<name>Arbustos de interior</name>\n");
sb.append("<title>Arb. interior</title>\n");
sb.append("<image></image>\n");
sb.append("</category>\n");

sb.append("<category>\n");
sb.append("<id>04</id>\n");
sb.append("<name>Arbustos de exterior</name>\n");
sb.append("<title>Arb. exterior</title>\n");
sb.append("<image></image>\n");
sb.append("</category>\n");

sb.append("<category>\n");
sb.append("<id>05</id>\n");
sb.append("<name>Aromaticas</name>\n");
sb.append("<title>Aromaticas</title>\n");
sb.append("<image></image>\n");
sb.append("</category>\n");

sb.append("<category>\n");
sb.append("<id>06</id>\n");
sb.append("<name>Arboles de hoja caduca</name>\n");
sb.append("<title>Arbol caduca</title>\n");
sb.append("<image></image>\n");
sb.append("</category>\n");

sb.append("<category>\n");
sb.append("<id>07</id>\n");
sb.append("<name>Arboles de hoja perenne</name>\n");
sb.append("<title>Arbol perenne</title>\n");
sb.append("<image></image>\n");
sb.append("</category>\n");

sb.append("<category>\n");
sb.append("<id>08</id>\n");
sb.append("<name>Cactus</name>\n");
sb.append("<title>Cactus</title>\n");
sb.append("<image></image>\n");
sb.append("</category>\n");

sb.append("</category-list>");

return sb.toString();
}
}


Lo habitual sería acceder a base de datos, en lugar de hacerlo "a pelo".

Esta clase se convertirá, a través de los asistentes del IDE que se utilice (Eclipse, NetBeans, JBuilder, IntelliJ, etc.) en un servicio Web. También, mediante el IDE, se levantará el servicio a través del servidor de aplicaciones (Tomcat, JBoss, Weblogic, etc.) configurado.

Una vez creado y desplegado el Web Service, tendremos un archivo WSDL con la descripción del servicio Web. En nuestro caso, estará localizado en


http://localhost:8080/Demo01/wsdl/CategoriesList.wsdl


y su contenido será el siguiente:


<wsdl:definitions
targetNamespace="http://ws.homatekno.com">
<wsdl:types>

<schema elementFormDefault="qualified"
targetNamespace="http://ws.homatekno.com">

<element name="getCategoriesList">
<complexType/>
</element>

<element name="getCategoriesListResponse">

<complexType>

<sequence>
<element name="getCategoriesListReturn"
type="xsd:string"/>
</sequence>
</complexType>
</element>
</schema>
</wsdl:types>

<wsdl:message
name="getCategoriesListResponse">
<wsdl:part element="impl:getCategoriesListResponse"
name="parameters"/>
</wsdl:message>

<wsdl:message name="getCategoriesListRequest">
<wsdl:part element="impl:getCategoriesList" name="parameters"/>
</wsdl:message>

<wsdl:portType name="CategoriesList">

<wsdl:operation name="getCategoriesList">
<wsdl:input message="impl:getCategoriesListRequest"
name="getCategoriesListRequest"/>
<wsdl:output message="impl:getCategoriesListResponse"
name="getCategoriesListResponse"/>
</wsdl:operation>
</wsdl:portType>

<wsdl:binding name="CategoriesListSoapBinding"
type="impl:CategoriesList">
<wsdlsoap:binding style="document"
transport="http://schemas.xmlsoap.org/soap/http"/>

<wsdl:operation name="getCategoriesList">
<wsdlsoap:operation soapAction=""/>

<wsdl:input name="getCategoriesListRequest">
<wsdlsoap:body use="literal"/>
</wsdl:input>

<wsdl:output name="getCategoriesListResponse">
<wsdlsoap:body use="literal"/>
</wsdl:output>
</wsdl:operation>
</wsdl:binding>

<wsdl:service name="CategoriesListService">

<wsdl:port binding="impl:CategoriesListSoapBinding"
name="CategoriesList">
<wsdlsoap:address
location="http://localhost:8080/Demo01/services/CategoriesList"/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>


Para probar el Web Service, directamente, se lanzaría la siguiente URL:


http://localhost:8080/Demo01/services/CategoriesList?method=
getCategoriesList


Siendo el resultado lo siguiente:


<soapenv:Envelope>
<soapenv:Body>
<getCategoriesListResponse>
<ns1:getCategoriesListReturn>
<?xml version='1.0' encoding='ISO-8859-1'?>
<category-list>
<category>
<id>01</id>
<name>Flores de interior</name>
<title>Flor interior</title>
<image></image>
...
</category>
</category-list>
</ns1:getCategoriesListReturn>
</getCategoriesListResponse>
</soapenv:Body>
</soapenv:Envelope>


Una vez comprobado que el Web Service está operativo y funcionando sin problemas, nos pondremos manos a la obra con el front-end de Flex.

Para invocar a un Web Service, se hará uso del objeto "mx:WebService":


<mx:WebService id="WSCategories"
wsdl="http://localhost:8080/Demo01/wsdl/CategoriesList.wsdl">
<mx:operation name="getCategoriesList"
result="getData(event)"
fault="getError(event)"/>
</mx:WebService>


En el parámetro "wsdl" se especifica la URL del archivo WSDL del Web Service a ejecutar.

El objeto "mx:operation" especifica el método a ejecutar del Web Service. En nuestro caso se ejecutará el método único "getCategoriesList", que no contiene parámetros". Este objeto posee otros parámetros, como "result" en el que se especifica el método (getData(event)) o acción a ejecutar cuando el Web Service se haya ejecutado sin problemas, y se trata el resultado. El parámetro "fault" indica el método (getError(event)) o acción en el caso de que el Web Service se haya ejecutado con errores.

El objeto "mx:WebService" define o prepara la ejecución del Web Service, pero no lo ejecuta. Para ello, nuestra interfaz contendrá un botón con el texto "Ejecutar WS", el cual, al hacer clic sobre él, lanzará invocará a un método, el cual lanzará el Web Service. El código del botón (objeto "mx:Button") es el siguiente:


<mx:Button x="10" y="10" label="Ejecutar WS"
id="btnRun" click="runWS()"/>


Los parámetros "x" e "y" definen la posición en el área de trabajo. El parámetro "label" contiene el texto a visualizar. El parámetro "click" define el método (runWS()) o acción a ejecutar cuando el usuario haga clic sobre el botón.

Para definir los métodos (getData(), getError() y runWS()), usaremos un bloque de código ActionScript dentro del bloque "mx:Application", en lugar de MXML. Este bloque contendrá el siguiente código:


<mx:Script>
<![CDATA[
import mx.collections.XMLListCollection;
import mx.rpc.events.ResultEvent;
import mx.rpc.events.FaultEvent;

[Bindable]
private var myXML:XML;

private function getData(event:ResultEvent):void{
myXML = new XML(event.result);
miTexto.text=myXML.toString();
}

private function getError(event:FaultEvent):void
{
miTexto.text=event.fault.message;
}

private function runWS():void {
WSCategories.getCategoriesList.send();
}
]]>
</mx:Script>


El bloque de código ActionScript queda englobado entre "mx:Script" + "[CDATA[".

Lo primero que se hace es importar los objetos a utilizar, a través de la sentencia "import".

El tag "[Bindable]" define los elementos que se enlazan o linkan. En nuestro caso se define una variable de tipo "XML", que gestionará el XML que se recuperará del Web Service.

El método "getData()", inicializa la variable XML, creando un nuevo objeto de tipo XML, y recogiendo el resultado retornado por el Web Service, el cual se encuentra en el parámetro "event" del método, y que es enviado desde el objeto "mx:WebService". Una vez recogido, se mostrará en el área de texto "miTexto" el contenido del XML (el área de texto se aborda más adelante).

El método "getError()" pone el mensaje de error devuelto por el Web Service en el área de texto "miTexto".

El método "runWS()" es el método que realmente ejecuta el Web Service, y que lo inicia el clic que hace el usuario sobre el botón. El resultado de este código lanzará los métodos "getData()" y "getError()" en caso de éxito o error. La única línea de este método hace que el objeto "WSCategories" (de tipo "mx:WebService") invoque al método "getCategoriesList" mediante la acción "send()".

El área de texto "miTexto" recogerá el resultado del WebService. El siguiente código define este objeto, de tipo "mx:TextArea":


<mx:TextArea x="10" y="203"
width="441" height="190"
id="miTexto" editable="false"
wordWrap="true"
cornerRadius="16"
fontFamily="Verdana" fontSize="8"
fontWeight="normal"
borderStyle="solid"
backgroundColor="#E6E1E1"
backgroundAlpha="0.5"/>


Se han agregado atributos especiales para dar efectos al componente. Así, el atributo "editable" permite indicar se se puede editar datos en el área de texto. El atributo "wordWrap" indica si el texto se reparte automáticamente cuando llena la línea, evitando el scrolling horizontal. El atributo "cornerRadius" indica cuánto se redondean los bordes del área de texto. Los atributos "fontFamily", "fontSize" y "fontWeight" definen el tipo de letra, el tamaño y el aspecto de ésta. El atributo "borderStyle" define el estilo del borde. El atributo "backgroundColor" define el color de fondo del área de texto. Por último, el atributo "backgroundAlpha" define el nivel de transparencia del componente (0.5 es un 50%).

Ya se ha visto cómo el resultado de la ejecución del Web Service se almacena en un objeto de tipo "XML". Este objeto contiene todo el XML, contando también con el nodo superior "<category-list>". Por ello, es necesario recoger la lista de datos, o los registros (por decirlo con algún símil) que necesitaremos representar en la tabla. Esta colección de datos son iguales, pues tienen X registros con el mismo número de campos. Para ello hay que buscar dentro de la jerarquía de nodos del XML, y bajar al nivel de nodo de esta lista. Para ello, se utilizará el objeto "mx:XMLListCollection", el cual será utilizado por la tabla para cargar los datos. Se realizará de la siguiente manera:


<mx:XMLListCollection id="myXMLList"
source="{myXML.category}"/>


Por último, se añade la matriz de datos o "DataGrid", cuyos atributos se explicaron en el anterior post (Un primer contacto con Flex). El código de éste es el siguiente:


<mx:DataGrid x="10" y="44"
width="441"
id="gridCategories"
dataProvider="{myXMLList}"
borderStyle="outset"
fontSize="8">
<mx:columns>
<mx:DataGridColumn id="ID"
dataField="id" headerText="ID"
width="15"/>
<mx:DataGridColumn id="Name"
dataField="name" headerText="Name"
width="50"/>
<mx:DataGridColumn id="Title"
dataField="title" headerText="Title"
width="50"/>
<mx:DataGridColumn id="Image"
dataField="image" headerText="Image"
width="50"/>
</mx:columns>
</mx:DataGrid>


Nótese que el atributo "dataProvider" del "DataGrid" apunta a la colección extraída del objeto XML.

El aspecto final de la aplicación será el siguiente:



El código completo de la aplicación Flex:


<?xml version="1.0" encoding="utf-8"?>
<mx:Application
xmlns:mx="http://www.adobe.com/2006/mxml"
layout="absolute">

<mx:Script>
<![CDATA[
import mx.collections.XMLListCollection;
import mx.rpc.events.ResultEvent;
import mx.rpc.events.FaultEvent;

[Bindable]
private var myXML:XML;

private function getData(event:ResultEvent):void {
myXML = new XML(event.result);
miTexto.text=myXML.toString();
}

private function getError(event:FaultEvent):void {
miTexto.text=event.fault.message;
}

private function runWS():void {
WSCategories.getCategoriesList.send();
}
]]>
</mx:Script>

<mx:XMLListCollection id="myXMLList"
source="{myXML.category}"/>


<mx:WebService id="WSCategories"
wsdl="http://localhost:8080/Demo01/wsdl/CategoriesList.wsdl">
<mx:operation name="getCategoriesList"
result="getData(event)"
fault="getError(event)"/>
</mx:WebService>

<mx:DataGrid x="10" y="44"
width="441"
id="gridCategories"
dataProvider="{myXMLList}"
borderStyle="outset"
fontSize="8">
<mx:columns>
<mx:DataGridColumn id="ID"
dataField="id" headerText="ID"
width="15"/>
<mx:DataGridColumn id="Name"
dataField="name" headerText="Name"
width="50"/>
<mx:DataGridColumn id="Title"
dataField="title" headerText="Title"
width="50"/>
<mx:DataGridColumn id="Image"
dataField="image" headerText="Image"
width="50"/>
</mx:columns>
</mx:DataGrid>
<mx:Button x="10" y="10"
label="Ejecutar WS" id="btnRun"
click="runWS()"/>
<mx:TextArea x="10" y="203"
width="441" height="190"
id="miTexto" editable="false"
wordWrap="true" cornerRadius="16"
fontFamily="Verdana" fontSize="8"
fontWeight="normal"
borderStyle="solid"
backgroundColor="#E6E1E1"
backgroundAlpha="0.5"/>

</mx:Application>