SeamFramework.orgCommunity Documentation

Chapter 53. Seam Remoting - Basic Features

53.1. Configuration
53.1.1. Dynamic type loading
53.2. The "Seam" object
53.2.1. A Hello World example
53.2.2. Seam.createBean
53.3. The Context
53.3.1. Setting and reading the Conversation ID
53.3.2. Remote calls within the current conversation scope
53.4. Working with Data types
53.4.1. Primitives / Basic Types
53.4.2. JavaBeans
53.4.3. Dates and Times
53.4.4. Enums
53.4.5. Collections
53.5. Debugging
53.6. Messages
53.7. Handling Exceptions
53.8. The Loading Message
53.8.1. Changing the message
53.8.2. Hiding the loading message
53.8.3. A Custom Loading Indicator
53.9. Controlling what data is returned
53.9.1. Constraining normal fields
53.9.2. Constraining Maps and Collections
53.9.3. Constraining objects of a specific type
53.9.4. Combining Constraints

Seam provides a convenient method of remotely accessing CDI beans 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 beans 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 Remoting servlet must first be configured in your web.xml file:


<servlet>
   <servlet-name>Remoting Servlet</servlet-name>
   <servlet-class>org.jboss.seam.remoting.Remoting</servlet-class>
   <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
   <servlet-name>Remoting Servlet</servlet-name>
   <url-pattern>/seam/resource/remoting/*</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>

By default, the client-side JavaScript is served in compressed form, with white space compacted and JavaScript comments removed. For a development environment, you may wish to use the uncompressed version of remote.js for debugging and testing purposes. To do this, simply add the compress=false parameter to the end of the url:


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

The second script that you need contains the stubs and type definitions for the beans you wish to call. It is generated dynamically based on the method signatures of your beans, and includes type definitions for all of the classes that can be used to call its remotable methods. The name of the script reflects the name of your bean. For example, if you have a named bean annotated with @Named, then your script tag should look like this (for a bean class called CustomerAction):


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

Otherwise, you can simply specify the fully qualified class name of the bean:


<script type="text/javascript" 
          src="seam/resource/remoting/interface.js?com.acme.myapp.CustomerAction"></script>

If you wish to access more than one bean 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>

Client-side interaction with your beans 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 bean. It contains methods for creating client-side bean objects and also 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 bean called helloAction:

@Named

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

Take note of the @WebRemote annotation on the sayHello() method in the above listing. This annotation makes the method accessible via the Remoting API. Besides this annotation, there's nothing else required on your bean to enable it for remoting.

Now for our web page - create a new JSF page and import the helloAction bean:


<script type="text/javascript" 
        src="seam/resource/remoting/interface.js?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.createBean("helloAction").sayHello(name, sayHelloCallback);
  }

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

   // ]]>
</script>

We're done! Deploy your application and open the page in a web browser. 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 the /examples/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.createBean("helloAction").sayHello(name, sayHelloCallback);

The first section of this line, Seam.createBean("helloAction") returns a proxy, or "stub" for our helloAction bean. We can invoke the methods of our bean 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 bean, passing in name as a parameter. The second parameter, sayHelloCallback isn't a parameter of our bean'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 Remoting Context contains additional information which is sent and received as part of a remoting request/response cycle. It currently contains the conversation ID and Call ID, and may be expanded to include other properties in the future.

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, set the Seam.debug property to true in Javascript:


Seam.debug = true;

If you want to write your own messages to the debug log, call Seam.log(message).

The Seam International module provides a Messages API that allows generation of view-independent messages. This is useful if you want to convey additional information to a user that is not returned directly from the result of a method invocation.

Using the Messages API is extremely easy. Simply add the Seam International libraries to your application (see the Seam International configuration chapter to learn how to do this), then inject the Messages object into your bean. The Messages object provides several methods for adding messages, see the Seam International documentation for more information. Here's a simple example showing how to create an info message (messages generally follow the same DEBUG, INFO, WARN, ERROR levels that a typical logging framework would provide):

import javax.inject.Inject;

import org.jboss.seam.international.status.Messages;
import org.jboss.seam.remoting.annotations.WebRemote;
public class HelloAction {    
    @Inject Messages messages;
    
    @WebRemote
    public String sayHello(String name) {
        messages.info("Invoked HelloAction.sayHello()");        
        return "Hello, " + name;
    }
}

After creating the message in your server-side code, you still need to write some client-side code to handle any messages that are returned by your remote invocations. Thankfully this is also simple, you just need to write a JavaScript handler function and assign it to Seam.messageHandler.

If any messages are returned from a remote method invocation, the message handler function will be invoked and passed a list of Message objects. These objects declare three methods for retrieving various properties of the message - getLevel() returns the message level (such as DEBUG, INFO, etc). The getTargets() method returns the targets of the message - these may be the ID's for specific user interface controls, which is helpful for conveying validation failures for certain field values. The getTargets() method may return null, if the message is not specific to any field value. Lastly, the getText() method returns the actual text of the message.

Here's a really simple example showing how you would display an alert box for any messages returned:

    function handleMessages(msgs) {      
      for (var i = 0; i < msgs.length; i++) {
        alert("Received message - Level: " + msgs[i].getLevel() + " Text: " + msgs[i].getText();
      }
    }
    
    Seam.messageHandler = handleMessages;

You can see the Messages API in action in the HelloWorld example. Simply choose the "Formal" option for the Formality, and "Localized (English)" for the Localization. Invoking this combination will cause a server-side message to be created, which you will then see in the Messages list at the top of the screen.

When invoking a remote bean method, it is possible to specify an exception handler which will process the response in the event of an exception during bean 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.createBean("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.createBean("helloAction").sayHello(name, null, exceptionHandler);

The exception object that is passed to the exception handler exposes two methods, getExceptionClass() which returns the name of the exception class that was thrown, and getMessage(), which returns the exception message which is produced by the exception thrown by the @WebRemote method.

It is also possible to register a global exception handler, which will be invoked if there is no exception handler defined for an individual invocation. By default, the global exception handler will display an alert message notifying the user that there was an exception - here's what the default exception handler looks like:

Seam.defaultExceptionHandler = function(exception) {
  alert("An exception has occurred while executing a remote request: " + exception.getExceptionClass() + ":" + exception.getMessage());
};

If you would like to provide your own global exception handler, then simply override the value of Seam.exceptionHandler with your own custom exception handler, as in the following example:

  function customExceptionHandler(exception) {
    alert("Uh oh, something bad has happened! [" + exception.getExceptionClass() + ":" + exception.getMessage() + "]");
  }    
  
  Seam.exceptionHandler = customExceptionHandler;
    

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:

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
}