SeamFramework.orgCommunity Documentation

Capitolo 25. Remoting

25.1. Configurazione
25.2. L'oggetto "Seam"
25.2.1. Esempio Hello World
25.2.2. Seam.Component
25.2.3. Seam.Remoting
25.3. Valutazione delle espressioni EL
25.4. Interfacce client
25.5. Il contesto
25.5.1. Impostazione e lettura dell'ID di conversazione
25.5.2. Chiamate remote all'interno della conversazone corrente
25.6. Richieste batch
25.7. Lavorare con i tipi di dati
25.7.1. Tipi primitivi/base
25.7.2. JavaBeans
25.7.3. Date e orari
25.7.4. Enums
25.7.5. Collections
25.8. Debugging
25.9. Gestione delle eccezioni
25.10. Il messaggio di caricamento
25.10.1. Cambiare il messaggio
25.10.2. Nascondere il messaggio di caricamento
25.10.3. Un indicatore di caricamento personalizzato
25.11. Controllare i dati restituiti
25.11.1. Vincolare campi normali
25.11.2. Vincolare mappe e collezioni
25.11.3. Vincolare oggetti di tipo specifico
25.11.4. Combinare i vincoli
25.12. Richieste transazionali
25.13. Messaggistica JMS
25.13.1. Configurazione
25.13.2. Sottoscrivere ad un topic JMS
25.13.3. Disiscriversi da un topic
25.13.4. Fare il tuning del processo di polling

Seam fornisce un metodo per accedere in modo remoto i componenti da una pagina web, usando AJAX (Asynchronous Javascript and XML). Il framework per questa funzionalità viene fornito con quasi nessuno sforzo di sviluppo - i componenti richiedono solamente una semplice annotazione per diventare accessibile via AJAX. Questo capitolo descrive i passi richiesti per costruire una pagina web abilitata a AJAX, poi spiega con maggior dettaglio le caratteristiche del framework Seam Remoting.

Per usare remoting, il resource servlet di Seam deve essere innanzitutto configurato nel file web.xml:


<servlet>
  <servlet-name
>Seam Resource Servlet</servlet-name>
  <servlet-class
>org.jboss.seam.servlet.SeamResourceServlet</servlet-class>
</servlet>

<servlet-mapping>
  <servlet-name
>Seam Resource Servlet</servlet-name>
  <url-pattern
>/seam/resource/*</url-pattern>
</servlet-mapping
>

Il passo successivi è importare il Javascript necessario nella propria pagina web. Ci sono un minimo di due script da importare. Il primo contiene tutto il codice del framework lato client che abilita le funzionalità di remoting:


<script type="text/javascript" src="seam/resource/remoting/resource/remote.js"
></script
>

Il secondo script contiene gli stub e le definizioni tipo per i componenti da chiamare. Viene generato dinamicamente basandosi sull'interfaccia locale dei propri componenti, ed include le definizioni tipo per tutte le classi che possono essere usate per chiamare i metodi remoti dell'interfaccia. Il nome dello script riflette il nome del componente. Per esempio se si ha un bean di sessione stateless annotato con @Name("customerAction"), allora il tag dello script dovrebbe essere simile a:


<script type="text/javascript" 
          src="seam/resource/remoting/interface.js?customerAction"
></script
>

Se si vuole accedere a più di un componente dalla stessa pagina, allora li si includa tutti come parametri nel tag script:


<script type="text/javascript" 
        src="seam/resource/remoting/interface.js?customerAction&accountAction"
></script
>

In alternativa si può usare il tag s:remote per importare il Javascript richiesto. Si separi ciascun componente o nome di classe che si vuole importare con una virgola:



  <s:remote include="customerAction,accountAction"/>    
    

L'interazione lato client con i componenti viene eseguita tutta tramite l'oggetto Javascript Seam. Quest'oggetti è definito in remote.js, e lo si userà per fare chiamate asincrone verso il componente. E' suddiviso in due aree di funzionalità; Seam.Component contiene metodi per lavorare con i componenti e Seam.Remoting contiene metodi per eseguire le richieste remote. La via più facile per diventare familiare con quest'oggetto è cominciare con un semplice esempio.

Si cominci con un semplice esempio per vedere come funziona l'oggetto Seam

@Stateless

@Name("helloAction")
public class HelloAction implements HelloLocal {
    public String sayHello(String name) {
        return "Hello, " + name;
    }
}

E' anche necessario creare un'interfaccia locale per il nuovo componente - tenete a mente in particolare l'annotazione @WebRemote, poiché è necessaria a rendere un metodo accessibile via remoting:

@Local

public interface HelloLocal {
  @WebRemote
  public String sayHello(String name);
}

Quello è tutto il codice da scrivere.

Ora, per quantro riguarda la pagina web - bisogna creare una nuova pagina e importare il componente helloAction:


<s:remote include="helloAction"/>

Per rendere l'esperienza dell'utente veramente interattiva, si aggiunga un bottone alla pagina:


<button onclick="javascript:sayHello()"
>Say Hello</button
>

Bisognerà anche aggiungere uno script per far fare qualcosa al bottone quando viene cliccato:


<script type="text/javascript">
  //<![CDATA[

  function sayHello() {
    var name = prompt("What is your name?");
    Seam.Component.getInstance("helloAction").sayHello(name, sayHelloCallback);
  }

  function sayHelloCallback(result) {
    alert(result);
  }

   // ]]>
</script
>

Abbiamo finito! Installate l'applicazione e andate col browser alla pagina creata. Premete il pulsante e inserite un nome quando richiesto. Una finestra mostrerà il messaggio di saluto che confermerà che la chiamata è avvenuta con successo. Per risparmiare tempo, cercate il codice dell'esempio Hello World nella directory /examples/remoting/helloworld di Seam.

Quindi, cosa fa realmente il codice del nostro script? Dividiamolo in pezzi più piccoli. Tanto per iniziare, dal listato Javascript si vede che abbiamo implementato due metodi - il primo serve a chiedere all'utente il suo nome e a fare una richiesta remota. Guardate la seguente linea:


Seam.Component.getInstance("helloAction").sayHello(name, sayHelloCallback);

La prima parte di questa linea, Seam.Component.getInstance("helloAction") restituisce un proxy, o "stub", del componente helloAction. Possiamo chiamare i metodi di questo componente usando tale stub, che è ciò che accade nel resto della linea: sayHello(name, sayHelloCallback);.

Nel suo complesso questa linea di codice invoca il metodo sayHello del componente, passandogli name come parametro. Il secondo parametro, sayHelloCallback non è un parametro del metodo sayHello del componente, ma, invece, comunica al Remoting framework di Seam che, una volta ricevuta la risposta alla richiesta, deve passarla al metodo Javascript sayHelloCallback. Questo parametro di callback è completamente opzionale, quindi sentitevi liberi di non usarlo se chiamate un metodo che restituisce void o se non siete interessati al risultato.

Il metodo sayHelloCallback, una volta ricevuta la risposta, mostra un messaggio di avviso con il risultato della chiamata.

L'oggetto Javascript Seam.Component fornisce un serie di metodi lato client per lavorare con i componenti Seam dell'applicazione. I due metodi principali, newInstance() e getInstance() sono documentati nelle sezioni successive, comunque, la loro differenza principale sta nel fatto che newInstance() crea sempre una nuova istanza di un tipo di componente, mentre getInstance() restituisce un'istanza singleton.

Seam Remoting supporta anche il calcolo di espressioni EL, che forniscono un altro metodo conveniente per ottenere dati dal server. Usando la funzione Seam.Remoting.eval(), è possibile calcolare un'espressione EL da remoto sul server e restituire il valore risultante a un metodo callback lato del client. Questa funzione accetta due parametri, di cui il primo è l'espressione EL da calcolare e il secondo il metodo callback da invocare con il valore dell'espressione. Ecco un esempio:


  function customersCallback(customers) {
    for (var i = 0; i < customers.lengthi++) {
      alert("Got customer: " + customers[i].getName());
    }    
  }
    
  Seam.Remoting.eval("#{customers}"customersCallback);  
    

In questo esempio l'espressione #{customers} è calcolata da Seam, e il suo valore (in questo caso una lista di oggetti customer) è restituita al metodo customersCallback(). E' importante ricordare che occorre importare i tipi degli oggetti restituiti in questo modo (via s:remote) per poterli usare nel Javascript. Così per lavorare con una lista di oggetti customer, è necessario importare il tipo customer:


<s:remote include="customer"/>

Nella sezione di configurazione vista sopra, l'interfaccia, o "stub", del componente è importata nella pagina o attraverso seam/resource/remoting/interface.js o usando la tag s:remote.


<script type="text/javascript" 
        src="seam/resource/remoting/interface.js?customerAction"
></script
>

<s:remote include="customerAction"/>

Includenco questo script nella pagina, le definizioni delle interfacce del componente, più quelle di ogni di ogni altro componente o tipo necessario a eseguire i metodi del componente in questione sono generate e rese visibili perché il framework di remoting possa utilizzarle.

E' possibile generare due tipi di stub client, stubs "eseguibili" stubs e stubs "tipo". Gli stubs eseguibili sono dotati di comportamento, e sono usati per eseguire metodi dei componenti session bean, mentre gli stubs tipo servono a contenere dello stato e rappresentano i tipi che possono essere passati come parametri o restituiti come risultati.

Il tipo di stub client che viene generato dipende dal tipo di componente Seam. Se il componente è un session bean, allora sarà generato uno stub eseguibile, altrimenti, se si tratta di un entity bean o di un Java bean, sarò generato uno stub tipo. Vi è una sola eccezione a questa regola; se il componente è un Javabean (cioè non è né un session bean né un entity bean) e uno dei suoi metodi è annotato con @WebRemote, allora sarà generato uno stub eseguibile invece di uno stub tipo. Questo consente di usare il remoting per chiamare i componenti JavaBean in un ambiente non EJB dove non occorre avere accesso ai session bean.

L'oggetto contesto (Seam Remoting Context) contiene informazioni aggiuntive che sono inviate e ricevute come parte del ciclo di richiesta/risposta. Attualmente contiene l'id della conversazione, ma potrebbe essere espanso in futuro.

Seam Remoting permette di eseguire più chiamate a componenti all'interno di una singola richiesta. Si raccomanda di usare questa funzionalità ogni volta che si riveli appropriato allo scopo di ridurre il traffico di rete.

Il metodo Seam.Remoting.startBatch() avvierà un nuovo batch, e tutte le chiamate a componenti eseguite in seguito saranno messe in coda invece che inviate immediatamente. Quando tutte le chiamate desiderate saranno state aggiunte al batch, il metodo Seam.Remoting.executeBatch() invierà una singola richiesta con la coda delle chiamate al server, dove saranno eseguite nell'ordine specificato. Dopo l'esecuzione delle chiamate, una singola risposta con tutti i valori di ritorno sarà restituita al client e le funzioni callback (se specificate) saranno chiamate nello steso ordine di esecuzione.

Se si da il via ad un nuovo batch usando il metodo startBatch(), ma successivamente si decide di non inviarlo, il metodo Seam.Remoting.cancelBatch() annullerà tutte le chiamate già in coda e uscirà dalla modalità batch.

Per vedere un esempio di batch in azione, date un'occhiata a /examples/remoting/chatroom.

Per aiutare l'individuazione dei bug, è possibile abilitare una modalità di debug che mostrerà in una finestra di popup il contenuto di tutti i pacchetti inviati avanti e indietro tra client e server. Per abilitare tale modalità, o si chiama il metodo setDebug() in Javascript:


Seam.Remoting.setDebug(true);

O lo si configuri via components.xml:


<remoting:remoting debug="true"/>

Per disabiliare la modalità di debug, occorre chiamare setDebug(false). Se si vogliono scrivere dei messaggi propri nel log di debug, occorre chiamare Seam.Remoting.log(message).

Quando si fa una chiamata remota, è possibile specificare un gestore di eccezioni che processerà la risposta nell'eventualità che il metodo lanci un'eccezione. Per specificare una funzione di gestione delle eccezioni, bisogna includere nel Javascript un riferimento ad essa dopo il parametro di callback:

var callback = function(result) { alert(result); };
var exceptionHandler = function(ex) { alert("An exception occurred: " + ex.getMessage()); };
Seam.Component.getInstance("helloAction").sayHello(name, callback, exceptionHandler);

Se non viene indicato il gestore della callback, al suo posto bisogna specificare null:

var exceptionHandler = function(ex) { alert("An exception occurred: " + ex.getMessage()); };
Seam.Component.getInstance("helloAction").sayHello(name, null, exceptionHandler);

L'oggetto eccezione che viene passato al gestore espone un solo metodo, getMessage(), che restituisce il messaggio di errore prodotto dall'eccezione lanciata dal memtodo annotato con @WebRemote.

Il messaggio di caricamento predefinito che appare nell'angolo dello schermo in alto a destra può essere modificato e il suo rendering personalizzato o anche eliminato completamente.

Quando un metodo remoto viene esegutio, il risultato viene serializzato in una risposta XML che viene restituita al client. Questa risposta viene allora deserializzata dal client in un oggetto Javascript. Nel caso di tipi complessi (vedi JavaBean) che includono riferimenti ad altri oggetti, anche tutti gli oggetti referenziati vengono serializzati all'interno della risposta. Questi oggetti possono referenziarne altri, che possono referenziarne altri ancora e così via. Se non lo si controlla, questo "grafo" di oggetti potrebbe essere potenzialmente enorme, a seconda delle relazioni esistenti tra gli oggetti stessi. E vi è l'ulteriore problema (oltre alla potenziale verbosità della risposta) che potrebbe essere desiderabile impedire che delle informazioni sensibili vengano esposte al client.

Seam Remoting fornisce un modo semplice per "vincolare" il grafo di oggetti: basta valorizzare il campo exclude dell'annotazione @WebRemote del metodo remoto. Questo campo accetta un array di stringhe contenenti uno o più percorsi specificati usando la notazione col punto. Quando un metodo remoto viene invocato, gli oggetti del grafo risultante il cui percorso coincide con uno di questi vengono esclusi dalla serializzazione.

Per tutti gli esempi utilizzeremo la seguente classe Widget:

@Name("widget")

public class Widget
{
  private String value;
  private String secret;
  private Widget child;
  private Map<String,Widget> widgetMap;
  private List<Widget> widgetList;
  
  // getters and setters for all fields
}

Di default non vi è alcuna transazione attiva durante una richiesta remota, così che, se si vogliono apportare delle modifiche al database, è necessario annotare il metodo @WebRemote con @Transactional, come di seguito:

  @WebRemote @Transactional(TransactionPropagationType.REQUIRED)
  public void updateOrder(Order order) {
    entityManager.merge(order);
  }

Seam Remoting fornisce supporto sperimentale per la gestione di messaggi JMS. Questa sezione descrive il tipo di supporto attualmente implementato, ma si tenga presente che esso potrà cambiare in futuro. Attualmente si raccomanda di non usare tale funzionalità in un ambiente di produzione.

E' possibile controllare il polling dei messaggi attraverso due parametri. Il primo è Seam.Remoting.pollInterval, che controlla quanto tempo passa tra due polling successivi per verificare l'arrivo di nuovi messaggi. Tale parametro è espresso in secondi, e il suo valore predefinito è 10.

Il secondo parametro è Seam.Remoting.pollTimeout, che pure è espresso in secondi. Controlla per quanto tempo una richiesta al server debba stare in attesa di un nuovo messaggio prima di andare in time out e inviare una risposta vuota. Il valore predefinito è 0: ciò significa che quando il server viene interrogato, se non ci sono nuovi messaggi pronti alla consegna, allora viene immediatamente restituita una risposta vuota.

Occorre essere molto cauti nell'impostare un valore alto di pollTimeout; ogni richiesta in attesa di un messaggio tiene un thread attivo finché non viene ricevuto un messaggio o finché la richiesta non va in time out. Se molte di queste richieste devono essere gestite contemporaneamente, si potrebbero avere molti thread in attesa.

Si raccomanda si impostare queste opzioni in components.xml, anche se possono essere sovrascritte dal codice Javascript. L'esempio seguente mostra come configurare il polling in modo da renderlo molto aggressivo. Dovreste impostare questi parametri a dei valori adatti alla vostra applicazione:

Via components.xml:


<remoting:remoting poll-timeout="5" poll-interval="1"/>

Via JavaScript:


// Attendere 1 secondo tra la ricezione della risposta del pool e l'invio della successiva richiesta di pool.
Seam.Remoting.pollInterval = 1;
  
// Attendere fino a 5 secondi sul server per nuovi messaggi
Seam.Remoting.pollTimeout = 5;