End of chapter 6
authorgumartinm <gustavo@gumartinm.name>
Mon, 19 Nov 2012 04:57:17 +0000 (05:57 +0100)
committergumartinm <gustavo@gumartinm.name>
Mon, 19 Nov 2012 04:57:17 +0000 (05:57 +0100)
PFC.tex
capitulo5/capitulo5.tex
capitulo6/capitulo6.tex
source/PHPWebServiceActions.php [new file with mode: 0644]
source/PHPWebServiceModel.php [new file with mode: 0644]
source/PHPWebServiceRouting.yml [new file with mode: 0644]
source/PHPWebServiceView.php [new file with mode: 0644]

diff --git a/PFC.tex b/PFC.tex
index 9daa063..df07f0b 100644 (file)
--- a/PFC.tex
+++ b/PFC.tex
 \include{capitulo4/capitulo4}
 \include{capitulo5/capitulo5}
 \include{capitulo6/capitulo6}
+\include{capitulo7/capitulo7}
 
 %============================================================
 %   Apéndices
index c4ba561..d92fed8 100644 (file)
@@ -245,7 +245,7 @@ El elemento \emph{binding} define cómo un cliente puede comunicar con el servic
 
 El elemento \emph{service} asocia una dirección para el servicio Web con un interfaz y binding específicos.
 
-\subsection{Modelo WSDL 2.0 utilizado}
+\subsection{Modelo WSDL 2.0 utilizado}\label{cap5:ModeloWSDL}
 
 \subsubsection{Definir el formato de mensajes para las operaciones}
 
index 6d64a78..a27bef1 100644 (file)
@@ -3,8 +3,7 @@
 Durante la fase de desarrollo se implementará y documentará la aplicación. Para la implementación de la aplicación Android se eligió el IDE Eclipse con el plugin Android para el desarrollo de aplicaciones para esta plataforma. En cambio, para la Web, se usó PHP como lenguaje de programación y como IDE vim\footnote{Para conocer más acerca del mejor editor de textos del mundo (¡y no provoca dolores en los dedos meñiques!) ver: \url{http://www.vim.org/}} con ctags. En realidad para Android (programación Java) también podría haberse usado vim pero el desarrollador deseaba experimentar con el popular y no tan poderoso Eclipse.
 
 No es objetivo de esta memoria explicar cada una de las líneas de código de las que se componen las aplicaciones implementadas, por tanto, en este capítulo lo que se mostrará será todos los pasos que con lleva la realización de una determinada acción que se ha considerado es representativa del funcionamiento global del sistema. Junto a esta acción se mostrarán y explicarán algunas partes del código que sirven para que el lector entienda qué elementos software ha habido que desarrollar para que dicha acción llegue a buen puerto.
-
-La documentación ha sido realizada durante la codificación de la aplicaciones usando Javadoc y phpDocumentor 2. Ambos se basan en la interpretación de los comentarios escritos en las clases y métodos para crear documentación en diversos formatos que pueden ser accedidos por usuarios que quieran conocer o tengan que usar o reutilizar el código implementado.
+a documentación ha sido realizada durante la codificación de la aplicaciones usando Javadoc y phpDocumentor 2. Ambos se basan en la interpretación de los comentarios escritos en las clases y métodos para crear documentación en diversos formatos que pueden ser accedidos por usuarios que quieran conocer o tengan que usar o reutilizar el código implementado.
 
 \section{Proceso de recepción de nuevos anuncios}
 
@@ -14,6 +13,8 @@ Se ha seleccionado la acción de recepción de anuncios ante variaciones en la l
 
 Desde el cliente y tras un cambio de posición geográfica de un usuario, el dispositivo móvil envía las coordenadas mediante una petición GET HTTP al servidor Web (al servicio Web RESTful) Primero la aplicación creará un nuevo hilo para que puedan ejecutarse varias peticiones HTTP en paralelo, tal y como se muestra en el Listado~\ref{list:JavaUpdateLocation}). Se recibe la nueva localización enviada por el sistema operativo Android cuando ésta varía y después se lanza una tarea mediante la instancia exec que es un objeto del tipo ExecutorService proporcionado por Java para poder lanzar y gestionar hilos de una forma sencilla. Básicamente mediante el método execute estamos diciendo a Android que construya un nuevo hilo donde las tareas batch o en segundo plano serán ejecutadas.
 
+La instancia local URLAuth contiene la dirección donde el servicio Web RESTful (ver sección \ref{cap5:ModeloWSDL}) está a la escucha, en este caso y tal y como dicta Symfony\footnote{Web Services Symfony 1.4: \url{http://symfony.com/legacy/doc/jobeet/1_4/en/15?orm=Doctrine}} es necesario especificar en la propia URL el tipo de dato que deseamos recibir (en este caso, JSON)
+
 
 \lstset{language=Java, basicstyle=\small, breaklines=true, float=[P], floatplacement={P}, frame=single, captionpos=b, caption={Código para la ejecución de tareas en paralelo}, label={list:JavaUpdateLocation}}
 \lstinputlisting{source/JavaUpdateLocation.java}
@@ -34,3 +35,40 @@ En el Listado~\ref{list:JavaHTTPJSONtreatment}) se observa también la existenci
 \lstinputlisting{source/JavaHTTPJSONtreatment.java}
 
 \subsection{Tratamiento de las peticiones en el servidor Web}
+
+La petición realizada por el cliente (un simple GET HTTP con la latitud, longitud y el tipo de contenido esperado en la propia URL) llega al servidor. El tratamiento de esta petición depende del framework, lenguaje, etc que estemos utilizando en la aplicación que actúa como servidor Web. En nuestro caso, se trata de una aplicación escrita en PHP y que usa el Framework Symfony, por tanto debemos ceñirnos (si queremos desarrollar un código mantenible y legible) al modo que Symfony nos permite implementar servicios Web RESTful.
+
+En el Listado~\ref{list:PHPWebServiceRouting}) se muestra la parte del archivo routing.yml utilizado en este proyecto para configurar en Symfony el servicio Web RESTful que recibe la posición actual del usuario. Explicado de forma sencilla el archivo routing.yml en Symfony permite configurar diferentes URLs en las que la aplicación Web escuchará y asociar estas URLs a clases PHP que se encuentran en nuestra aplicación y que tratarán la petición del usuario y devolverán (si es necesario) una respuesta. En este caso es un servicio Web RESTful lo que se está configurando, pero en la práctica podría ser cualquier cosa, por ejemplo, una simple página web escrita en HTML.
+
+\lstset{language=bash, basicstyle=\small, breaklines=true, float=[P], floatplacement={P}, frame=single, captionpos=b, caption={Servicio Web RESTful en Symfony: definición}, label={list:PHPWebServiceRouting}}
+\lstinputlisting{source/PHPWebServiceRouting.yml}
+
+\begin{itemize}
+    \item La entrada \emph{url} define el formato de la dirección que manejará el servicio web (su URL) indicando mediante el uso de el carácter ``:'' que ese campo es una variable cuyo valor no es conocido a priori y tiene que venir dado por la petición GET HTTP desde el cliente.
+    \item La entrada \emph{param} indica el método que se ejecutará cuando llega una petición GET HTTP a la URL definida anteriormente.
+    \item \emph{sf\_format} indica el formato de los datos devueltos por el servicio Web. En nuestro caso se permite XML, JSON o YAML pero finalmente solo ha sido implementado JSON.
+    \item Por último la entrada \emph{sf\_method} hace referencia al tipo de método HTTP que se permitirá para hacer uso de este servicio Web.
+\end{itemize}
+
+Posteriormente hay que atrapar la petición GET HTTP para tratarla y devolver un resultado, este proceso se muestra en el Listado~\ref{list:PHPWebServiceActions}) donde se ejecuta el método definido en el Listado~\ref{list:PHPWebServiceRouting}). RESTful permite usar cookies pero únicamente para indicar si un usuario está autenticado o no, para este servicio Web en concreto es necesario que el usuario haya hecho \emph{log in} previamente en el servidor Web. Tal tarea se lleva a cabo en la aplicación Android, si el \emph{log in} es satisfactorio guardará la cookie recibida desde el servidor Web (desde la aplicación PHP) y la adjuntará en la cabecera de posteriores peticiones GET HTTP (usando AndroidHttpClient tal y como fue mostrado en el Listado~\ref{list:JavaHTTPGet}) Como el usuario está autenticado podemos averiguar el id asociado a ese usuario dentro del sistema (en la base de datos de la aplicación Web); en caso de no estar autenticado este método fallará y no devolverá nada.
+
+\lstset{language=PHP, basicstyle=\small, breaklines=true, float=[P], floatplacement={P}, frame=single, captionpos=b, caption={Código para el tratamiento del servicio Web}, label={list:PHPWebServiceActions}}
+\lstinputlisting{source/PHPWebServiceActions.php}
+
+El método que lleva a cabo todo el peso de búsqueda en base de datos de anuncios localizados en las cercanías del usuario es getAdsByGPSAndUserIdAndLanguageId. Si este método no devuelve nada, significa que o bien no hay anuncios en esas coordenadas geográficas o el usuario no se ha subscrito a categorías relacionadas con anuncios localizados en ese área en el cual se encuentra actualmente. El método getParameters() devuelve un array con los parámetros de entrada suministrados en el GET HTTP. En este caso, dichos parámetros se insertan directamente en la URL y fueron definidos en el Listado~\ref{list:PHPWebServiceRouting}) dentro de la entrada url como \emph{longitude} y \emph{latitude}.
+
+El Listado~\ref{list:PHPWebServiceModel}) muestra como haciendo uso del id del usuario dentro del sistema (lo conocemos porque tuvo que hacer previamente \emph{log in} y envía su cookie en cada petición GET HTTP al servicio Web), la latitud y la longitud se puede mediante peticiones a la base de datos averiguar si existen anuncios de interés en las cercanías del usuario. Actualmente el radio de acción al rededor del cual se buscará en la base de datos PostGIS por posibles anuncios de interés se define directamente en el código PHP; en posteriores implementaciones sería posible por ejemplo, que en función de si el servicio es premium o no, el usuario pueda seleccionar el radio de acción al rededor del cual desea que se busquen anuncios en su posición actual.
+
+\lstset{language=PHP, basicstyle=\small, breaklines=true, float=[P], floatplacement={P}, frame=single, captionpos=b, caption={Código para la búsqueda de anuncios en la base de datos PostgreSQL/PostGIS}, label={list:PHPWebServiceModel}}
+\lstinputlisting{source/PHPWebServiceModel.php}
+
+El método ST\_DWithin del Listado~\ref{list:PHPWebServiceModel}) es un método especial definido por PostGIS que permite hacer búsquedas en la base de datos PostgreSQL de posiciones geográficas que se encuentren dentro de una circunferencia con radio \emph{radius} y centro marcado por el tipo SRID 4326 o también llamado WGS 84 que son las siglas en Inglés para \emph{World Geodetic System 84}. WGS 84\footnote{La Wikipedia proporciona una buena introducción al estándar WGS: \url{http://en.wikipedia.org/wiki/WGS84}} es el sistema usado por el \emph{Global Positioning System} o GPS que es precisamente desde el cual estamos recibiendo datos a través del dispositivo móvil utilizado por el usuario.
+
+Como también se puede observar en el Listado~\ref{list:PHPWebServiceModel}) realizamos peticiones directamente a la base de datos. El objetivo de llevar a cabo las peticiones de este modo es disminuir la sobrecarga de código y por tanto de ciclos de CPU para implementar en el servidor cada petición al servicio Web realizada por los clientes. Este código será usado por muchos usuarios de forma concurrente, por tanto es necesario que se ejecute tan rápidamente como sea posible. Ganamos velocidad pero perdemos legibilidad y mantenibilidad del código.
+
+Se requiere de varias peticiones a la base de datos para poder obtener al final un array con los anuncios (findOneByAdIdAndLanguageId devuelve objetos PHP del tipo Doctrine\_Record\footnote{Sobre objetos Doctrine ver: \url{http://docs.doctrine-project.org/projects/doctrine1/en/latest/en/manual/component-overview.html}}) que se encuentran alrededor de la localización geográfica del usuario (primera consulta a la base de datos) y que están relacionados con categorías a las cuales el usuario se encuentra asociado (segunda consulta a la base de datos).
+
+Y finalmente en el Listado~\ref{list:PHPWebServiceView}) se genera el código JSON que será recibido y procesado por la aplicación cliente Android tal y como se mostró en el Listado~\ref{list:JavaHTTPJSONtreatment})
+
+\lstset{language=PHP, basicstyle=\small, breaklines=true, float=[P], floatplacement={P}, frame=single, captionpos=b, caption={Código para la generación de datos en formato JSON}, label={list:PHPWebServiceView}}
+\lstinputlisting{source/PHPWebServiceView.php}
diff --git a/source/PHPWebServiceActions.php b/source/PHPWebServiceActions.php
new file mode 100644 (file)
index 0000000..1bf4510
--- /dev/null
@@ -0,0 +1,12 @@
+public function executeGetadsbygps(sfWebRequest $request)
+{
+    $this->ads = array();
+    $this->ads = AdDescriptionTable::getInstance()->
+        getAdsByGPSAndUserIdAndLanguageId(
+           $this->getRoute()->getParameters(),
+           $this->getUser()->getGuardUser()->getId(),
+           $this->getUser()->getGuardUser()->getLanguage()->getId());
+
+    if (empty($this->ads))
+        die;
+}
diff --git a/source/PHPWebServiceModel.php b/source/PHPWebServiceModel.php
new file mode 100644 (file)
index 0000000..8b1a210
--- /dev/null
@@ -0,0 +1,22 @@
+public function getAdsByGPSAndUserIdAndLanguageId(array $parameters, $userId, $languageId)
+{
+    $longitude = $parameters['longitude'];
+    $latitude = $parameters['latitude'];
+    $radius = sfConfig::get('app_radius', '100');
+    $adIds = Doctrine_Manager::getInstance()->getCurrentConnection()->fetchColumn(
+        SELECT ad_id FROM office INNER JOIN office_ads ON (office_ads.office_id=office.id)
+        WHERE ST_DWithin(office_gps, ST_GeographyFromText(SRID=4326;POINT($longitude $latitude)), $radius)
+        );
+    foreach ($adIds as $adId)
+    {
+        $adIds[] = Doctrine_Manager::getInstance()->getCurrentConnection()->fetchOne(
+        SELECT ad.id FROM ad INNER JOIN company_category ON (ad.company_categ_id=company_category.id)
+        INNER JOIN general_category ON (company_category.general_categ_id=general_category.id)
+        INNER JOIN user_basket ON (user_basket.general_categ_id=general_category.id)
+        WHERE user_id=$userId AND ad.id=$adId);
+    }
+    $ads = array();
+    foreach($adIds as $adId)
+        $ads[] = $this->findOneByAdIdAndLanguageId($adId, $languageId);
+    return $ads;
+}
diff --git a/source/PHPWebServiceRouting.yml b/source/PHPWebServiceRouting.yml
new file mode 100644 (file)
index 0000000..003537c
--- /dev/null
@@ -0,0 +1,7 @@
+api_getadsbygps:
+  url:     /api/:latitude/:longitude/gpsads.:sf_format
+  class:   sfRequestRoute
+  param:   { module: api, action: getadsbygps }
+  requirements:
+   sf_format: (?:xml|json|yaml)
+   sf_method: [GET]
diff --git a/source/PHPWebServiceView.php b/source/PHPWebServiceView.php
new file mode 100644 (file)
index 0000000..9c98e44
--- /dev/null
@@ -0,0 +1,7 @@
+<?php foreach ($ads as $ad): ?>
+   { "id" : <?php echo $ad->getAd()->getId()?>,
+    "image" : <?php echo $ad->getAd()->getAdMobileImageLink() ?>,
+    "link" : <?php echo $ad->getAdLink()?>,
+    "text" : <?php echo $ad->getAdMobileText()?>,
+    "adname" : <?php echo $ad->getAdName()?> },
+<?php endforeach ?>