Chapter 7. Remoting

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.

7.1. Configuration

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

          
  <servlet>
    <servlet-name>Seam Remoting</servlet-name>
    <servlet-class>org.jboss.seam.remoting.SeamRemotingServlet</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>Seam Remoting</servlet-name>
    <url-pattern>/seam/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/remoting/resource/remote.js">
    <!--
    // This space intentionally left blank
    //-->
  </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/remoting/interface.js?customerAction">
    <!--
    // This space intentionally left blank
    //-->
  </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/remoting/interface.js?customerAction&accountAction">
    <!--
    // This space intentionally left blank
    //-->
  </script>
        
        

7.2. The "Seam" object

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.

7.2.1. A Hello World 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")
  @Scope(SESSION)
  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 following scripts:

          
  <script type="text/javascript" src="seam/remoting/resource/remote.js">
    <!--
    // This space intentionally left blank
    //-->
  </script>

  <script type="text/javascript" src="seam/remoting/interface.js?helloAction">
    <!--
    // This space intentionally left blank
    //-->
  </script>
          
        

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.

7.2.2. Seam.Component

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.

7.2.2.1. Seam.Component.newInstance()

Use this method to create a new instance of an entity or Javabean component. The object returned by this method will have the same getter/setter methods as its server-side counterpart, or alternatively if you wish you can access its fields directly. Take the following Seam entity component for example:

  @Name("customer")
  @Entity
  public class Customer implements Serializable
  {
    private Integer customerId;
    private String firstName;
    private String lastName;
    
    @Column public Integer getCustomerId() { 
      return customerId; 
    }
    
    public void setCustomerId(Integer customerId} { 
      this.customerId = customerId; 
    }
    
    @Column public String getFirstName() { 
      return firstName; 
    }
    
    public void setFirstName(String firstName) {
      this.firstName = firstName; 
    }
    
    @Column public String getLastName() {
      return lastName;
    }
    
    public void setLastName(String lastName) {
      this.lastName = lastName;
    }
  }
          

To create a client-side Customer you would write the following code:

  var customer = Seam.Component.newInstance("customer");
          

Then from here you can set the fields of the customer object:

  customer.setFirstName("John");
  // Or you can set the fields directly
  customer.lastName = "Smith";
          

7.2.2.2. Seam.Component.getInstance()

The getInstance() method is used to get a reference to a Seam session bean component stub, which can then be used to remotely execute methods against your component. This method returns a singleton for the specified component, so calling it twice in a row with the same component name will return the same instance of the component.

To continue our example from before, if we have created a new customer and we now wish to save it, we would pass it to the saveCustomer() method of our customerAction component:

  Seam.Component.getInstance("customerAction").saveCustomer(customer);          
          

7.2.2.3. Seam.Component.getComponentName()

Passing an object into this method will return its component name if it is a component, or null if it is not.

  if (Seam.Component.getComponentName(instance) == "customer")
    alert("Customer");
  else if (Seam.Component.getComponentName(instance) == "staff")
    alert("Staff member");          
          

7.2.3. Seam.Remoting

Most of the client side functionality for Seam Remoting is contained within the Seam.Remoting object. While you shouldn't need to directly call most of its methods, there are a couple of important ones worth mentioning.

7.2.3.1. Seam.Remoting.createType()

If your application contains or uses Javabean classes that aren't Seam components, you may need to create these types on the client side to pass as parameters into your component method. Use the createType() method to create an instance of your type. Pass in the fully qualified Java class name as a parameter:

  var widget = Seam.Remoting.createType("com.acme.widgets.MyWidget");          
          

7.2.3.2. Seam.Remoting.getTypeName()

This method is the equivalent of Seam.Component.getComponentName() but for non-component types. It will return the name of the type for an object instance, or null if the type is not known. The name is the fully qualified name of the type's Java class.

7.3. Client Interfaces

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

        
  <script type="text/javascript" src="seam/remoting/interface.js?customerAction">
    <!--
    // This space intentionally left blank
    //-->
  </script>
        
      

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.

7.4. The Context

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.

7.4.1. Setting and reading the Conversation ID

If you intend on using remote calls within the scope of a conversation then you need to be able to read or set the conversation ID in the Seam Remoting Context. To read the conversation ID after making a remote request call Seam.Remoting.getContext().getConversationId(). To set the conversation ID before making a request, call Seam.Remoting.getContext().setConversationId().

If the conversation ID hasn't been explicitly set with Seam.Remoting.getContext().setConversationId(), then it will be automatically assigned the first valid conversation ID that is returned by any remoting call. If you are working with multiple conversations within your page, then you may need to explicitly set the conversation ID before each call. If you are working with just a single conversation, then you don't need to do anything special.

7.5. Batch Requests

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.

7.6. Working with Data types

7.6.1. Primitives / Basic Types

This section describes the support for basic data types. On the server side these values are generally compatible with either their primitive type or their corresponding wrapper class.

7.6.1.1. String

Simply use Javascript String objects when setting String parameter values.

7.6.1.2. Number

There is support for all number types supported by Java. On the client side, number values are always serialized as their String representation and then on the server side they are converted to the correct destination type. Conversion into either a primitive or wrapper type is supported for Byte, Double, Float, Integer, Long and Short types.

7.6.1.3. Boolean

Booleans are represented client side by Javascript Boolean values, and server side by a Java boolean.

7.6.2. JavaBeans

In general these will be either Seam entity or JavaBean components, or some other non-component class. Use the appropriate method (either Seam.Component.newInstance() for Seam components or Seam.Remoting.createType() for everything else) to create a new instance of the object.

It is important to note that only objects that are created by either of these two methods should be used as parameter values, where the parameter is not one of the other valid types mentioned anywhere else in this section. In some situations you may have a component method where the exact parameter type cannot be determined, such as:

  @Name("myAction")
  public class MyAction implements MyActionLocal {
    public void doSomethingWithObject(Object obj) {
      // code
    }
  }
        

In this case you might want to pass in an instance of your myWidget component, however the interface for myAction won't include myWidget as it is not directly referenced by any of its methods. To get around this, MyWidget needs to be explicitly imported:

                  
  <script type="text/javascript" src="seam/remoting/interface.js?myAction&myWidget">
    <!--
    // This space intentionally left blank
    //-->
  </script>        
          
        

This will then allow a myWidget object to be created with Seam.Component.newInstance("myWidget"), which can then be passed to myAction.doSomethingWithObject().

7.6.3. Dates and Times

Date values are serialized into a String representation that is accurate to the millisecond. On the client side, use a Javascript Date object to work with date values. On the server side, use any java.util.Date (or descendent, such as java.sql.Date or java.sql.Timestamp class.

7.6.4. Enums

On the client side, enums are treated the same as Strings. When setting the value for an enum parameter, simply use the String representation of the enum. Take the following component as an example:

  @Name("paintAction")
  public class paintAction implements paintLocal {
    public enum Color {red, green, blue, yellow, orange, purple};

    public void paint(Color color) {
      // code
    }    
  }            
        

To call the paint() method with the color red, pass the parameter value as a String literal:

  Seam.Component.getInstance("paintAction").paint("red");
        

The inverse is also true - that is, if a component method returns an enum parameter (or contains an enum field anywhere in the returned object graph) then on the client-side it will be represented as a String.

7.6.5. Collections

7.6.5.1. Bags

Bags cover all collection types including arrays, collections, lists, sets, (but excluding Maps - see the next section for those), and are implemented client-side as a Javascript array. When calling a component method that accepts one of these types as a parameter, your parameter should be a Javascript array. If a component method returns one of these types, then the return value will also be a Javascript array. The remoting framework is clever enough on the server side to convert the bag to an appropriate type for the component method call.

7.6.5.2. Maps

As there is no native support for Maps within Javascript, a simple Map implementation is provided with the Seam Remoting framework. To create a Map which can be used as a parameter to a remote call, create a new Seam.Remoting.Map object:

  var map = new Seam.Remoting.Map();          
          

This Javascript implementation provides basic methods for working with Maps: size(), isEmpty(), keySet(), values(), get(key), put(key, value), remove(key) and contains(key). Each of these methods are equivalent to their Java counterpart. Where the method returns a collection, such as keySet() and values(), a Javascript Array object will be returned that contains the key or value objects (respectively).

7.7. Debugging

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, execute the setDebug() method:

  Seam.Remoting.setDebug(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).

7.8. The Loading Message

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.

7.8.1. Changing the message

To change the message from the default "Please Wait..." to something different, set the value of Seam.Remoting.loadingMessage:

  Seam.Remoting.loadingMessage = "Loading...";        
        

7.8.2. Hiding the loading message

To completely suppress the display of the loading message, override the implementation of displayLoadingMessage() and hideLoadingMessage() with functions that instead do nothing:

  // don't display the loading indicator
  Seam.Remoting.displayLoadingMessage = function() {};
  Seam.Remoting.hideLoadingMessage = function() {};        
        

7.8.3. A Custom Loading Indicator

It is also possible to override the loading indicator to display an animated icon, or anything else that you want. To do this override the displayLoadingMessage() and hideLoadingMessage() messages with your own implementation:

  Seam.Remoting.displayLoadingMessage = function() {
    // Write code here to display the indicator
  };
  
  Seam.Remoting.hideLoadingMessage = function() {
    // Write code here to hide the indicator
  };
        

7.9. JMS Messaging

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.

7.9.1. Configuration

Before you can subscribe to a JMS topic, you must first configure a list of the topics that can be subscribed to by Seam Remoting. List the topics under org.jboss.seam.remoting.messaging.subscriptionRegistry.allowedTopics in seam.properties, web.xml or components.xml.

<component name="org.jboss.seam.remoting.messaging.subscriptionRegistry">
    <property name="allowedTopics">chatroomTopic, stockTickerTopic</property>
</component>

7.9.2. Subscribing to a JMS Topic

The following example demonstrates how to subscribe to a JMS Topic:

  function subscriptionCallback(message)
  {
    if (message instanceof Seam.Remoting.TextMessage)
      alert("Received message: " + message.getText());
  }        
        
  Seam.Remoting.subscribe("topicName", subscriptionCallback);
        

The Seam.Remoting.subscribe() method accepts two parameters, the first being the name of the JMS Topic to subscribe to, the second being the callback function to invoke when a message is received.

There are two types of messages supported, Text messages and Object messages. If you need to test for the type of message that is passed to your callback function you can use the instanceof operator to test whether the message is a Seam.Remoting.TextMessage or Seam.Remoting.ObjectMessage. A TextMessage contains the text value in its text field (or alternatively call getText() on it), while an ObjectMessage contains its object value in its object field (or call its getObject() method).

7.9.3. Unsubscribing from a Topic

To unsubscribe from a topic, call Seam.Remoting.unsubscribe() and pass in the topic name:

  Seam.Remoting.unsubscribe("topicName");        
        

7.9.4. Tuning the Polling Process

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.

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:

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