Introduzione

In questo articolo creeremo da zero un semplice progetto tramite Eclipse e Flex Builder. Per i nostri scopi utilizzeremo i seguenti strumenti:

  • Eclipse Galileo (versione 3.5.2)
  • JBoss Tools (versione 3.1.0.GA)
  • Flex Builder 3.0.0
  • Granite Eclipse Builder 2.1.0
  • JBoss Seam 2.2.0.GA

Utilizzeremo inoltre l'application server JBoss 4.2.3.GA configurato come server runtime in JBoss Tools. L'applicazione che andremo a creare sarà una rivisitazione di un'applicazione che abbiamo già visto in un articolo precedente della serie [1]. L'applicazione si occuperà di gestire il salvataggio, la modifica e la visualizzazione di una serie di prodotti, tramite una semplice interfaccia composta da una gliglia dati, che mostra tutti i prodotti caricati nel nostro database, e un pulsante in alto che permette l'inserimento di un nuovo prodotto. Ogni riga ha inoltre un piccolo pannello con due pulsanti, uno per aprire in modifica il prodotto in questione e uno per eliminarlo.

Creiamo il progetto Seam

Iniziamo creando un nuovo Seam Web Project:

Figura 1 - Nuovo Seam Web Project.
Figura 1 - Nuovo Seam Web Project.

Nel passaggio successivo inseriamo il nome e lasciamo invariati tutti gli altri valori, assicurandoci che in Configuration sia impostato Dynamic Web Project con Seam 2.2:

Figura 2 - Wizard, impostazione dei parametri fondamentali.
Figura 2 - Wizard, impostazione dei parametri fondamentali.

Nei successivi 3 passaggi accettiamo i valori di default, e andiamo avanti fino ad arrivare al passaggio in cui occorre scegliere il tipo di pacchetto da creare. Qui scegliamo di creare un pacchetto EAR e selezioniamo il profilo di connessione al database MySQL (se non esiste lo creiamo).

Figura 3 - Wizard, impostazione dei parametri fondamentali.
Figura 3 - Wizard, impostazione dei parametri fondamentali.

Concludiamo il wizard cliccando su Finish.

Progetto Client in Flex Builder

Dentro il progetto precedentemente creato con Eclipse, creiamo una cartella, Flex, nel pacchetto GDSTide dove andremo a collocare il progetto Flex Builder.

Figura 4 - Cartella per il progetto del Client.
Figura 4 - Cartella per il progetto del Client.

Successivamente apriamo Flex Builder e creiamo un nuovo Flex Project selezionando come location la cartella precedentemente creata all'interno del progetto Eclipse.

Figura 5 - Nuovo Flex Project
Figura 5 - Nuovo Flex Project

Nel passaggio successivo impostiamo come output folder direttamente la cartella WebContent del progetto Eclipse:

Figura 6 - Output folder del progetto Flex Builder.
Figura 6 - Output folder del progetto Flex Builder.

E concludiamo il wizard impostando, come cartella per i sorgenti, flex_src.

GraniteDS Nature

Torniamo su Eclipse, clicchiamo con il tasto destro sul pacchetto GDSTide-ejb e selezioniamo Add GraniteDS Nature per avviare un altro wizard tramite il quale forniremo al Gas3 le informazioni neccessarie per creare le classi AS3 corrispondenti alle classi Java degli oggetti che verranno scambiati tra server e client.

Figura 7 - GraniteDS Nature
Figura 7 - GraniteDS Nature

Gli oggetti di nostro interesse sono gli entity.

Figura 8 - Selezione del percorso contenente le classi da ricreare in AS3.
Figura 8 - Selezione del percorso contenente le classi da ricreare in AS3.

Impostiamo un filtro in maniera tale da selezionare solo gli entity, e indichiamo anche il percorso ove andare a posizionare le classi AS3 generate, Flex/flex_src del pacchetto principale (GDSTide).

Figura 9 - Percorso dove posizionare le classi AS3.
Figura 9 - Percorso dove posizionare le classi AS3.

Nei due passaggi successivi accettiamo i valori di default, e andiamo avanti fino all'ultimo passaggio in cui impostiamo il template da utilizzare per la creazione delle classi AS3. Qui clicchiamo sul tasto Use Tide.

Figura 10 - Scelta del Template
Figura 10 - Scelta del Template

File di configurazione

Completiamo il progetto con le modifiche ad alcuni file di configurazione. Iniziamo dal file components.xml (nel pacchetto principale sotto la cartella WebContent/WEB-INF) aggiungendo le righe in grassetto

components.xml

<?xml version="1.0" encoding="UTF-8"?>

<components xmlns="http://jboss.com/products/seam/components"

xmlns:core="http://jboss.com/products/seam/core"

...

xsi:schemaLocation="http://jboss.com/products/seam/core http://jboss.com/products/seam/core-2.2.xsd

http://jboss.com/products/seam/persistence

http://jboss.com/products/seam/persistence-2.2.xsd

http://jboss.com/products/seam/drools

http://jboss.com/products/seam/drools-2.2.xsd

http://jboss.com/products/seam/bpm

http://jboss.com/products/seam/bpm-2.2.xsd

http://jboss.com/products/seam/security

http://jboss.com/products/seam/security-2.2.xsd

http://jboss.com/products/seam/mail

http://jboss.com/products/seam/mail-2.2.xsd

http://jboss.com/products/seam/web

http://jboss.com/products/seam/web-2.2.xsd

http://jboss.com/products/seam/components

http://jboss.com/products/seam/components-2.2.xsd

http://www.graniteds.org/config

http://www.graniteds.org/public/dtd/2.1.0/granite-config-2.1.xsd">

<core:init debug="true" jndi-pattern="@jndiPattern@"/>

...

</components>

Aggiungiamo adesso il file granite-config.xml nella cartella WebContent/WEB-INF/granite del pacchetto principale. Qui attiviamo i componenti Seam, generati dal Seam Tools wizard, per la comunicazione con Tide. Questa parte è opzionale perchè basterebbe annotare i componenti da invocare con RemoteDestination o TideEnabled.

granite-config.xml

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE granite-config PUBLIC "-//Granite Data Services

//DTD granite-config internal//EN"

"http://www.graniteds.org/public/dtd/2.0.0/granite-config.dtd">

<granite-config scan="true">

<!--

! Enable Seam components for Tide

!-->

<tide-components>

<tide-component instance-of="org.jboss.seam.security.Identity"/>

<tide-component instance-of="org.jboss.seam.framework.Home"/>

<tide-component instance-of="org.jboss.seam.framework.Query"/>

</tide-components>

</granite-config>

Aggiungiamo anche il file services-config.xml nella cartella WebContent/WEB-INF/flex del pacchetto principale, dove andremo a definire l'unica destinazione necessaria:

services-config.xml

<services-config>

<services>

<service id="granite-service"

class="flex.messaging.services.RemotingService"

messageTypes="flex.messaging.messages.RemotingMessage">

<!--

! Use "tideSeamFactory" and "my-graniteamf" for "seam" destination (see below).

! The destination must be "seam" when using Tide with default configuration.

!-->

<destination id="seam">

<channels>

<channel ref="my-graniteamf"/>

</channels>

<properties>

<factory>tideSeamFactory</factory>

</properties>

</destination>

</service>

</services>

...

</services-config>

Tide può essere usato anche senza definire il file standard di configurazione services-config.xml. Infatti, dal momento che Tide incapsula un RemoteObject, è possibile definire programmaticamente il canale di comunicazione tramite l'uso del DefaultServiceInitializer implementato nel Tide Context

<mx:Application preinitialize="Seam.getInstance().initApplication()">

<mx:Script>

<![CDATA[

import org.granite.tide.service.DefaultServiceInitializer;

Tide.getInstance().addComponent("serviceInitializer",

DefaultServiceInitializer,{contextRoot: "/ context-root " });

]]>

</mx:Script>

...

</mx:Application>

Aggiungiamo adesso le librerie granite.jar, granite-hibernate.jar e granite-seam21.jar nella cartella EarContent/lib del pacchetto GDSTide-ear (server), e granite-essentials.swc e granite.swc nella cartella Flex/libs del pacchetto GDSTide(client).

Per finire su FlexBuilder, dobbiamo impostare alcuni parametri di compilazione (tutto sulla stessa linea, senza a capo), ossia

-keep-as3-metadata=Bindable,Managed,ChangeEvent, NonCommittingChangeEvent,

Transient,Id,Version,Name,In,Inject,Out,Observer,ManagedEvent,Destroy

-includes=org.domain.gdstide.entity.Product

-include-libraries "D:/Applicazioni/Java/eclipse-jee-galileo-SR2-

win32/eclipse/workspace/GDSTide/Flex/libs/granite-essentials.swc"

"D:/Applicazioni/Java/eclipse-jee-galileo-SR2-win32/eclipse

/workspace/GDSTide/Flex/libs/granite.swc" -locale en_US -services

"D:/Applicazioni/Java/eclipse-jee-galileo-SR2-win32/eclipse/workspace

/GDSTide/WebContent/WEB-INF/flex/services-config.xml"

-context-root "/GDSTide"
Figura 11 - Parametri di compilazione.
Figura 11 - Parametri di compilazione.

Per finire aggiungiamo il progetto alla lista di progetti da pubblicare sul server JBoss 4.2.3.

Figura 12 - Publish del progetto su JBoss.
Figura 12 - Publish del progetto su JBoss.

Va notato che tutto quello che abbiamo fin qui creato può essere utilizzato come modello per i futuri progetti con Tide.

Ed ora scriviamo il codice

Utilizziamo il wizard Seam di Eclipse per creare l' entity Product. Il wizard creerà la classe Product e due componenti Seam ProductList and ProductHome. La classe Product avrà il codice seguente

package org.domain.gdstide.entity;

import java.io.Serializable;

...

@Entity

@EntityListeners(DataListener.class)

public class Product implements Serializable, org.granite.tide.IUID

{

@Id @GeneratedValue

private Long id;

@Version

private Integer version;

@Basic

private String uid;

@Basic

private String name;

@Basic

private String descr;

@Basic

private Double price;

@Basic

private Integer quantity;

public Long getId() {

return id;

}

public Integer getVersion() {

return version;

}

public String getUid() {

return uid;

}

public String getName() {

return name;

}

...

}

Nel codice sopra riportato ho messo in evidenza in grassetto due linee di codice (private Long id; e private String uid;) perchè necessitano di un approfondimento. In un'applicazione tipica a tre livelli, Flex, Business Layer, e DataBase, un'oggetto vive in tutti i tre livelli:

  • il client Flex
  • il contesto di persistenza Hibernate/JPA
  • il DataBase

Durante la vita dell'oggetto, l'unico elemento invariante è l'id. L'id lega le differenti versioni dello stesso oggetto nei tre livelli. Quando si lavora su oggetti esistenti, o creati a livello di database, non ci sono problemi perchè l'id è già impostato e viene mantenuto invariato in tutti e tre i livelli durante le operazioni di serializzazione e deserializzazione.

Un problema si può avere quando un'oggetto viene creato nei due livelli superiori. Il nuovo oggetto non avrà un id fino a quando non verrà salvato nel DB e questo significa che tra la creazione dell'oggetto e il suo salvataggio, l'id passerà dal valore null a un valore reale. In questo modo non è possibile avere un legame tra l'oggetto che è stato creato e l'oggetto successivamente salvato.

La soluzione più comune è quella di creare un ulteriore id, l'uid, che viene creato e impostato dal client e salvato assieme all'oggetto. Quando ciò non è possibile, perchè stiamo lavorando su applicativi server già operativi e che non prevedevano tale proprietà, bisogna considerare che si possono verificare dei problemi nel creare degli oggetti da Flex.

Assicuriamoci che, nella cartella Flex/flex_src, sia stata creata la classe AS3 corrispondente. Poiche' non useremo le conversations, in ProductHome, commentiamo il metodo create.

Passiamo ora al Client Flex. L'applicazione avrà lo stesso funzionamento che aveva nell'esempio del precedente articolo [1], ma la modificheremo per mostrare le caratteristiche di Tide (modifiche evidenziate in grassetto).

Scriviamo la pagina principale, che consisterà di un DataGrid e di alcuni tasti

<?xml version="1.0" encoding="utf-8"?>

<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"

layout="absolute"

preinitialize="Seam.getInstance().initApplication()">

<mx:Script>

<![CDATA[

import org.granite.tide.events.TideUIEvent;

import org.granite.tide.service.DefaultServiceInitializer;

import org.granite.tide.seam.Seam;

import org.granite.tide.seam.Context;

import org.granite.tide.seam.framework.PagedQuery;

import org.domain.gdstide.ProductsCtl;

Seam.getInstance().addComponents([ProductsCtl]);

Seam.getInstance().addComponentWithFactory(

"productList", PagedQuery, { maxResults: 36 }

);

[Bindable] [In]

public var productList:PagedQuery;

]]>

</mx:Script>

<mx:Label x="10" y="10" text="GraniteDS - Tide Example"

fontSize="16" color="#E9F3F5"/>

<mx:DataGrid id="dgProducts" dataProvider="{productList}"

bottom="10" right="10" top="68" left="10">

<mx:columns>

<mx:DataGridColumn width="70" headerText="ID" dataField="id"/>

<mx:DataGridColumn width="200" headerText="Product" dataField="name"/>

<mx:DataGridColumn headerText="Description" dataField="descr"/>

<mx:DataGridColumn width="100" headerText="Quantity" dataField="quantity"/>

<mx:DataGridColumn width="100" headerText="Price" dataField="price"/>

<mx:DataGridColumn width="100"

headerText="Manage" itemRenderer="ManageRenderer"/>

</mx:columns>

</mx:DataGrid>

<mx:Button id="bAdd" label="Add New Product"

click="dispatchEvent(new TideUIEvent('createProduct'));" right="10" top="38"/>

</mx:Application>

Diamo un'occhiata al codice evidenziato in grassetto

preinitialize="Seam.getInstance().initApplication()"

Inizializziamo il Tide context e registriamo l'MXML principale, e così anche tutti gli altri annotati con [Name]. Possiamo ora lanciare e intercettare eventi e usare l'injection. L'oggetto Context è il cuore di Tide e costituisce, per la parte client, la rappresentazione del server. Viene salvato in un'istanza singleton per cui è accessibile in qualsiasi punto dell'applicativo Flex. I componenti server possono essere referenziati tramite i loro nomi come proprietà del Context.

Seam.getInstance().addComponents([ProductsCtl]);

Registriamo nel context un componente che vedremo un pò più avanti.

Seam.getInstance().addComponentWithFactory("productList",

PagedQuery,{maxResults:36});

Registriamo il componente productList come componente PagedQuery di Flex, ovvero una collezione in grado di interagire con gli oggetti lato server Query

[Bindable] [In]

public var productList:PagedQuery;

Questa è una variabile injected

dispatchEvent(new TideUIEvent('createProduct'))

Viene lanciato un evento nel contesto Tide che verrà segnalato a tutti gli osservatori interessati a questo evento.

Vediamo le modifiche da apportare al form che ci consente di editare i prodotti:

<?xml version="1.0" encoding="utf-8"?>

<mx:Panel xmlns:mx="http://www.adobe.com/2006/mxml"

layout="absolute" width="400" height="300" title="Products">

<mx:Metadata>

[Name("productEditor")]

</mx:Metadata>

<mx:Script>

<![CDATA[

import org.granite.tide.events.TideUIEvent;

import org.domain.gdstide.entity.Product;

[Bindable] [In]

public var product:Product;

private function save():void {

...

}

]]>

</mx:Script>

<mx:VBox>

<mx:Form>

<mx:FormItem label="Name" fontWeight="bold">

<mx:TextInput id="tiName" text="{product.name}"/>

...

<mx:HBox>

<mx:Button id="bSave" label="Save Product"

click="save();"/>

<mx:Button id="bClose" label="Close"

click="dispatchEvent(new TideUIEvent('closeProductEditor'))"/>

</mx:HBox>

</mx:Form>

</mx:VBox>

</mx:Panel>

Questo MXML verrà registrato automaticamente nel Tide Context perchè annotato con [Name].

Vediamo infine parte della classe ActionScript che fungerà da controller, ProductCtl, dove, oltre a delle variabili che verranno injected ed outjected, ci sono i metodi Observer che verranno eseguiti al verificarsi di determinati eventi:

package org.domain.gdstide{

import flash.display.DisplayObject;

...

[Name("productsCtl")]

public class ProductsCtl {

[In]

public var application:DisplayObject;

[Bindable] [In] [Out]

public var product:Product = null;

[In]

public var productHome:Object;

[In]

public var productList:Object;

private var productEditor:ProductEditor;

[Observer("createProduct")]

public function createProduct():void {

product = new Product();

product.quantity=0;

product.price=0;

productEditor = PopUpManager.createPopUp(application, ProductEditor, true) as ProductEditor;

PopUpManager.centerPopUp(productEditor);

}

...

}

Conclusioni

Con questo articolo, in cui abbiamo visto un esempio completo di utilizzo del framework GDS e Tide, concludiamo questa serie rivolta all'integrazione tra il mondo Flex e il mondo Java. Per coloro che intendono approfondire la tematica, molte informazioni possono essere trovate nella ricca e completa documentazione presente nel sito ufficiale del progetto [2] e nel blog [3].

Riferimenti

  • [1] Angelo Ferraro, "GraniteDS: Flex incontra Java - II parte: EJB3 con Externalization", MokaByte 149, Marzo 2010

http://www2.mokabyte.it/cms/section.run?name=mb149

  • [2] Il sito ufficiale del progetto GraniteDS

http://www.graniteds.org/

  • [3] GraniteDS Blog

http://graniteds.blogspot.com/