Applications typically need to access some objects from practically all user
interface code, such as a user object, a business data model, or a database
connection. This data is typically initialized and managed in the application
class. Some such data is built-in into the Application
class, such as the locale.
You can access the application object from any user interface component using
the getApplication()
method. For example:
class MyApplication extends Application { UserData userData; public void init() { userData = new UserData(); } public UserData getUserData() { return userData; } } ... data = ((MyApplication)component.getApplication()).getUserData();
The basic problem in accessing session-global data is that the
getApplication()
method works only after the
component has been attached to the application. Before that, it returns
null
. This is the case in constructors of
components, such as a CustomComponent
:
class MyComponent extends CustomComponent { public MyComponent() { // This fails with NullPointerException Label label = new Label("Country: " + getApplication().getLocale().getCountry()); setCompositionRoot(label); } }
Using a static variable or a singleton implemented with such to give a global access to user session data is not possible, because static variables are global in the entire web application, not just the user session. This can be handy for communicating data between the concurrent sessions, but creates a problem within a session.
For example, the following would not work:
class MyApplication extends Application { static UserData userData; public void init() { userData = new UserData(); } public static UserData getUserData() { return userData; } }
The data would be shared by all users and be reinitialized every time a new user opens the application.
To get the application object or any other global data, you have the following solutions:
Pass a reference to the global data as a parameter.
Initialize components in attach()
method.
Store a reference to global data using the ThreadLocal Pattern.
Each solution is described in the following sections.
You can pass references to objects as parameters. This is the normal way in object-oriented programming.
class MyApplication extends Application { UserData userData; public void init() { Window mainWindow = new Window("My Window"); setMainWindow(mainWindow); userData = new UserData(); mainWindow.addComponent(new MyComponent(this)); } public UserData getUserData() { return userData; } } class MyComponent extends CustomComponent { public MyComponent(MyApplication app) { Label label = new Label("Name: " + app.getUserData().getName()); setCompositionRoot(label); } }
If you need the reference in other methods, you either have to pass it again as a parameter or store it in a member variable.
The problem with this solution is that practically all constructors in the application need to get a reference to the application object, and passing it further around in the classes is another hard task.
The attach()
method is called when the component
is attached to the application component through containment
hierarchy. The getApplication()
method always
works.
class MyComponent extends CustomComponent { public MyComponent() { // Must set a dummy root in constructor setCompositionRoot(new Label("")); } @Override public void attach() { Label label = new Label("Name: " + ((MyApplication)component.getApplication()) .getUserData().getName()); setCompositionRoot(label); } }
While this solution works, it is slightly messy. You may need to do some
initialization in the constructor, but any construction requiring the
global data must be done in the attach()
method.
Especially, CustomComponent
requires that the
setCompositionRoot()
method is called in the
constructor. If you can't create the actual composition root component in
the constructor, you need to use a temporary dummy root, as is done in the
example above.
Using getApplication()
also needs casting if you
want to use methods defined in your application class.
The ThreadLocal pattern gives a solution to the global access problem by solving two sub-problems.
As the first problem, the servlet container processes requests for many users (sessions) sequentially, so if a static variable is set in a request belonging one user, it could be read or re-set by the next incoming request belonging to another user. This can be solved by setting the global reference at the beginning of each HTTP request to point to data of the current user, as illustrated in Figure 12.11.
Figure 12.11. Switching a static (or ThreadLocal) reference during sequential processing of requests
You can implement such switching either with the
TransactionListener
or
HttpServletRequestListener
interface by setting the
reference in transactionStart()
or
onRequestStart()
, respectively. We use the former
interface in the example code in this section, as the latter interface has
to be implemented in the application class.
The second problem is that servlet containers typically do thread pooling
with multiple worker threads that process requests. Therefore, setting a
static reference would change it in all threads running concurrently,
possibly just when another thread is processing a request for another
user. The solution is to store the reference in a thread-local variable
instead of a static. You can do so by using the
ThreadLocal
class in Java for the switch reference.
Notice that if you use a TransactionListener
, the
listeners are attached to the web application context (in practice a user
session), not the application instance. The problem is that an application
context can have multiple different Vaadin applications that share the
same user session. If two of these applications add a transaction listener
to the context to listen for requests, both are called and without any
checks they would both set the reference to themselves. Therefore, the
application data object needs to know which application it belongs to and
check that when the transaction begins and ends. Using the
HttpServletRequestListener
frees you from these
checks.
While you may not absolutely need to clear the reference in
transactionEnd()
, you are probably on the safer
side if you do. Setting such unneeded references to
null
can help avoid memory leaks and it could also be a
good security precaution not to leave a reference to session data so that
it could be seen by another user session in the next request.
We end up with the following code. As we put the application data to a
class separate from the application class, we have to make it a
TransactionListener
.
/** Holds data for one user session. */ public class AppData implements TransactionListener, Serializable { private ResourceBundle bundle; private Locale locale; // Current locale private String userData; // Trivial data model for the user private Application app; // For distinguishing between apps private static ThreadLocal<AppData> instance = new ThreadLocal<AppData>(); public AppData(Application app) { this.app = app; // It's usable from now on in the current request instance.set(this); } @Override public void transactionStart(Application application, Object transactionData) { // Set this data instance of this application // as the one active in the current thread. if (this.app == application) instance.set(this); } @Override public void transactionEnd(Application application, Object transactionData) { // Clear the reference to avoid potential problems if (this.app == application) instance.set(null); } public static void initLocale(Locale locale, String bundleName) { instance.get().locale = locale; instance.get().bundle = ResourceBundle.getBundle(bundleName, locale); } public static Locale getLocale() { return instance.get().locale; } public static String getMessage(String msgId) { return instance.get().bundle.getString(msgId); } public static String getUserData() { return instance.get().userData; } public static void setUserData(String userData) { instance.get().userData = userData; } }
We can then use it in the application as follows. Observe that we do not
have a reference to the application object in the constructor of the
MyComponent
class.
/** * We can now nicely access the session-global data * in the constuctor of this class. */ class MyComponent extends CustomComponent { public MyComponent() { VerticalLayout layout = new VerticalLayout(); // Get stuff from the application data object layout.addComponent(new Label("Hello, " + AppData.getUserData())); layout.addComponent(new Label("Your locale is " + AppData.getLocale().getDisplayLanguage())); layout.addComponent(new Button( AppData.getMessage(MyAppCaptions.CancelKey))); setCompositionRoot(layout); } } /** The application class. */ public class ThreadLocalApplication extends Application { public void init() { Window main = new Window("Hello window"); setMainWindow(main); // Create the application data instance AppData sessionData = new AppData(this); // Register it as a listener in the application context getContext().addTransactionListener(sessionData); // Initialize the session-global data AppData.initLocale(getLocale(), MyAppCaptions.class.getName()); // Also set the user data model AppData.setUserData("Billy"); // Now, we do not pass this application object // in the constructor, so it couldn't access the // app data otherwise. main.addComponent(new MyComponent()); } }