Scopes and Data Stores in RWT

In a desktop environment, an instance of an application normally serves only a single user, runs in its own Java VM instance, and the operating system usually provides a user-specific storage on the file system. For RAP applications, things are very different.

A RAP application does not own the VM. It runs on a server and shares the VM with the servlet container, other applications, maybe even other RAP applications. There are multiple users accessing the application, each one with a separate UI, but all sharing the same classes. Moreover, a RAP application runs for a very long time. When a user logs in, the application is already running, and it continues to run when the user leaves. For that reason, it is necessary to distinguish several scopes when designing RAP applications.

Application Scope

There can be more than one RAP application running at the same time, e.g. on different network ports or different contexts paths. Every application has its own set of entry points, registered resources, and so on. All these things have application scope. If you have objects that should be accessible from everywhere in the application, they need to have application scope as well. RWT provides an application-scoped store for these objects, called application store, that can be accessed using RWT.getApplicationStore().

Session Scope

User session in RAP are built on top of the servlet container's session management. But in contrast to the underlying HTTP session, a user session in RAP spans exactly one execution of an entrypoint. As an example, when the browser is refreshed (usually by hitting F5) to start over, the user still has the same HTTP session, but a fresh RAP session. RAP also provides a session-scoped data store, the session store, accessible using RWT.getSessionStore(). The session store also provides access to the HTTP session. Note that the session store can not be accessed directly from a background thread. For working with session-unique singletons, see Session Singleton.

Session Timeout

A RAP session ends when the execution of an entrypoint is finished or when the underlying HTTP session times out. The timeout interval must be be configured with the servlet container. For web applications, this can be done in the web application's deployment descriptor (web.xml):

<session-config>
  <session-timeout>30</session-timeout>
</session-config>
  

The timeout value can also be changed programmatically on a per session basis:

RWT.getSessionStore().getHttpSession().setMaxInactiveInterval(<timeout in Seconds>);
  

Note that when using the RAP launcher, sessions never expire by default. To change this, adjust the timeout setting on the Main tab.

Session Cleanup

To cleanup session-scoped objects, a SessionStoreListener can be registered with the session store:

RWT.getSessionStore().addSessionStoreListener( new SessionStoreListener() {
  public void beforeDestroy( SessionStoreEvent event ) {
    // Perform cleanup        
  }
} );
   

Request Scope

A third scope in RAP spans the processing of a single request. There is also a request-scoped store available, called service store, which can be accessed using RWT.getServiceStore(). Normally, this scope is not relevant for RAP application development.

Persisting User Data

RWT provides a persistent data store called SettingStore, which can be accessed using RWT.getSettingStore(). The settings store uses a cookie to identify a returning user. By default, the cookie expires after 3 months, but may also be deleted by the user before then.

Data Stores (Overview)

Store Access Scope cleared
IApplicationStore RWT.getApplicationStore() application when application is stopped/restarted
ISessionStore RWT.getSessionStore() session when session expires
IServiceStore RWT.getServiceStore() request when HTTP response is sent to client
ISettingsStore RWT.getSettingStore() user (persistent) never, cookie lasts 3 month by default

Singletons and Static Fields

The classical singleton pattern suggests that a class has only one instance and keeps the reference to this instance in a static field. In RAP, this pattern is dangerous, because static values are kept in the class, which is shared among different user sessions and different applications. As an example, if a copy of the user's shopping cart is kept as a session singleton, this would work on the desktop, but in RAP, all users would share the same shopping cart instance.

Instead of using singletons and static fields, all data must be kept in the correct scope in RAP. RAP provides a helper class SessionSingletonBase for creating session-unique instances of a given class, so called “session singletons”. In the context of one user session SessionSingletonBase.getInstance( Class ) will always return the same object, but for different user sessions the returned instances will be different. The following code snippet illustrates this pattern:

public class MySessionSingleton {
  private MySessionSingleton() {
    // prevent instantiation from outside
  }

  public static MySessionSingleton getInstance() {
    return SessionSingletonBase.getInstance( MySessionSingleton.class );
  }

  // other methods ...
}
  

Access from a background thread

As with the session store, it is not possible to access session singletons from a background thread because background threads aren't bound to any specific user session. Any non-UI thread trying to access a session singleton will fail with an java.lang.IllegalStateException:

// INCORRECT
// will throw IllegalStateException: No context available ...
Runnable runnable = new Runnable() {
  public void run() {
    MySessionSingleton sessionSingleton = MySessionSingleton.getInstance();
    // do something with the session singleton
  }
};
new Thread( runnable ).start();

The solution is to run the code in question with a simulated context that can determine the user session based on the reference to the user's display. To do so, use the legendary method UICallBack.runNonUIThreadWithFakeContext(). This method will run a given runnable in with a context that is bound to the user session identified by the specified display, thus allowing the code to access session singletons:

// CORRECT
final Display display = Display.getCurrent();
final Runnable runnable = new Runnable() {
  public void run() {
    UICallBack.runNonUIThreadWithFakeContext( display, new Runnable() {
      public void run() {
        MySessionSingleton sessionSingleton = MySessionSingleton.getInstance();
        // do something with the session singleton
      }
    } );
  }
};
new Thread( runnable ).start();