/* @ITMillApache2LicenseForJavaFiles@ */ package com.vaadin; import java.io.Serializable; import java.net.SocketException; import java.net.URL; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; import java.util.EventListener; import java.util.EventObject; import java.util.Hashtable; import java.util.Iterator; import java.util.LinkedList; import java.util.Locale; import java.util.Properties; import java.util.logging.Level; import java.util.logging.Logger; import com.vaadin.service.ApplicationContext; import com.vaadin.terminal.ApplicationResource; import com.vaadin.terminal.DownloadStream; import com.vaadin.terminal.ErrorMessage; import com.vaadin.terminal.ParameterHandler; import com.vaadin.terminal.SystemError; import com.vaadin.terminal.Terminal; import com.vaadin.terminal.URIHandler; import com.vaadin.terminal.VariableOwner; import com.vaadin.terminal.gwt.server.ChangeVariablesErrorEvent; import com.vaadin.terminal.gwt.server.PortletApplicationContext; import com.vaadin.terminal.gwt.server.WebApplicationContext; import com.vaadin.ui.AbstractComponent; import com.vaadin.ui.Window; /** *
* Base class required for all Vaadin applications. This class provides all the * basic services required by Vaadin. These services allow external discovery * and manipulation of the user, {@link com.vaadin.ui.Window windows} and * themes, and starting and stopping the application. *
* *
* As mentioned, all Vaadin applications must inherit this class. However, this
* is almost all of what one needs to do to create a fully functional
* application. The only thing a class inheriting the Application
* needs to do is implement the init
method where it creates the
* windows it needs to perform its function. Note that all applications must
* have at least one window: the main window. The first unnamed window
* constructed by an application automatically becomes the main window which
* behaves just like other windows with one exception: when accessing windows
* using URLs the main window corresponds to the application URL whereas other
* windows correspond to a URL gotten by catenating the window's name to the
* application URL.
*
* See the class com.vaadin.demo.HelloWorld
for a simple example of
* a fully working application.
*
* Window access. Application
provides methods to
* list, add and remove the windows it contains.
*
* Execution control. This class includes method to start and * finish the execution of the application. Being finished means basically that * no windows will be available from the application anymore. *
* ** Theme selection. The theme selection process allows a theme * to be specified at three different levels. When a window's theme needs to be * found out, the window itself is queried for a preferred theme. If the window * does not prefer a specific theme, the application containing the window is * queried. If neither the application prefers a theme, the default theme for * the {@link com.vaadin.terminal.Terminal terminal} is used. The terminal * always defines a default theme. *
* * @author IT Mill Ltd. * @version * @VERSION@ * @since 3.0 */ @SuppressWarnings("serial") public abstract class Application implements URIHandler, Terminal.ErrorListener, Serializable { private final static Logger logger = Logger.getLogger(Application.class .getName()); /** * Id use for the next window that is opened. Access to this must be * synchronized. */ private int nextWindowId = 1; /** * Application context the application is running in. */ private ApplicationContext context; /** * The current user ornull
if no user has logged in.
*/
private Object user;
/**
* Mapping from window name to window instance.
*/
private final Hashtable
* Gets a window by name. Returns null
if the application is
* not running or it does not contain a window corresponding to the name.
*
* All windows can be referenced by their names in url
* http://host:port/foo/bar/
where
* http://host:port/foo/
is the application url as returned by
* getURL() and bar
is the name of the window.
*
* One should note that this method can, as a side effect create new windows * if needed by the application. This can be achieved by overriding the * default implementation. *
* ** If for some reason user opens another window with same url that is * already open, name is modified by adding "_12345678" postfix to the name, * where 12345678 is a random number. One can decide to create another * window-object for those windows (recommended) or to discard the postfix. * If the user has two browser windows pointing to the same window-object on * server, synchronization errors are likely to occur. *
* *
* If no browser-level windowing is used, all defaults are fine and this
* method can be left as is. In case browser-level windows are needed, it is
* recommended to create new window-objects on this method from their names
* if the super.getWindow() does not find existing windows. See below for
* implementation example:
*
// If we already have the requested window, use it
Window w = super.getWindow(name);
if (w == null) {
// If no window found, create it
w = new Window(name);
// set windows name to the one requested
w.setName(name);
// add it to this application
addWindow(w);
// ensure use of window specific url
w.open(new ExternalResource(w.getURL().toString()));
// add some content
w.addComponent(new Label("Test window"));
}
return w;
* Note that all returned Window objects must be added to * this application instance. * *
* The method should return null if the window does not exists (and is not * created as a side-effect) or if the application is not running anymore. *
* * @param name * the name of the window. * @return the window associated with the given URI ornull
*/
public Window getWindow(String name) {
// For closed app, do not give any windows
if (!isRunning()) {
return null;
}
// Gets the window by name
final Window window = windows.get(name);
return window;
}
/**
* Adds a new window to the application.
*
* * This implicitly invokes the * {@link com.vaadin.ui.Window#setApplication(Application)} method. *
* *
* Note that all application-level windows can be accessed by their names in
* url http://host:port/foo/bar/
where
* http://host:port/foo/
is the application url as returned by
* getURL() and bar
is the name of the window. Also note that
* not all windows should be added to application - one can also add windows
* inside other windows - these windows show as smaller windows inside those
* windows.
*
Window
to add. If the name of the window
* is null
, an unique name is automatically given
* for the window.
* @throws IllegalArgumentException
* if a window with the same name as the new window already
* exists in the application.
* @throws NullPointerException
* if the given Window
is null
.
*/
public void addWindow(Window window) throws IllegalArgumentException,
NullPointerException {
// Nulls can not be added to application
if (window == null) {
return;
}
// Check that one is not adding a sub-window to application
if (window.getParent() != null) {
throw new IllegalArgumentException(
"Window was already added inside another window"
+ " - it can not be added to application also.");
}
// Gets the naming proposal from window
String name = window.getName();
// Checks that the application does not already contain
// window having the same name
if (name != null && windows.containsKey(name)) {
// If the window is already added
if (window == windows.get(name)) {
return;
}
// Otherwise complain
throw new IllegalArgumentException("Window with name '"
+ window.getName()
+ "' is already present in the application");
}
// If the name of the window is null, the window is automatically named
if (name == null) {
boolean accepted = false;
while (!accepted) {
// Try another name
synchronized (this) {
name = String.valueOf(nextWindowId);
nextWindowId++;
}
if (!windows.containsKey(name)) {
accepted = true;
}
}
window.setName(name);
}
// Adds the window to application
windows.put(name, window);
window.setApplication(this);
fireWindowAttachEvent(window);
// If no main window is set, declare the window to be main window
if (getMainWindow() == null) {
mainWindow = window;
}
}
/**
* Send information to all listeners about new Windows associated with this
* application.
*
* @param window
*/
private void fireWindowAttachEvent(Window window) {
// Fires the window attach event
if (windowAttachListeners != null) {
final Object[] listeners = windowAttachListeners.toArray();
final WindowAttachEvent event = new WindowAttachEvent(window);
for (int i = 0; i < listeners.length; i++) {
((WindowAttachListener) listeners[i]).windowAttached(event);
}
}
}
/**
* Removes the specified window from the application.
*
* * Removing the main window of the Application also sets the main window to * null. One must another window to be the main window after this with * {@link #setMainWindow(Window)}. *
* ** Note that removing window from the application does not close the browser * window - the window is only removed from the server-side. *
* * @param window * the window to be removed. */ public void removeWindow(Window window) { if (window != null && windows.contains(window)) { // Removes the window from application windows.remove(window.getName()); // If the window was main window, clear it if (getMainWindow() == window) { setMainWindow(null); } // Removes the application from window if (window.getApplication() == this) { window.setApplication(null); } fireWindowDetachEvent(window); } } private void fireWindowDetachEvent(Window window) { // Fires the window detach event if (windowDetachListeners != null) { final Object[] listeners = windowDetachListeners.toArray(); final WindowDetachEvent event = new WindowDetachEvent(window); for (int i = 0; i < listeners.length; i++) { ((WindowDetachListener) listeners[i]).windowDetached(event); } } } /** * Gets the user of the application. * ** Vaadin doesn't define of use user object in any way - it only provides * this getter and setter methods for convenience. The user is any object * that has been stored to the application with {@link #setUser(Object)}. *
* * @return the User of the application. */ public Object getUser() { return user; } /** ** Sets the user of the application instance. An application instance may * have a user associated to it. This can be set in login procedure or * application initialization. *
** A component performing the user login procedure can assign the user * property of the application and make the user object available to other * components of the application. *
** Vaadin doesn't define of use user object in any way - it only provides * getter and setter methods for convenience. The user reference stored to * the application can be read with {@link #getUser()}. *
* * @param user * the new user. */ public void setUser(Object user) { final Object prevUser = this.user; if (user == prevUser || (user != null && user.equals(prevUser))) { return; } this.user = user; if (userChangeListeners != null) { final Object[] listeners = userChangeListeners.toArray(); final UserChangeEvent event = new UserChangeEvent(this, user, prevUser); for (int i = 0; i < listeners.length; i++) { ((UserChangeListener) listeners[i]) .applicationUserChanged(event); } } } /** * Gets the URL of the application. * ** This is the URL what can be entered to a browser window to start the * application. Navigating to the application URL shows the main window ( * {@link #getMainWindow()}) of the application. Note that the main window * can also be shown by navigating to the window url ( * {@link com.vaadin.ui.Window#getURL()}). *
* * @return the application's URL. */ public URL getURL() { return applicationUrl; } /** * Ends the Application. * ** In effect this will cause the application stop returning any windows when * asked. When the application is closed, its state is removed from the * session and the browser window is redirected to the application logout * url set with {@link #setLogoutURL(String)}. If the logout url has not * been set, the browser window is reloaded and the application is * restarted. *
* . */ public void close() { applicationIsRunning = false; } /** * Starts the application on the given URL. * ** This method is called by Vaadin framework when a user navigates to the * application. After this call the application corresponds to the given URL * and it will return windows when asked for them. There is no need to call * this method directly. *
* ** Application properties are defined by servlet configuration object * {@link javax.servlet.ServletConfig} and they are overridden by * context-wide initialization parameters * {@link javax.servlet.ServletContext}. *
* * @param applicationUrl * the URL the application should respond to. * @param applicationProperties * the Application properties as specified by the servlet * configuration. * @param context * the context application will be running in. * */ public void start(URL applicationUrl, Properties applicationProperties, ApplicationContext context) { this.applicationUrl = applicationUrl; properties = applicationProperties; this.context = context; init(); applicationIsRunning = true; } /** * Tests if the application is running or if it has been finished. * ** Application starts running when its * {@link #start(URL, Properties, ApplicationContext)} method has been * called and stops when the {@link #close()} is called. *
* * @returntrue
if the application is running,
* false
if not.
*/
public boolean isRunning() {
return applicationIsRunning;
}
/**
* Gets the set of windows contained by the application.
*
* * Note that the returned set of windows can not be modified. *
* * @return the Unmodifiable collection of windows. */ public Collection
* Main initializer of the application. The init
method is
* called by the framework when the application is started, and it should
* perform whatever initialization operations the application needs, such as
* creating windows and adding components to them.
*
null
is returned.
*
* @return the name of the application's theme.
*/
public String getTheme() {
return theme;
}
/**
* Sets the application's theme.
*
* Note that this theme can be overridden in the the application level
* windows with {@link com.vaadin.ui.Window#setTheme(String)}. Setting theme
* to be null
selects the default theme. For the available
* theme names, see the contents of the VAADIN/themes directory.
*
* The main window is the window attached to the application URL ( * {@link #getURL()}) and thus which is show by default to the user. *
** Note that each application must have at least one main window. *
* * @return the main window. */ public Window getMainWindow() { return mainWindow; } /** ** Sets the mainWindow. If the main window is not explicitly set, the main * window defaults to first created window. Setting window as a main window * of this application also adds the window to this application. *
* * @param mainWindow * the mainWindow to set. */ public void setMainWindow(Window mainWindow) { addWindow(mainWindow); this.mainWindow = mainWindow; } /** * Returns an enumeration of all the names in this application. * ** See {@link #start(URL, Properties, ApplicationContext)} how properties * are defined. *
* * @return an enumeration of all the keys in this property list, including * the keys in the default property list. * */ public Enumeration> getPropertyNames() { return properties.propertyNames(); } /** * Searches for the property with the specified name in this application. * This method returnsnull
if the property is not found.
*
* See {@link #start(URL, Properties, ApplicationContext)} how properties
* are defined.
*
* @param name
* the name of the property.
* @return the value in this property list with the specified key value.
*/
public String getProperty(String name) {
return properties.getProperty(name);
}
/**
* Adds new resource to the application. The resource can be accessed by the
* user of the application.
*
* @param resource
* the resource to add.
*/
public void addResource(ApplicationResource resource) {
// Check if the resource is already mapped
if (resourceKeyMap.containsKey(resource)) {
return;
}
// Generate key
final String key = String.valueOf(++lastResourceKeyNumber);
// Add the resource to mappings
resourceKeyMap.put(resource, key);
keyResourceMap.put(key, resource);
}
/**
* Removes the resource from the application.
*
* @param resource
* the resource to remove.
*/
public void removeResource(ApplicationResource resource) {
final Object key = resourceKeyMap.get(resource);
if (key != null) {
resourceKeyMap.remove(resource);
keyResourceMap.remove(key);
}
}
/**
* Gets the relative uri of the resource. This method is intended to be
* called only be the terminal implementation.
*
* This method can only be called from within the processing of a UIDL
* request, not from a background thread.
*
* @param resource
* the resource to get relative location.
* @return the relative uri of the resource or null if called in a
* background thread
*
* @deprecated this method is intended to be used by the terminal only. It
* may be removed or moved in the future.
*/
@Deprecated
public String getRelativeLocation(ApplicationResource resource) {
// Gets the key
final String key = resourceKeyMap.get(resource);
// If the resource is not registered, return null
if (key == null) {
return null;
}
return context.generateApplicationResourceURL(resource, key);
}
/**
* Application URI handling hub.
*
* * This method gets called by terminal. It has lots of duties like to pass * uri handler to proper uri handlers registered to windows etc. *
* ** In most situations developers should NOT OVERRIDE this method. Instead * developers should implement and register uri handlers to windows. *
* * @deprecated this method is called be the terminal implementation only and * might be removed or moved in the future. Instead of * overriding this method, add your {@link URIHandler} to a top * level {@link Window} (eg. * getMainWindow().addUriHanler(handler) instead. */ @Deprecated public DownloadStream handleURI(URL context, String relativeUri) { if (this.context.isApplicationResourceURL(context, relativeUri)) { // Handles the resource request final String key = this.context.getURLKey(context, relativeUri); final ApplicationResource resource = keyResourceMap.get(key); if (resource != null) { DownloadStream stream = resource.getStream(); if (stream != null) { stream.setCacheTime(resource.getCacheTime()); return stream; } else { return null; } } else { // Resource requests override uri handling return null; } } else { return null; } } /** * Gets the default locale for this application. * * By default this is the preferred locale of the user using the * application. In most cases it is read from the browser defaults. * * @return the locale of this application. */ public Locale getLocale() { if (locale != null) { return locale; } return Locale.getDefault(); } /** * Sets the default locale for this application. * * By default this is the preferred locale of the user using the * application. In most cases it is read from the browser defaults. * * @param locale * the Locale object. * */ public void setLocale(Locale locale) { this.locale = locale; } /** ** An event that characterizes a change in the current selection. *
* Application user change event sent when the setUser is called to change * the current user of the application. * * @version * @VERSION@ * @since 3.0 */ public class UserChangeEvent extends java.util.EventObject { /** * New user of the application. */ private final Object newUser; /** * Previous user of the application. */ private final Object prevUser; /** * Constructor for user change event. * * @param source * the application source. * @param newUser * the new User. * @param prevUser * the previous User. */ public UserChangeEvent(Application source, Object newUser, Object prevUser) { super(source); this.newUser = newUser; this.prevUser = prevUser; } /** * Gets the new user of the application. * * @return the new User. */ public Object getNewUser() { return newUser; } /** * Gets the previous user of the application. * * @return the previous Vaadin user, if user has not changed ever on * application it returnsnull
*/
public Object getPreviousUser() {
return prevUser;
}
/**
* Gets the application where the user change occurred.
*
* @return the Application.
*/
public Application getApplication() {
return (Application) getSource();
}
}
/**
* The UserChangeListener
interface for listening application
* user changes.
*
* @version
* @VERSION@
* @since 3.0
*/
public interface UserChangeListener extends EventListener, Serializable {
/**
* The applicationUserChanged
method Invoked when the
* application user has changed.
*
* @param event
* the change event.
*/
public void applicationUserChanged(Application.UserChangeEvent event);
}
/**
* Adds the user change listener.
*
* This allows one to get notification each time {@link #setUser(Object)} is
* called.
*
* @param listener
* the user change listener to add.
*/
public void addListener(UserChangeListener listener) {
if (userChangeListeners == null) {
userChangeListeners = new LinkedListnull
, the application is closed normally as defined by the
* application running environment.
* * Desktop application just closes the application window and * web-application redirects the browser to application main URL. *
* * @return the URL. */ public String getLogoutURL() { return logoutURL; } /** * Sets the URL user is redirected to on application close. If the URL is *null
, the application is closed normally as defined by the
* application running environment: Desktop application just closes the
* application window and web-application redirects the browser to
* application main URL.
*
* @param logoutURL
* the logoutURL to set.
*/
public void setLogoutURL(String logoutURL) {
this.logoutURL = logoutURL;
}
/**
* Gets the SystemMessages for this application. SystemMessages are used to
* notify the user of various critical situations that can occur, such as
* session expiration, client/server out of sync, and internal server error.
*
* You can customize the messages by "overriding" this method and returning
* {@link CustomizedSystemMessages}. To "override" this method, re-implement
* this method in your application (the class that extends
* {@link Application}). Even though overriding static methods is not
* possible in Java, Vaadin selects to call the static method from the
* subclass instead of the original {@link #getSystemMessages()} if such a
* method exists.
*
* @return the SystemMessages for this application
*/
public static SystemMessages getSystemMessages() {
return DEFAULT_SYSTEM_MESSAGES;
}
/**
*
* Invoked by the terminal on any exception that occurs in application and
* is thrown by the setVariable
to the terminal. The default
* implementation sets the exceptions as ComponentErrors
to the
* component that initiated the exception and prints stack trace to standard
* error stream.
*
* You can safely override this method in your application in order to * direct the errors to some other destination (for example log). *
* * @param event * the change event. * @see com.vaadin.terminal.Terminal.ErrorListener#terminalError(com.vaadin.terminal.Terminal.ErrorEvent) */ public void terminalError(Terminal.ErrorEvent event) { final Throwable t = event.getThrowable(); if (t instanceof SocketException) { // Most likely client browser closed socket logger.info("SocketException in CommunicationManager." + " Most likely client (browser) closed socket."); return; } // Finds the original source of the error/exception Object owner = null; if (event instanceof VariableOwner.ErrorEvent) { owner = ((VariableOwner.ErrorEvent) event).getVariableOwner(); } else if (event instanceof URIHandler.ErrorEvent) { owner = ((URIHandler.ErrorEvent) event).getURIHandler(); } else if (event instanceof ParameterHandler.ErrorEvent) { owner = ((ParameterHandler.ErrorEvent) event).getParameterHandler(); } else if (event instanceof ChangeVariablesErrorEvent) { owner = ((ChangeVariablesErrorEvent) event).getComponent(); } // Shows the error in AbstractComponent if (owner instanceof AbstractComponent) { if (t instanceof ErrorMessage) { ((AbstractComponent) owner).setComponentError((ErrorMessage) t); } else { ((AbstractComponent) owner) .setComponentError(new SystemError(t)); } } // also print the error on console logger.log(Level.SEVERE, "Terminal error:", t); } /** * Gets the application context. ** The application context is the environment where the application is * running in. The actual implementation class of may contains quite a lot * more functionality than defined in the {@link ApplicationContext} * interface. *
** By default, when you are deploying your application to a servlet * container, the implementation class is {@link WebApplicationContext} - * you can safely cast to this class and use the methods from there. When * you are deploying your application as a portlet, context implementation * is {@link PortletApplicationContext}. *
* * @return the application context. */ public ApplicationContext getContext() { return context; } /** * Override this method to return correct version number of your * Application. Version information is delivered for example to Testing * Tools test results. By default this returns a string "NONVERSIONED". * * @return version string */ public String getVersion() { return "NONVERSIONED"; } /** * Gets the application error handler. * * The default error handler is the application itself. * * @return Application error handler */ public Terminal.ErrorListener getErrorHandler() { return errorHandler; } /** * Sets the application error handler. * * The default error handler is the application itself. By overriding this, * you can redirect the error messages to your selected target (log for * example). * * @param errorHandler */ public void setErrorHandler(Terminal.ErrorListener errorHandler) { this.errorHandler = errorHandler; } /** * Contains the system messages used to notify the user about various * critical situations that can occur. ** Customize by overriding the static * {@link Application#getSystemMessages()} and returning * {@link CustomizedSystemMessages}. *
** The defaults defined in this class are: *
* Vaadin gets the SystemMessages from your application by calling a static * getSystemMessages() method. By default the * Application.getSystemMessages() is used. You can customize this by * defining a static MyApplication.getSystemMessages() and returning * CustomizedSystemMessages. Note that getSystemMessages() is static - * changing the system messages will by default change the message for all * users of the application. *
*
* The default behavior is to show a notification, and restart the
* application the the user clicks the message.
* Instead of restarting the application, you can set a specific URL that
* the user is taken to.
* Setting both caption and message to null will restart the application (or
* go to the specified URL) without displaying a notification.
* set*NotificationEnabled(false) will achieve the same thing.
*
* The situations are: *