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. Remote calls within the current conversation scope
25.6. Richieste batch
25.7. Lavorare con i tipi di dati
25.7.1. Primitives / Basic Types
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. A Custom Loading Indicator
25.11. Controlling what data is returned
25.11.1. Constraining normal fields
25.11.2. Constraining Maps and Collections
25.11.3. Constraining objects of a specific type
25.11.4. Combining Constraints
25.12. Richieste transazionali
25.13. Messaggistica JMS
25.13.1. Configurazione
25.13.2. Iscriversi ad un Topic JMS
25.13.3. Disiscriversi da un Topic
25.13.4. Tuning the Polling Process

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;
    }
}

You also need to create a local interface for our new component - take special note of the @WebRemote annotation, as it's required to make our method accessible via remoting:

@Local

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

That's all the server-side code we need to write. Now for our web page - create a new page and import the helloAction component:


<s:remote include="helloAction"/>

To make this a fully interactive user experience, let's add a button to our page:


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

We'll also need to add some more script to make our button actually do something when it's clicked:


<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
>

We're done! Deploy your application and browse to your page. Click the button, and enter a name when prompted. A message box will display the hello message confirming that the call was successful. If you want to save some time, you'll find the full source code for this Hello World example in Seam's /examples/remoting/helloworld directory.

So what does the code of our script actually do? Let's break it down into smaller pieces. To start with, you can see from the Javascript code listing that we have implemented two methods - the first method is responsible for prompting the user for their name and then making a remote request. Take a look at the following line:


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

The first section of this line, Seam.Component.getInstance("helloAction") returns a proxy, or "stub" for our helloAction component. We can invoke the methods of our component against this stub, which is exactly what happens with the remainder of the line: sayHello(name, sayHelloCallback);.

What this line of code in its completeness does, is invoke the sayHello method of our component, passing in name as a parameter. The second parameter, sayHelloCallback isn't a parameter of our component's sayHello method, instead it tells the Seam Remoting framework that once it receives the response to our request, it should pass it to the sayHelloCallback Javascript method. This callback parameter is entirely optional, so feel free to leave it out if you're calling a method with a void return type or if you don't care about the result.

The sayHelloCallback method, once receiving the response to our remote request then pops up an alert message displaying the result of our method call.

The Seam.Component Javascript object provides a number of client-side methods for working with your Seam components. The two main methods, newInstance() and getInstance() are documented in the following sections however their main difference is that newInstance() will always create a new instance of a component type, and getInstance() will return a singleton instance.

Seam Remoting also supports the evaluation of EL expressions, which provides another convenient method for retrieving data from the server. Using the Seam.Remoting.eval() function, an EL expression can be remotely evaluated on the server and the resulting value returned to a client-side callback method. This function accepts two parameters, the first being the EL expression to evaluate, and the second being the callback method to invoke with the value of the expression. Here's an example:


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

In this example, the expression #{customers} is evaluated by Seam, and the value of the expression (in this case a list of Customer objects) is returned to the customersCallback() method. It is important to remember that the objects returned this way must have their types imported (via s:remote) to be able to work with them in Javascript. So to work with a list of customer objects, it is required to import the customer type:


<s:remote include="customer"/>

In the configuration section above, the interface, or "stub" for our component is imported into our page either via seam/resource/remoting/interface.js: or using the s:remote tag:


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

<s:remote include="customerAction"/>

By including this script in our page, the interface definitions for our component, plus any other components or types that are required to execute the methods of our component are generated and made available for the remoting framework to use.

There are two types of client stub that can be generated, "executable" stubs and "type" stubs. Executable stubs are behavioural, and are used to execute methods against your session bean components, while type stubs contain state and represent the types that can be passed in as parameters or returned as a result.

The type of client stub that is generated depends on the type of your Seam component. If the component is a session bean, then an executable stub will be generated, otherwise if it's an entity or JavaBean, then a type stub will be generated. There is one exception to this rule; if your component is a JavaBean (ie it is not a session bean nor an entity bean) and any of its methods are annotated with @WebRemote, then an executable stub will be generated for it instead of a type stub. This allows you to use remoting to call methods of your JavaBean components in a non-EJB environment where you don't have access to session beans.

The Seam Remoting Context contains additional information which is sent and received as part of a remoting request/response cycle. At this stage it only contains the conversation ID but may be expanded in the future.

Seam Remoting allows multiple component calls to be executed within a single request. It is recommended that this feature is used wherever it is appropriate to reduce network traffic.

The method Seam.Remoting.startBatch() will start a new batch, and any component calls executed after starting a batch are queued, rather than being sent immediately. When all the desired component calls have been added to the batch, the Seam.Remoting.executeBatch() method will send a single request containing all of the queued calls to the server, where they will be executed in order. After the calls have been executed, a single response containining all return values will be returned to the client and the callback functions (if provided) triggered in the same order as execution.

If you start a new batch via the startBatch() method but then decide you don't want to send it, the Seam.Remoting.cancelBatch() method will discard any calls that were queued and exit the batch mode.

To see an example of a batch being used, take a look at /examples/remoting/chatroom.

To aid in tracking down bugs, it is possible to enable a debug mode which will display the contents of all the packets send back and forth between the client and server in a popup window. To enable debug mode, either execute the setDebug() method in Javascript:


Seam.Remoting.setDebug(true);

O lo si configuri via components.xml:


<remoting:remoting debug="true"/>

To turn off debugging, call setDebug(false). If you want to write your own messages to the debug log, call Seam.Remoting.log(message).

When invoking a remote component method, it is possible to specify an exception handler which will process the response in the event of an exception during component invocation. To specify an exception handler function, include a reference to it after the callback parameter in your JavaScript:

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

If you do not have a callback handler defined, you must specify null in its place:

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

The exception object that is passed to the exception handler exposes one method, getMessage() that returns the exception message which is produced by the exception thrown by the @WebRemote method.

The default loading message that appears in the top right corner of the screen can be modified, its rendering customised or even turned off completely.

When a remote method is executed, the result is serialized into an XML response that is returned to the client. This response is then unmarshaled by the client into a Javascript object. For complex types (i.e. Javabeans) that include references to other objects, all of these referenced objects are also serialized as part of the response. These objects may reference other objects, which may reference other objects, and so forth. If left unchecked, this object "graph" could potentially be enormous, depending on what relationships exist between your objects. And as a side issue (besides the potential verbosity of the response), you might also wish to prevent sensitive information from being exposed to the client.

Seam Remoting provides a simple means to "constrain" the object graph, by specifying the exclude field of the remote method's @WebRemote annotation. This field accepts a String array containing one or more paths specified using dot notation. When invoking a remote method, the objects in the result's object graph that match these paths are excluded from the serialized result packet.

For all our examples, we'll use the following Widget class:

@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
}

By default there is no active transaction during a remoting request, so if you wish to perform database updates during a remoting request, you need to annotate the @WebRemote method with @Transactional, like so:

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

Seam Remoting provides experimental support for JMS Messaging. This section describes the JMS support that is currently implemented, but please note that this may change in the future. It is currently not recommended that this feature is used within a production environment.

There are two parameters which you can modify to control how polling occurs. The first one is Seam.Remoting.pollInterval, which controls how long to wait between subsequent polls for new messages. This parameter is expressed in seconds, and its default setting is 10.

The second parameter is Seam.Remoting.pollTimeout, and is also expressed as seconds. It controls how long a request to the server should wait for a new message before timing out and sending an empty response. Its default is 0 seconds, which means that when the server is polled, if there are no messages ready for delivery then an empty response will be immediately returned.

Caution should be used when setting a high pollTimeout value; each request that has to wait for a message means that a server thread is tied up until a message is received, or until the request times out. If many such requests are being served simultaneously, it could mean a large number of threads become tied up because of this reason.

It is recommended that you set these options via components.xml, however they can be overridden via Javascript if desired. The following example demonstrates how to configure the polling to occur much more aggressively. You should set these parameters to suitable values for your application:

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;