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.
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()
.
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.
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.
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
}
} );
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.
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.
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 |
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 ...
}
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();