Operaciones CRUD desde Flex con Pimento y Cinnamon



¿Qué es Pimento Data Services y qué funcionalidades ofrece?

Cuando utilizamos JPA e Hibernate para ejecutar operaciones CRUD en nuestra aplicación creamos las entidades con sus correspondientes anotaciones, luego creamos una capa DAO en la cual invocamos las operaciones de persistencia y finalmente una capa de servicios desde la cual invocamos los métodos del DAO y encapsulamos la lógica de negocios. Si ejecutamos todo este código en una aplicación JEE que implemente AMF podremos invocar estos métodos desde Flex utilizando Remote Objects. Pero en caso que decidamos dotar de más inteligencia a la aplicación Flex podemos trasladar la capa de servicios al cliente y en este caso necesitaríamos invocar directamente a la capa DAO o a las operaciones de persistencia.

Pimento Data Services nos permite, desde ActionScript, invocar las operaciones de persistencia definidas por JPA en el servidor, a través de la clase EntityManager, homóloga de la de JPA, pero adaptada a este tipo de cliente. A través de esta clase podemos invocar las operaciones “load”, “persist”, “remove” y “merge”, además de ejecutar consultas utilizando JPAQL y “Named Query”.

El framework almacena en un “cache” en el cliente las entidades cargadas del servidor, asegurando que exista una sola instancia de cada entidad, y este “cache” se utiliza para enviar al servidor solamente las propiedades que cambien cuando se realice una operación “merge” o para restablecer el estado original de la entidad, también ofrece métodos para eliminar objetos del “cache” e inspeccionar si un objeto ya existe en el “cache”.

Pimento ofrece también funcionalidades que permiten realizar “lazy loading” de entidades. Para todas las asociaciones y colecciones marcadas como “lazy” que no han sido inicializadas en el momento de la carga, el servidor envía solamente “proxies” al cliente, y en el momento necesario, utilizando el método “initialize” de la clase EntityManager se cargarán el resto de los datos.

Ante una operación “merge” de una entidad se compara el estado de la misma con la que se encuentra en el “cache” y se envía al servidor solamente aquellas propiedades que hayan cambiado, este proceso ocurre recursivamente cuando las propiedades son colecciones.

El framework también se ocupa de gestionar las llaves primarias de las entidades, al hacer persistente una nueva entidad se devuelve la misma entidad con el valor correspondiente de su llave primaria inyectada.

Si tenemos propiedades en nuestra entidad en el servidor que no deseamos que lleguen al cliente o que no sean modificadas en éste, anotamos las mismas usando anotaciones propias del framework.
En el servidor podemos interceptar todas las operaciones iniciadas en el cliente y controlar el valor de las propiedades de las entidades involucradas en las operaciones.

Pimento Data Services se compone de dos partes, una se incluye en el cliente y otra en el servidor. La parte que se incluye en el servidor utiliza Spring para inyectar dependencias en todas las clases que reciben las entidades enviadas por el cliente. La parte correspondiente en el cliente incluye librerías que se añaden al proyecto.

El framework ofrece dos modelos de programación para trabajar con entidades persistentes: uno utilizando la clase EntityManager de AS3 y otro utilizando servicios personalizados, cada uno tiene sus ventajas y desventajas. El modelo más simple es el que utiliza el EntityManager de AS3, con este modelo creamos las entidades y le incluimos las anotaciones propias de JPA, luego añadimos la anotación “@Managed” a aquellas entidades que queremos que sean gestionadas por Pimento, se crean las clases AS3 equivalentes a las entidades gestionadas, se inicializa el cliente y se invocan las operaciones CRUD, opcionalmente se pueden crear interceptores.

Los servicios personalizados por su parte son equivalente a las capa de servicios que mencionábamos anteriormente, son clases Java gestionadas por Spring y que se exponen al cliente para que sean invocadas remotamente. El framework incluye tareas ant para generar los “proxies” de esas clases en el cliente.

Desde el punto de vista de flexibilidad los servicios personalizados son más convenientes si planeamos que nuestra aplicación tenga múltiples clientes (Web, de escritorio, Web Service, etc.), pero si el único cliente es Flex entonces la solución más simple es usar el EntityManager de AS3, en este caso solamente tenemos que crear en el servidor las entidades con sus anotaciones correspondientes.
El framework se soporta sobre Cinnamon Remoting, una aplicación JEE que implementa el protocolo AMF3, como BlazeDS, pero a diferencia de éste no soporta mensajería, por lo que se hace un poco complejo implementar la sincronización de datos.

En este artículo veremos a través de un ejemplo como ejecutar operaciones CRUD desde Flex con Pimento Data Services utiizando el EntityManager de AS3. En artículos futuros veremos la funcionalidad de “lazy loading” y los servicios personalizados.

El ejemplo

En el ejemplo que desarrollaremos partimos del hecho que queremos gestionar una entidad denominada User y contamos con un servidor de bases de datos MySQL.

Luego de descargar el framework desde aquí lo primero que haremos será crear la aplicación JEE que utilice Cinnamon y donde crearemos las entidades. Para esto creamos un proyecto de tipo “Dynamic Web Project” con las siguientes características:

  • Nombre del proyecto: PimentoServer
  • Target Runtime: En nuestro caso usaremos Apache Tomcat 6, pero puede utilizarse cualquier otro que se haya configurado.
  • Versión del módulo Web Dinámico: 2.5
  • Configuración: Configuración por defecto para Apache Tomcat 6.0
  • Cambiamos la carpeta donde se compilarán las clases a “PimentoServer/WebContent/WEB-INF/classes”.

Una vez creado el proyecto copiamos el contenido de la carpeta “server/web/WEB-INF/lib” de la distribución del framework en “WebContent/WEB-INF/lib” de nuestro proyecto. Sustituimos el archivo de HSQLDB por el controlador JDBC de MySQL, y copiamos de la carpeta “server/release” los archivos “cinnamon-core”, “cinnamon-reflect” y “pimento-core”. Necesitamos copiar también el paquete “javassist”, este lo podemos encontrar en la distribución de Spring con dependencias.

Creamos una carpeta denominada “config” dentro de “WebContent/WEB-INF” de nuestro proyecto y copiamos los archivos “jpa-spring.xml” y “pimento-spring.xml” en “server/web/WEB-INF/config” en la distribución del framework; también copiamos el archivo “dp.properties.template.mysql” de “server/web/WEB-INF/config/db-templates” como “db.properties” en “WebContent/WEB-INF/config” de nuestro proyecto y ajustamos los valores de la conexión a la base de datos. Como estamos en fase de desarrollo ponemos “hibernate.hbm2ddl.auto=update”.

Finalmente el archivo “web.xml” de nuestro proyecto queda así:

[ftf w=”500″ h=”200″]

Pimento_Server

index.html
index.htm
index.jsp
default.html
default.htm
default.jsp

contextConfigLocation /WEB-INF/config/jpa-spring.xml
/WEB-INF/config/pimento-spring.xml

org.springframework.web.context.ContextLoaderListener org.springframework.web.context.request.RequestContextListener


Cinnamon
org.spicefactory.cinnamon.web.SpringCinnamonServlet
1


Cinnamon
/service/*


[/ftf]

A continuación crearemos la entidad que vamos a gestionar desde el cliente:

[ftf w=”500″ h=”200″]
package com.nonocarballo.pimento.vo;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;

import org.spicefactory.pimento.config.Managed;

@Managed
@Entity
@NamedQueries(value={@NamedQuery(name=”loadByCredentials”, query=”select u from User u where u.userName=:username and u.password=:password”),
@NamedQuery(name=”loadAllUsers”, query=”select u from User u”)})

public class User {

private String fullName;
private String userName;
private String password;
private Long id;
private Boolean isadministrator;

@Id
@GeneratedValue
public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getFullName() {
return fullName;
}

public void setFullName(String fullName) {
this.fullName = fullName;
}

@Column(unique = true)
public String getUserName() {
return userName;
}

public void setUserName(String userName) {
this.userName = userName;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

public Boolean getIsadministrator() {
return isadministrator;
}

public void setIsadministrator(Boolean isadministrator) {
this.isadministrator = isadministrator;
}
}
[/ftf]

Aquí la única anotación propia de Pimento es “@Managed”, que indica que la entidad será gestionada por el framework, o sea, será incluida en el “cache” del cliente cuando sea cargada, su llave primaria será inyectada al hacer persistente una instancia de la clase, se enviarán solamente las propiedades que cambien al realizar una operación “merge” y su estado podrá ser gestionado a través del EntityManager.

Desplegamos la aplicación en Tomcat y arrancamos el servicio, pero antes creamos la base de datos según quedó configurada en el archivo “db.properties”. Al iniciar la aplicación se creará la tabla que representa la entidad en la base de datos. Llenamos esta tabla con información inicial para poder efectuar la autenticación en la UI.

[ftf w=”500″ h=”200″]
INSERT INTO `user` (`id`,`full_name`,`isadministrator`,`password`,`user_name`) VALUES
(1,’Nono Carballo Escalona’,0x01,’nono123′,’nono’);
[/ftf]

Esto es todo lo que haremos en el servidor, a continuación crearemos la UI.

Creamos un proyecto Flex con las siguientes características:

  • Nombre del proyecto: PimentoClient
  • Tipo de aplicación: Web
  • Seleccionamos el SDK por defecto
  • Como tecnología del servidor no seleccionamos ninguna
  • Como carpeta de salida seleccionamos la carpeta WebContent del proyecto PimentoServer que creamos anteriormente.

La interface de usuario contiene un ViewStack con dos elementos, en uno pondremos un panel de autenticación:

login

Al introducir un nombre de usuario y contraseña válidos pasaremos al segundo elemento del View Stack, en el cual se realizará la gestión de la entidad:

crud

Para estructurar mejor el proyecto nos auxiliaremos del framework Robotlegs. Lo primero que haremos será incluir en el proyecto las librerías del framework Pimento y Cinnamon. Copiamos en la carpeta “libs” del proyecto los archivos “cinnamon-1.1.0.swc” y “pimento-1.1.0.swc” que se encuentran en la carpeta “client\release” de la distribución, incluimos también “spicelib-core-2.0.1.swc” y “spicelib-reflect-2.0.1.swc” de la carpeta “client\lib” y la librería del framework Robotlegs.

Luego procedemos a crear el equivalente AS3 de la entidad creada en java:

[ftf w=”500″ h=”200″]
package com.nonocarballo.pimento.vo
{
public class User
{
public function User()
{
}

public var userName:String;
public var password:String;
public var fullName:String;
public var id:Number;
public var isadministrator:Boolean;
}
}
[/ftf]

Como podemos ver, no se incluye el metadata “RemoteClass”, que normalmente utilizamos para indicar la clase equivalente a ésta en el servidor, Pimento y Cinnamon se encargan de registrar la clase y realizar todo el proceso de serialización/deserialización entre AS3 y Java de las instancias cuando viajan de cliente a servidor y viceversa.

La configuración del framework en el cliente comienza con la creación de una instancia de la clase “PimentoConfig”, a través de la cual podemos obtener el “EntityManager” que usaremos para invocar las operaciones de persistencia. La clase PimentoConfig necesita conocer el URL de la aplicación JEE que a través de Cinnamon recibirá las solicitudes del cliente para invocar las operaciones de persistencia, específicamente el URL al servlet “service”. En nuestro caso cargaremos esta información de un archivo xml externo. El comando que hace todo esto queda así:

[ftf w=”500″ h=”200″]
package com.nonocarballo.pimento.command
{
import mx.controls.Alert;
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;
import mx.rpc.http.HTTPService;

import org.robotlegs.mvcs.Command;
import org.spicefactory.pimento.config.PimentoConfig;
import org.spicefactory.pimento.service.EntityManager;

public class StartUpCommand extends Command
{

public function StartUpCommand()
{
super();
}

override public function execute():void{
var service:HTTPService = new HTTPService();
service.url = “config/config.xml”;
service.resultFormat = “e4x”;
service.addEventListener(ResultEvent.RESULT, result);
service.addEventListener(FaultEvent.FAULT, fault);
service.send();

}

private function result(event:ResultEvent):void{
var serviceURL:String = event.result..serviceUrl;
var config:PimentoConfig = new PimentoConfig();
config.serviceUrl = serviceURL+”/service”;
config.defaultTimeout = 5000;
config.configure();
var entityManager:EntityManager = config.entityManager;
injector.mapValue(EntityManager, entityManager);
}

private function fault(event:FaultEvent):void{
Alert.show(event.toString());
}
}
}
[/ftf]

El contenido del archivo “config.xml” que se encuentra en la carpeta “src/config” del proyecto Flex es el siguiente:

[ftf w=”500″ h=”200″]


http://localhost:8080/pimentoserver

[/ftf]

Cuando el usuario introduzca un nombre de usuario y contraseña válidos se ejecutará el comando que realiza la autenticación, que es éste:

[ftf w=”500″ h=”200″]
package com.nonocarballo.pimento.command
{
import com.nonocarballo.pimento.vo.User;

import flash.events.Event;

import mx.controls.Alert;
import mx.events.DynamicEvent;

import org.robotlegs.mvcs.Command;
import org.spicefactory.cinnamon.service.ErrorMessage;
import org.spicefactory.cinnamon.service.ServiceRequest;
import org.spicefactory.pimento.service.EntityManager;
import org.spicefactory.pimento.service.Query;

public class LoginCommand extends Command
{
public function LoginCommand()
{
super();
}

[Inject]
public var entityManager:EntityManager;

[Inject]
public var event:DynamicEvent;

override public function execute():void{
var query:Query = entityManager.createNamedQuery(“loadByCredentials”);
query.setNamedParameter(“username”, event.username);
query.setNamedParameter(“password”, event.password);
var request:ServiceRequest = query.getSingleResult();
request.addResultHandler(result);
request.addErrorHandler(fault);
}

private function result(value:User):void{
dispatch(new Event(“loginOK”));
dispatch(new Event(“loadAllUsers”));
}

private function fault(value:ErrorMessage):void{
Alert.show(value.message);
}
}
}
[/ftf]

Como vemos en el método “execute”, el modo de uso de la clase EntityManager de Pimento es muy similar a su homóloga de JPA, pero con la característica distintiva que las operaciones de persistencia son asincrónicas, de ahí que debamos registrar manejadores para obtener los resultados y los posibles errores.

De forma similar el código para realizar las operaciones CRUD queda así:

Adicionar
[ftf w=”500″ h=”200″]
package com.nonocarballo.pimento.command
{
import com.nonocarballo.pimento.vo.User;

import flash.events.Event;

import mx.controls.Alert;
import mx.events.DynamicEvent;

import org.robotlegs.mvcs.Command;
import org.spicefactory.cinnamon.service.ServiceRequest;
import org.spicefactory.pimento.service.EntityManager;

public class AddUserCommand extends Command
{
public function AddUserCommand()
{
super();
}

[Inject]
public var event:DynamicEvent;

[Inject]
public var entityManager:EntityManager;

override public function execute():void{
var request:ServiceRequest = entityManager.persist(event.user);
request.addResultHandler(result);
request.addErrorHandler(fault);
}

private function result(value:User):void{
dispatch(new Event(“loadAllUsers”));
}

private function fault(value:*):void{
Alert.show(“Error insertando”);
}
}
}
[/ftf]

Modificar
[ftf w=”500″ h=”200″]
package com.nonocarballo.pimento.command
{
import com.nonocarballo.pimento.vo.User;

import flash.events.Event;

import mx.controls.Alert;
import mx.events.DynamicEvent;

import org.robotlegs.mvcs.Command;
import org.spicefactory.cinnamon.service.ServiceRequest;
import org.spicefactory.pimento.service.EntityManager;

public class UpdateUserCommand extends Command
{
public function UpdateUserCommand()
{
super();
}

[Inject]
public var event:DynamicEvent;

[Inject]
public var entityManager:EntityManager;

override public function execute():void{
entityManager.evict(event.user);
var request:ServiceRequest = entityManager.merge(event.user);
request.addResultHandler(result);
request.addErrorHandler(fault);
}

private function result(value:User):void{
dispatch(new Event(“loadAllUsers”));
}

private function fault(value:*):void{
Alert.show(“Error actualizando”);
}
}
}
[/ftf]

Eliminar
[ftf w=”500″ h=”200″]
package com.nonocarballo.pimento.command
{
import com.nonocarballo.pimento.vo.User;

import flash.events.Event;

import mx.controls.Alert;
import mx.events.DynamicEvent;

import org.robotlegs.mvcs.Command;
import org.spicefactory.cinnamon.service.ServiceRequest;
import org.spicefactory.pimento.service.EntityManager;

public class DeleteUserCommand extends Command
{
public function DeleteUserCommand()
{
super();
}

[Inject]
public var entityManager:EntityManager;

[Inject]
public var event:DynamicEvent;

override public function execute():void{
var request:ServiceRequest = entityManager.remove(event.user);
request.addResultHandler(result);
request.addErrorHandler(fault);
}

private function result(value:User):void{
dispatch(new Event(“loadAllUsers”));
}

private function fault(value:*):void{
Alert.show(“Error eliminando”);
}
}
}
[/ftf]

Listar
[ftf w=”500″ h=”200″]
package com.nonocarballo.pimento.command
{
import mx.collections.ArrayCollection;
import mx.controls.Alert;
import mx.events.DynamicEvent;

import org.robotlegs.mvcs.Command;
import org.spicefactory.cinnamon.service.ServiceRequest;
import org.spicefactory.pimento.service.EntityManager;
import org.spicefactory.pimento.service.Query;

public class LoadAllUsersCommand extends Command
{
public function LoadAllUsersCommand()
{
super();
}

[Inject]
public var entityManager:EntityManager;

override public function execute():void{
var query:Query = entityManager.createNamedQuery(“loadAllUsers”);
var request:ServiceRequest = query.getResultList();
request.addResultHandler(result);
request.addErrorHandler(fault);
}

private function result(value:Array):void{
var event:DynamicEvent = new DynamicEvent(“allUsersLoaded”);
event.usersList = new ArrayCollection(value);
dispatch(event);
}

private function fault(value:*):void{
Alert.show(“Error cargando”);
}
}
}
[/ftf]

El código fuente de los proyectos del servidor y el cliente se puede descargar de aquí.

Conclusiones

Hemos visto el modelo de programación más simple de Pimento Data Services, este modelo nos permite trasladar al cliente la lógica de la capa de servicios e invocar directamente desde éste las operaciones de persistencia de JPA en el servidor. Hemos dejado fuera las funcionalidades del “lazy loading” y los servicios personalizados, que serán tratados en artículos futuros.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Acerca de Made In Flex

Made In Flex es una comunidad de desarrolladores de Apache Flex creada en 2006.

Apache Flex, anteriormente conocido como Adobe Flex, es un SDK (kit de desarrollo de software) para crear aplicaciones enriquecidas - multiplataforma basadas en Adobe Flash donado por Adobe a la fundación Apache in 2011 y promocionado a proyecto de primer nivel en Diciembre de 2012.

Actualmente estamos cambiando muchos aspectos del sitio web para ofrecer un sitio útil para toda la comunidad que tenga en cuenta las necesidades actuales.

Últimas Fotos

Instalador de Apache Flex

Entrar o Registrase