Seam FrameworkCommunity Documentation

Chapter 26. Remoting

26.1. Configuration
26.2. The "Seam" object
26.2.1. A Hello World example
26.2.2. Seam.Component
26.2.3. Seam.Remoting
26.3. Client Interfaces
26.4. The Context
26.4.1. Setting and reading the Conversation ID
26.4.2. Remote calls within the current conversation scope
26.5. Batch Requests
26.6. Working with Data types
26.6.1. Primitives / Basic Types
26.6.2. JavaBeans
26.6.3. Dates and Times
26.6.4. Enums
26.6.5. Collections
26.7. Debugging
26.8. Handling Exceptions
26.9. The Loading Message
26.9.1. Changing the message
26.9.2. Hiding the loading message
26.9.3. A Custom Loading Indicator
26.10. Controlling what data is returned
26.10.1. Constraining normal fields
26.10.2. Constraining Maps and Collections
26.10.3. Constraining objects of a specific type
26.10.4. Combining Constraints
26.11. Transactional Requests
26.12. JMS Messaging
26.12.1. Configuration
26.12.2. Subscribing to a JMS Topic
26.12.3. Unsubscribing from a Topic
26.12.4. Tuning the Polling Process

Seam provides a convenient method of remotely accessing components from a web page, using AJAX (Asynchronous Javascript and XML). The framework for this functionality is provided with almost no up-front development effort - your components only require simple annotating to become accessible via AJAX. This chapter describes the steps required to build an AJAX-enabled web page, then goes on to explain the features of the Seam Remoting framework in more detail.

To use remoting, the Seam Resource servlet must first be configured in your web.xml file:


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

The next step is to import the necessary Javascript into your web page. There are a minimum of two scripts that must be imported. The first one contains all the client-side framework code that enables remoting functionality:


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

The second script contains the stubs and type definitions for the components you wish to call. It is generated dynamically based on the local interface of your components, and includes type definitions for all of the classes that can be used to call the remotable methods of the interface. The name of the script reflects the name of your component. For example, if you have a stateless session bean annotated with @Name("customerAction"), then your script tag should look like this:


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

If you wish to access more than one component from the same page, then include them all as parameters of your script tag:


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

Alternatively, you may use the s:remote tag to import the required Javascript. Separate each component or class name you wish to import with a comma:



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

Client-side interaction with your components is all performed via the Seam Javascript object. This object is defined in remote.js, and you'll be using it to make asynchronous calls against your component. It is split into two areas of functionality; Seam.Component contains methods for working with components and Seam.Remoting contains methods for executing remote requests. The easiest way to become familiar with this object is to start with a simple example.

Let's step through a simple example to see how the Seam object works. First of all, let's create a new Seam component called helloAction.

@Stateless

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

Take special note of the @WebRemote annotation, as it's required to make our method accessible via remoting:

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.

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 containning 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);

Or configure it 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:


// Only wait 1 second between receiving a poll response and sending the next poll request.
Seam.Remoting.pollInterval = 1;
  
// Wait up to 5 seconds on the server for new messages
Seam.Remoting.pollTimeout = 5;