sábado, 10 de diciembre de 2011

Persistencia de datos con Google App Engine


Persistencia de datos a través de JDO

Almacenar datos en una aplicación web escalable puede ser complicado. Un usuario podría estar interactuando con cualquiera de una docena de servidores web en un momento dado y la siguiente solicitud de ese usuario podría dirigirse a un servidor web distinto del servidor que haya gestionado la solicitud anterior. Todos los servidores web deben interactuar con datos que también están distribuidos por docenas de equipos y que posiblemente se encuentran en distintas partes del mundo.
Google App Engine soluciona todos estos problemas. La infraestructura de App Engine se encarga de todas las tareas de distribución, replicación y equilibrio de carga de los datos de un API sencilla, además de ofrecer un potente motor de consulta y transacciones.
El almacén de datos de App Engine es uno de los diversos servicios que ofrece App Engine con dos API: un API estándar y otra de nivel inferior. El uso de las API estándares facilita el traslado de las aplicaciones a otros entornos de alojamiento y a otras tecnologías de bases de datos, en caso necesario. Las API estándares "desconectan" la aplicación de los servicios de App Engine. Los servicios de App Engine ofrecen también varias API de nivel inferior que muestran directamente las funciones del servicio. Puedes utilizar las API de nivel inferior para implementar nuevas interfaces de adaptadores o utilizarlas directamente en la aplicación.
App Engine es compatible con dos estándares de API diferentes para el almacén de datos: Objetos de datos Java (JDO) y API de persistencia Java (JPA).DataNucleus Access Platform, una implementación de código abierto de varios estándares de persistencia Java que dispone de un adaptador para el almacén de datos de App Engine, proporciona estas interfaces.
Para el libro de visitas, utilizaremos la interfaz JDO para la recuperación y para la publicación de los mensajes de los usuarios.

Configuración de DataNucleus Access Platform

Access Platform necesita un archivo de configuración que le indique que debe utilizar el almacén de datos de App Engine como servidor para la implementación de JDO. En el WAR final, este archivo se denomina jdoconfig.xml y se encuentra en el directorio war/WEB-INF/classes/META-INF/.
Si utilizas Eclipse, este archivo se crea como src/META-INF/jdoconfig.xml. Cuando creas el proyecto, el archivo se copia automáticamente en war/WEB-INF/classes/META-INF/.
Si no utilizas Eclipse, puedes crear el directorio war/WEB-INF/classes/META-INF/ directamente o dejar que se genere durante el proceso de compilación y copiar el archivo de configuración de otra ubicación. La secuencia de comandos de compilación Ant que se describe en Uso de Apache Ant copia este archivo desrc/META-INF/.
El archivo jdoconfig.xml debe contener lo siguiente:
<?xml version="1.0" encoding="utf-8"?>
<jdoconfig xmlns="http://java.sun.com/xml/ns/jdo/jdoconfig"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="http://java.sun.com/xml/ns/jdo/jdoconfig">

    <persistence-manager-factory name="transactions-optional">
        <property name="javax.jdo.PersistenceManagerFactoryClass"
            value="org.datanucleus.store.appengine.jdo.DatastoreJDOPersistenceManagerFactory"/>
        <property name="javax.jdo.option.ConnectionURL" value="appengine"/>
        <property name="javax.jdo.option.NontransactionalRead" value="true"/>
        <property name="javax.jdo.option.NontransactionalWrite" value="true"/>
        <property name="javax.jdo.option.RetainValues" value="true"/>
        <property name="datanucleus.appengine.autoCreateDatastoreTxns" value="true"/>
    </persistence-manager-factory>
</jdoconfig>

Mejora de la clase JDO

Cuando creas clases JDO, debes utilizar anotaciones Java para describir cómo se deben guardar las instancias en el almacén de datos y cómo se deben volver a crear al recuperarlas de dicho almacén. Access Platform conecta las clases de datos a la implementación mediante un paso de procesamiento posterior a la compilación que DataNucleus denomina "mejora" de las clases.
Si utilizas Eclipse y el complemento de Google, dicho complemento realiza el paso de mejora de la clase JDO automáticamente durante el proceso de compilación.
Si utilizas la secuencia de comandos de compilación Ant que se describe en Uso de Apache Ant, dicha secuencia incluye el paso de mejora necesario.
Para obtener más información acerca de la mejora de la clase JDO, consulta Uso de JDO.

POJO y anotaciones de JDO

JDO permite almacenar objetos Java (a veces denominados "objetos Java antiguos y simples" o POJO) en cualquier almacén de datos con un adaptador compatible con JDO, como DataNucleus Access Platform. El SDK de App Engine incluye un complemento Access Platform para el almacén de datos de App Engine. Esto significa que puedes almacenar instancias de clases definidas en el almacén de datos de App Engine y recuperarlas como objetos mediante el API JDO. Puedes indicar a JDO cómo debe almacenar y reconstruir las instancias de tu clase a través de anotaciones Java.
Vamos a crear una clase Greeting para representar los mensajes individuales publicados en el libro de visitas.
Crea una nueva clase llamada Greeting en el paquete guestbook. (Aquellos usuarios que no utilicen Eclipse pueden crear el archivo Greeting.java en el directorio src/guestbook/). Asigna al archivo de origen el siguiente contenido:
package guestbook;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.users.User;
import java.util.Date;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;
@PersistenceCapable
public class Greeting {
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Key key;

    @Persistent
    private User author;

    @Persistent
    private String content;

    @Persistent
    private Date date;

    public Greeting(User author, String content, Date date) {
        this.author = author;
        this.content = content;
        this.date = date;
    }

    public Key getKey() {
        return key;
    }

    public User getAuthor() {
        return author;
    }

    public String getContent() {
        return content;
    }

    public Date getDate() {
        return date;
    }

    public void setAuthor(User author) {
        this.author = author;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public void setDate(Date date) {
        this.date = date;
    }
}
Esta sencilla clase define tres propiedades de un saludo author (autor), content (contenido) y date (fecha). Estos tres campos privados presentan la anotación@Persistent, que indica a DataNucleus que debe almacenarlos como propiedades de los objetos en el almacén de datos de App Engine.
Esta clase define captadores y definidores de las propiedades que solo utiliza la aplicación. Con el uso de definidores, puedes garantizar de forma sencilla que la implementación de JDO reconozca las actualizaciones. La modificación de los campos omite directamente la función de JDO que guarda los campos actualizados de forma automática, a menos que realices otros cambios en el código que habiliten esto.
La clase también define un campo key (clave), es decir, una clase Key que presenta dos anotaciones: @Persistent y @PrimaryKey. El almacén de datos de App Engine tiene una noción de las claves de entidades y puede representar las claves de varias formas en el objeto. La clase Key representa todos los aspectos de las claves del almacén de datos de App Engine, incluido un ID numérico que se define automáticamente como un valor exclusivo cuando se guarda el objeto.
Para obtener más información acerca de las anotaciones de JDO, consulta Definición de las clases de datos.

La clase PersistenceManagerFactory

Todas las solicitudes que utilizan el almacén de datos crean una nueva instancia de la clase PersistenceManager. Para ello, utilizan una instancia de la clase PersistenceManagerFactory.
Una instancia de PersistenceManagerFactory tarda algún tiempo en inicializarse. Afortunadamente, solo se necesita una instancia para cada aplicación, y esta instancia se puede almacenar en una variable estática que pueden utilizar varias solicitudes y clases. Una forma sencilla de realizar este procedimiento es mediante la creación de una clase envoltorio "singleton" para la instancia estática.
Crea una nueva clase denominada PMF en el paquete guestbook (un archivo denominado PMF.java en el directorio src/guestbook/) que contenga el siguiente contenido:
package guestbook;
import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManagerFactory;
public final class PMF {
    private static final PersistenceManagerFactory pmfInstance =
        JDOHelper.getPersistenceManagerFactory("transactions-optional");

    private PMF() {}

    public static PersistenceManagerFactory get() {
        return pmfInstance;
    }
}

Creación y almacenamiento de objetos

Con DataNucleus y la clase Greeting, la lógica de procesamiento de formularios puede almacenar ahora nuevos saludos en el almacén de datos.
Modifica src/guestbook/SignGuestbookServlet.java tal como se indica para que se parezca a lo que se muestra a continuación:
package guestbook;
import java.io.IOException;
import java.util.Date;
import java.util.logging.Logger;
import javax.jdo.PersistenceManager;
import javax.servlet.http.*;
import com.google.appengine.api.users.User;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;
import guestbook.Greeting;
import guestbook.PMF;
public class SignGuestbookServlet extends HttpServlet {
    private static final Logger log = Logger.getLogger(SignGuestbookServlet.class.getName());

    public void doPost(HttpServletRequest req, HttpServletResponse resp)
                throws IOException {
        UserService userService = UserServiceFactory.getUserService();
        User user = userService.getCurrentUser();

        String content = req.getParameter("content");
        Date date = new Date();
        Greeting greeting = new Greeting(user, content, date);

        PersistenceManager pm = PMF.get().getPersistenceManager();
        try {
            pm.makePersistent(greeting);
        } finally {
            pm.close();
        }

        resp.sendRedirect("/guestbook.jsp");
    }
}
Este código crea una nueva instancia Greeting mediante la invocación del constructor. Para guardar la instancia en el almacén de datos, crea una clasePersistenceManager a través de una clase PersistenceManagerFactory y, a continuación, transmite la instancia al método makePersistent() dePersistenceManager. Las anotaciones y la mejora del código de bytes se toman de ahí. Una vez que se obtiene makePersistent(), el nuevo objeto se guarda en el almacén de datos.

Consultas con JDOQL

El estándar JDO define un mecanismo para las consultas de objetos persistentes denominado JDOQL. Puedes utilizar JDOQL para realizar consultas de entidades del almacén de datos de App Engine y para recuperar objetos con mejoras de la clase JDO.
En este ejemplo, no nos complicaremos y escribiremos el código de consulta directamente en guestbook.jsp. Si se tratara de una aplicación de mayores dimensiones, la lógica de consulta se debería delegar en otra clase.
Modifica war/guestbook.jsp y añade las líneas indicadas de forma que se parezca a lo que se muestra a continuación:
<%@ page contentType="text/html;charset=UTF-8" language="java" %><%@ page import="java.util.List" %><%@ page import="javax.jdo.PersistenceManager" %>
<%@ page import="com.google.appengine.api.users.User" %><%@ page import="com.google.appengine.api.users.UserService" %><%@ page import="com.google.appengine.api.users.UserServiceFactory" %><%@ page import="guestbook.Greeting" %><%@ page import="guestbook.PMF" %>
<html>
  <body>
<%
    UserService userService = UserServiceFactory.getUserService();
    User user = userService.getCurrentUser();
    if (user != null) {
%><p>Hello, <%= user.getNickname() %>! (You can
<a href="<%= userService.createLogoutURL(request.getRequestURI()) %>">sign out</a>.)</p>
<%
    } else {
%><p>Hello!
<a href="<%= userService.createLoginURL(request.getRequestURI()) %>">Sign in</a>
to include your name with greetings you post.</p>
<%
    }
%>
<%
    PersistenceManager pm = PMF.get().getPersistenceManager();
    String query = "select from " + Greeting.class.getName();
    List<Greeting> greetings = (List<Greeting>) pm.newQuery(query).execute();
    if (greetings.isEmpty()) {
%><p>The guestbook has no messages.</p>
<%
    } else {
        for (Greeting g : greetings) {
            if (g.getAuthor() == null) {
%><p>An anonymous person wrote:</p>
<%
            } else {
%><p><b><%= g.getAuthor().getNickname() %></b> wrote:</p>
<%
            }
%><blockquote><%= g.getContent() %></blockquote>
<%
        }
    }
    pm.close();
%>

    <form action="/sign" method="post">
      <div><textarea name="content" rows="3" cols="60"></textarea></div>
      <div><input type="submit" value="Post Greeting" /></div>
    </form>

  </body>
</html>
Para preparar una consulta, debes ejecutar el método newQuery() de una instancia PersistenceManager con el texto de la consulta como cadena. El método devuelve un objeto de consulta. El método execute() del objeto de consulta realiza la consulta y, a continuación, muestra una lista List<> con objetos de resultado del tipo adecuado. La cadena de consulta debe incluir el nombre completo de la clase a la que se dirige la consulta, incluido el nombre del paquete.
Vuelve a crear el proyecto y reinicia el servidor. Consulta http://localhost:8888/. Introduce un saludo y envíalo. El saludo aparecerá encima del formulario. Introduce otro saludo y envíalo. Se mostrarán los dos saludos. Prueba a salir y a volver a acceder utilizando los enlaces; a continuación, intenta enviar mensajes con la sesión iniciada y con la sesión cerrada.
Consejo: en una aplicación real, puede ser una buena idea utilizar caracteres de escape con los caracteres HTML cuando se muestra el contenido enviado por los usuarios como, por ejemplo, los saludos de esta aplicación. JavaServer Pages Standard Tag Library (JSTL) incluye una serie de rutinas que permiten realizar este procedimiento. App Engine incluye JSTL (y otros archivos JAR de tiempo de ejecución relacionados con JSP), así que no es necesario que los incluyas en la aplicación. Busca la función escapeXml en la biblioteca de etiquetas disponible en http://java.sun.com/jsp/jstl/functions. Para obtener más información, consulta el tutorial de J2EE 1.4 de Sun.

Introducción a JDOQL

El libro de invitados muestra actualmente todos los mensajes que se han publicado en el sistema. Además, los mensajes aparecen en el orden en que se crearon. Cuando el libro de invitados contenga muchos mensajes, es posible que resulte más útil mostrar solo los mensajes recientes y que aparezca en primer lugar el mensaje más reciente. Para ello, podemos ajustar la consulta del almacén de datos.
Puedes realizar una consulta con la interfaz JDO mediante JDOQL, un lenguaje de consulta parecido a SQL que permite recuperar objetos de datos. En la página JSP, la cadena de consulta JDOQL se define con la siguiente línea:
    String query = "select from " + Greeting.class.getName();
Dicho de otro modo, la cadena de consulta JDOQL es la siguiente:
select from guestbook.Greeting
Esta consulta busca en el almacén de datos todas las instancias de la clase Greeting que se han guardado hasta el momento.
Una consulta puede especificar el orden en que se deben mostrar los resultados en términos de los valores de las propiedades. Para recuperar todos los objetosGreeting en el orden inverso al de su publicación (es decir, del más reciente al más antiguo), se debería utilizar la siguiente consulta:
select from guestbook.Greeting order by date desc
Se pueden realizar consultas que limiten también el número de resultados. Por ejemplo, para obtener solamente los cinco saludos más recientes, se debería utilizarorder by y range, tal como se muestra a continuación:
select from guestbook.Greeting order by date desc range 0,5
Prueba a realizar este procedimiento con la aplicación del libro de visitas. En guestbook.jsp, sustituye la definición de query por lo siguiente:
    String query = "select from " + Greeting.class.getName() + " order by date desc range 0,5";
Publica más de cinco saludos en el libro de visitas. solo se muestran los cinco más recientes en orden cronológico inverso.
Puedes obtener más información acerca de las consultas y del lenguaje JDOQL en Consultas e índices.

Siguiente...

Todas las aplicaciones web devuelven código HTML generado de forma dinámica a partir del código de la aplicación mediante plantillas o algún otro mecanismo. La mayor parte de las aplicaciones web también necesitan publicar contenido estático, como imágenes, hojas de estilo CSS o archivos JavaScript. Para una mayor eficiencia, App Engine no trata igual los archivos estáticos que el código fuente de la aplicación y los archivos de datos. Vamos a crear ahora una hoja de estilo CSS para esta aplicación que sea un archivo estático.
Luego veremos como utilizar archivos estaticos, pero eso en otra entrada del Blog... ya tenemos mucho para nuestro servidor de las aplicaciones Android,no?
saludo,
MAriano!

No hay comentarios:

Publicar un comentario en la entrada