/* @VaadinApache2LicenseForJavaFiles@ */ package com.vaadin; import java.io.IOException; import java.io.Serializable; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.net.SocketException; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; import java.util.EventListener; import java.util.EventObject; import java.util.HashMap; import java.util.HashSet; import java.util.Hashtable; import java.util.LinkedList; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.vaadin.annotations.EagerInit; import com.vaadin.annotations.Theme; import com.vaadin.annotations.Widgetset; import com.vaadin.data.util.converter.Converter; import com.vaadin.data.util.converter.ConverterFactory; import com.vaadin.data.util.converter.DefaultConverterFactory; import com.vaadin.event.EventRouter; import com.vaadin.service.ApplicationContext; import com.vaadin.shared.ApplicationConstants; import com.vaadin.terminal.AbstractErrorMessage; import com.vaadin.terminal.ApplicationResource; import com.vaadin.terminal.CombinedRequest; import com.vaadin.terminal.DeploymentConfiguration; import com.vaadin.terminal.RequestHandler; import com.vaadin.terminal.Terminal; import com.vaadin.terminal.VariableOwner; import com.vaadin.terminal.WrappedRequest; import com.vaadin.terminal.WrappedRequest.BrowserDetails; import com.vaadin.terminal.WrappedResponse; import com.vaadin.terminal.gwt.server.AbstractApplicationServlet; import com.vaadin.terminal.gwt.server.BootstrapFragmentResponse; import com.vaadin.terminal.gwt.server.BootstrapListener; import com.vaadin.terminal.gwt.server.BootstrapPageResponse; import com.vaadin.terminal.gwt.server.BootstrapResponse; import com.vaadin.terminal.gwt.server.ChangeVariablesErrorEvent; import com.vaadin.terminal.gwt.server.ClientConnector; import com.vaadin.terminal.gwt.server.WebApplicationContext; import com.vaadin.tools.ReflectTools; import com.vaadin.ui.AbstractComponent; import com.vaadin.ui.AbstractField; import com.vaadin.ui.Root; import com.vaadin.ui.Table; 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 Vaadin Ltd. * @since 3.0 */ @SuppressWarnings("serial") public class Application implements Terminal.ErrorListener, Serializable { /** * The name of the parameter that is by default used in e.g. web.xml to * define the name of the default {@link Root} class. */ public static final String ROOT_PARAMETER = "root"; private static final Method BOOTSTRAP_FRAGMENT_METHOD = ReflectTools .findMethod(BootstrapListener.class, "modifyBootstrapFragment", BootstrapFragmentResponse.class); private static final Method BOOTSTRAP_PAGE_METHOD = ReflectTools .findMethod(BootstrapListener.class, "modifyBootstrapPage", BootstrapPageResponse.class); /** * A special application designed to help migrating applications from Vaadin * 6 to Vaadin 7. The legacy application supports setting a main window, * adding additional browser level windows and defining the theme for the * entire application. * * @deprecated This class is only intended to ease migration and should not * be used for new projects. * * @since 7.0 */ @Deprecated public static class LegacyApplication extends Application { /** * Ignore initial / and then get everything up to the next / */ private static final Pattern WINDOW_NAME_PATTERN = Pattern .compile("^/?([^/]+).*"); private Root.LegacyWindow mainWindow; private String theme; private Map legacyRootNames = new HashMap(); /** * Sets the main window of this application. Setting window as a main * window of this application also adds the window to this application. * * @param mainWindow * the root to set as the default window */ public void setMainWindow(Root.LegacyWindow mainWindow) { if (this.mainWindow != null) { throw new IllegalStateException( "mainWindow has already been set"); } if (mainWindow.getApplication() == null) { mainWindow.setApplication(this); } else if (mainWindow.getApplication() != this) { throw new IllegalStateException( "mainWindow is attached to another application"); } this.mainWindow = mainWindow; } /** * Gets the mainWindow of the application. * *

* 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 root used as the default window */ public Root.LegacyWindow getMainWindow() { return mainWindow; } /** * This implementation simulates the way of finding a window for a * request by extracting a window name from the requested path and * passes that name to {@link #getWindow(String)}. * * {@inheritDoc} * * @see #getWindow(String) * @see Application#getRoot(WrappedRequest) */ @Override public Root.LegacyWindow getRoot(WrappedRequest request) { String pathInfo = request.getRequestPathInfo(); String name = null; if (pathInfo != null && pathInfo.length() > 0) { Matcher matcher = WINDOW_NAME_PATTERN.matcher(pathInfo); if (matcher.matches()) { // Skip the initial slash name = matcher.group(1); } } Root.LegacyWindow window = getWindow(name); if (window != null) { return window; } return mainWindow; } /** * Sets the application's theme. *

* Note that this theme can be overridden for a specific root with * {@link Application#getThemeForRoot(Root)}. Setting theme to be * null selects the default theme. For the available theme * names, see the contents of the VAADIN/themes directory. *

* * @param theme * the new theme for this application. */ public void setTheme(String theme) { this.theme = theme; } /** * Gets the application's theme. The application's theme is the default * theme used by all the roots for which a theme is not explicitly * defined. If the application theme is not explicitly set, * null is returned. * * @return the name of the application's theme. */ public String getTheme() { return theme; } /** * This implementation returns the theme that has been set using * {@link #setTheme(String)} *

* {@inheritDoc} */ @Override public String getThemeForRoot(Root root) { return theme; } /** *

* Gets a root by name. Returns null if the application is * not running or it does not contain a window corresponding to the * name. *

* * @param name * the name of the requested window * @return a root corresponding to the name, or null to use * the default window */ public Root.LegacyWindow getWindow(String name) { return legacyRootNames.get(name); } /** * Counter to get unique names for windows with no explicit name */ private int namelessRootIndex = 0; /** * Adds a new browser level window to this application. Please note that * Root doesn't have a name that is used in the URL - to add a named * window you should instead use {@link #addWindow(Root, String)} * * @param root * the root window to add to the application * @return returns the name that has been assigned to the window * * @see #addWindow(Root, String) */ public void addWindow(Root.LegacyWindow root) { if (root.getName() == null) { String name = Integer.toString(namelessRootIndex++); root.setName(name); } legacyRootNames.put(root.getName(), root); root.setApplication(this); } /** * Removes the specified window from the application. This also removes * all name mappings for the window (see * {@link #addWindow(Root, String) and #getWindowName(Root)}. * *

* Note that removing window from the application does not close the * browser window - the window is only removed from the server-side. *

* * @param root * the root to remove */ public void removeWindow(Root.LegacyWindow root) { for (Entry entry : legacyRootNames .entrySet()) { if (entry.getValue() == root) { legacyRootNames.remove(entry.getKey()); } } } /** * 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 getWindows() { return Collections.unmodifiableCollection(legacyRootNames.values()); } } /** * An event sent to {@link #start(ApplicationStartEvent)} when a new * Application is being started. * * @since 7.0 */ public static class ApplicationStartEvent implements Serializable { private final URL applicationUrl; private final Properties applicationProperties; private final ApplicationContext context; private final boolean productionMode; /** * @param applicationUrl * the URL the application should respond to. * @param applicationProperties * the Application properties as specified by the deployment * configuration. * @param context * the context application will be running in. * @param productionMode * flag indicating whether the application is running in * production mode. */ public ApplicationStartEvent(URL applicationUrl, Properties applicationProperties, ApplicationContext context, boolean productionMode) { this.applicationUrl = applicationUrl; this.applicationProperties = applicationProperties; this.context = context; this.productionMode = productionMode; } /** * Gets the URL the application should respond to. * * @return the URL the application should respond to or * null if the URL is not defined. * * @see Application#getURL() */ public URL getApplicationUrl() { return applicationUrl; } /** * Gets the Application properties as specified by the deployment * configuration. * * @return the properties configured for the applciation. * * @see Application#getProperty(String) */ public Properties getApplicationProperties() { return applicationProperties; } /** * Gets the context application will be running in. * * @return the context application will be running in. * * @see Application#getContext() */ public ApplicationContext getContext() { return context; } /** * Checks whether the application is running in production mode. * * @return true if in production mode, else * false * * @see Application#isProductionMode() */ public boolean isProductionMode() { return productionMode; } } private final static Logger logger = Logger.getLogger(Application.class .getName()); /** * Application context the application is running in. */ private ApplicationContext context; /** * The current user or null if no user has logged in. */ private Object user; /** * The application's URL. */ private URL applicationUrl; /** * Application status. */ private volatile boolean applicationIsRunning = false; /** * Application properties. */ private Properties properties; /** * Default locale of the application. */ private Locale locale; /** * List of listeners listening user changes. */ private LinkedList userChangeListeners = null; /** * Application resource mapping: key <-> resource. */ private final Hashtable resourceKeyMap = new Hashtable(); private final Hashtable keyResourceMap = new Hashtable(); private long lastResourceKeyNumber = 0; /** * URL where the user is redirected to on application close, or null if * application is just closed without redirection. */ private String logoutURL = null; /** * The default SystemMessages (read-only). Change by overriding * getSystemMessages() and returning CustomizedSystemMessages */ private static final SystemMessages DEFAULT_SYSTEM_MESSAGES = new SystemMessages(); /** * Application wide error handler which is used by default if an error is * left unhandled. */ private Terminal.ErrorListener errorHandler = this; /** * The converter factory that is used to provide default converters for the * application. */ private ConverterFactory converterFactory = new DefaultConverterFactory(); private LinkedList requestHandlers = new LinkedList(); private int nextRootId = 0; private Map roots = new HashMap(); private boolean productionMode = true; private final Map retainOnRefreshRoots = new HashMap(); private final EventRouter eventRouter = new EventRouter(); /** * Keeps track of which roots have been inited. *

* TODO Investigate whether this might be derived from the different states * in getRootForRrequest. *

*/ private Set initedRoots = new HashSet(); /** * 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 event * the application start event containing details required for * starting the application. * */ public void start(ApplicationStartEvent event) { applicationUrl = event.getApplicationUrl(); productionMode = event.isProductionMode(); properties = event.getApplicationProperties(); context = event.getContext(); 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. *

* * @return true if the application is running, * false if not. */ public boolean isRunning() { return applicationIsRunning; } /** *

* 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. *

*/ public void init() { // Default implementation does nothing } /** * 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 returns null 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); } /** * 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. * * @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 returns null */ 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. * * @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 LinkedList(); } userChangeListeners.add(listener); } /** * Removes the user change listener. * * @param listener * the user change listener to remove. */ public void removeListener(UserChangeListener listener) { if (userChangeListeners == null) { return; } userChangeListeners.remove(listener); if (userChangeListeners.isEmpty()) { userChangeListeners = null; } } /** * Window detach event. * * This event is sent each time a window is removed from the application * with {@link com.vaadin.Application#removeWindow(Window)}. */ public class WindowDetachEvent extends EventObject { private final Window window; /** * Creates a event. * * @param window * the Detached window. */ public WindowDetachEvent(Window window) { super(Application.this); this.window = window; } /** * Gets the detached window. * * @return the detached window. */ public Window getWindow() { return window; } /** * Gets the application from which the window was detached. * * @return the Application. */ public Application getApplication() { return (Application) getSource(); } } /** * Window attach event. * * This event is sent each time a window is attached tothe application with * {@link com.vaadin.Application#addWindow(Window)}. */ public class WindowAttachEvent extends EventObject { private final Window window; /** * Creates a event. * * @param window * the Attached window. */ public WindowAttachEvent(Window window) { super(Application.this); this.window = window; } /** * Gets the attached window. * * @return the attached window. */ public Window getWindow() { return window; } /** * Gets the application to which the window was attached. * * @return the Application. */ public Application getApplication() { return (Application) getSource(); } } /** * Window attach listener interface. */ public interface WindowAttachListener extends Serializable { /** * Window attached * * @param event * the window attach event. */ public void windowAttached(WindowAttachEvent event); } /** * Window detach listener interface. */ public interface WindowDetachListener extends Serializable { /** * Window detached. * * @param event * the window detach event. */ public void windowDetached(WindowDetachEvent event); } /** * Returns 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. *

* * @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) */ @Override public void terminalError(Terminal.ErrorEvent event) { final Throwable t = event.getThrowable(); if (t instanceof SocketException) { // Most likely client browser closed socket getLogger().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 ChangeVariablesErrorEvent) { owner = ((ChangeVariablesErrorEvent) event).getComponent(); } // Shows the error in AbstractComponent if (owner instanceof AbstractComponent) { ((AbstractComponent) owner).setComponentError(AbstractErrorMessage .getErrorMessageForException(t)); } // also print the error on console getLogger().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; } /** * Gets the {@link ConverterFactory} used to locate a suitable * {@link Converter} for fields in the application. * * See {@link #setConverterFactory(ConverterFactory)} for more details * * @return The converter factory used in the application */ public ConverterFactory getConverterFactory() { return converterFactory; } /** * Sets the {@link ConverterFactory} used to locate a suitable * {@link Converter} for fields in the application. *

* The {@link ConverterFactory} is used to find a suitable converter when * binding data to a UI component and the data type does not match the UI * component type, e.g. binding a Double to a TextField (which is based on a * String). *

*

* The {@link Converter} for an individual field can be overridden using * {@link AbstractField#setConverter(Converter)} and for individual property * ids in a {@link Table} using * {@link Table#setConverter(Object, Converter)}. *

*

* The converter factory must never be set to null. * * @param converterFactory * The converter factory used in the application */ public void setConverterFactory(ConverterFactory converterFactory) { this.converterFactory = converterFactory; } /** * 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: *

    *
  • sessionExpiredURL = null
  • *
  • sessionExpiredNotificationEnabled = true
  • *
  • sessionExpiredCaption = ""
  • *
  • sessionExpiredMessage = * "Take note of any unsaved data, and click here to continue."
  • *
  • communicationErrorURL = null
  • *
  • communicationErrorNotificationEnabled = true
  • *
  • communicationErrorCaption = "Communication problem"
  • *
  • communicationErrorMessage = * "Take note of any unsaved data, and click here to continue."
  • *
  • internalErrorURL = null
  • *
  • internalErrorNotificationEnabled = true
  • *
  • internalErrorCaption = "Internal error"
  • *
  • internalErrorMessage = "Please notify the administrator.
    * Take note of any unsaved data, and click here to continue."
  • *
  • outOfSyncURL = null
  • *
  • outOfSyncNotificationEnabled = true
  • *
  • outOfSyncCaption = "Out of sync"
  • *
  • outOfSyncMessage = "Something has caused us to be out of sync * with the server.
    * Take note of any unsaved data, and click here to re-sync."
  • *
  • cookiesDisabledURL = null
  • *
  • cookiesDisabledNotificationEnabled = true
  • *
  • cookiesDisabledCaption = "Cookies disabled"
  • *
  • cookiesDisabledMessage = "This application requires cookies to * function.
    * Please enable cookies in your browser and click here to try again. *
  • *
*

* */ public static class SystemMessages implements Serializable { protected String sessionExpiredURL = null; protected boolean sessionExpiredNotificationEnabled = true; protected String sessionExpiredCaption = "Session Expired"; protected String sessionExpiredMessage = "Take note of any unsaved data, and click here to continue."; protected String communicationErrorURL = null; protected boolean communicationErrorNotificationEnabled = true; protected String communicationErrorCaption = "Communication problem"; protected String communicationErrorMessage = "Take note of any unsaved data, and click here to continue."; protected String authenticationErrorURL = null; protected boolean authenticationErrorNotificationEnabled = true; protected String authenticationErrorCaption = "Authentication problem"; protected String authenticationErrorMessage = "Take note of any unsaved data, and click here to continue."; protected String internalErrorURL = null; protected boolean internalErrorNotificationEnabled = true; protected String internalErrorCaption = "Internal error"; protected String internalErrorMessage = "Please notify the administrator.
Take note of any unsaved data, and click here to continue."; protected String outOfSyncURL = null; protected boolean outOfSyncNotificationEnabled = true; protected String outOfSyncCaption = "Out of sync"; protected String outOfSyncMessage = "Something has caused us to be out of sync with the server.
Take note of any unsaved data, and click here to re-sync."; protected String cookiesDisabledURL = null; protected boolean cookiesDisabledNotificationEnabled = true; protected String cookiesDisabledCaption = "Cookies disabled"; protected String cookiesDisabledMessage = "This application requires cookies to function.
Please enable cookies in your browser and click here to try again."; /** * Use {@link CustomizedSystemMessages} to customize */ private SystemMessages() { } /** * @return null to indicate that the application will be restarted after * session expired message has been shown. */ public String getSessionExpiredURL() { return sessionExpiredURL; } /** * @return true to show session expiration message. */ public boolean isSessionExpiredNotificationEnabled() { return sessionExpiredNotificationEnabled; } /** * @return "" to show no caption. */ public String getSessionExpiredCaption() { return (sessionExpiredNotificationEnabled ? sessionExpiredCaption : null); } /** * @return * "Take note of any unsaved data, and click here to continue." */ public String getSessionExpiredMessage() { return (sessionExpiredNotificationEnabled ? sessionExpiredMessage : null); } /** * @return null to reload the application after communication error * message. */ public String getCommunicationErrorURL() { return communicationErrorURL; } /** * @return true to show the communication error message. */ public boolean isCommunicationErrorNotificationEnabled() { return communicationErrorNotificationEnabled; } /** * @return "Communication problem" */ public String getCommunicationErrorCaption() { return (communicationErrorNotificationEnabled ? communicationErrorCaption : null); } /** * @return * "Take note of any unsaved data, and click here to continue." */ public String getCommunicationErrorMessage() { return (communicationErrorNotificationEnabled ? communicationErrorMessage : null); } /** * @return null to reload the application after authentication error * message. */ public String getAuthenticationErrorURL() { return authenticationErrorURL; } /** * @return true to show the authentication error message. */ public boolean isAuthenticationErrorNotificationEnabled() { return authenticationErrorNotificationEnabled; } /** * @return "Authentication problem" */ public String getAuthenticationErrorCaption() { return (authenticationErrorNotificationEnabled ? authenticationErrorCaption : null); } /** * @return * "Take note of any unsaved data, and click here to continue." */ public String getAuthenticationErrorMessage() { return (authenticationErrorNotificationEnabled ? authenticationErrorMessage : null); } /** * @return null to reload the current URL after internal error message * has been shown. */ public String getInternalErrorURL() { return internalErrorURL; } /** * @return true to enable showing of internal error message. */ public boolean isInternalErrorNotificationEnabled() { return internalErrorNotificationEnabled; } /** * @return "Internal error" */ public String getInternalErrorCaption() { return (internalErrorNotificationEnabled ? internalErrorCaption : null); } /** * @return "Please notify the administrator.
* Take note of any unsaved data, and click here to * continue." */ public String getInternalErrorMessage() { return (internalErrorNotificationEnabled ? internalErrorMessage : null); } /** * @return null to reload the application after out of sync message. */ public String getOutOfSyncURL() { return outOfSyncURL; } /** * @return true to enable showing out of sync message */ public boolean isOutOfSyncNotificationEnabled() { return outOfSyncNotificationEnabled; } /** * @return "Out of sync" */ public String getOutOfSyncCaption() { return (outOfSyncNotificationEnabled ? outOfSyncCaption : null); } /** * @return "Something has caused us to be out of sync with the server.
* Take note of any unsaved data, and click here to * re-sync." */ public String getOutOfSyncMessage() { return (outOfSyncNotificationEnabled ? outOfSyncMessage : null); } /** * Returns the URL the user should be redirected to after dismissing the * "you have to enable your cookies" message. Typically null. * * @return A URL the user should be redirected to after dismissing the * message or null to reload the current URL. */ public String getCookiesDisabledURL() { return cookiesDisabledURL; } /** * Determines if "cookies disabled" messages should be shown to the end * user or not. If the notification is disabled the user will be * immediately redirected to the URL returned by * {@link #getCookiesDisabledURL()}. * * @return true to show "cookies disabled" messages to the end user, * false to redirect to the given URL directly */ public boolean isCookiesDisabledNotificationEnabled() { return cookiesDisabledNotificationEnabled; } /** * Returns the caption of the message shown to the user when cookies are * disabled in the browser. * * @return The caption of the "cookies disabled" message */ public String getCookiesDisabledCaption() { return (cookiesDisabledNotificationEnabled ? cookiesDisabledCaption : null); } /** * Returns the message shown to the user when cookies are disabled in * the browser. * * @return The "cookies disabled" message */ public String getCookiesDisabledMessage() { return (cookiesDisabledNotificationEnabled ? cookiesDisabledMessage : null); } } /** * Contains the system messages used to notify the user about various * critical situations that can occur. *

* 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: *

  • Session expired: the user session has expired, usually due to * inactivity.
  • *
  • Communication error: the client failed to contact the server, or the * server returned and invalid response.
  • *
  • Internal error: unhandled critical server error (e.g out of memory, * database crash) *
  • Out of sync: the client is not in sync with the server. E.g the user * opens two windows showing the same application, but the application does * not support this and uses the same Window instance. When the user makes * changes in one of the windows - the other window is no longer in sync, * and (for instance) pressing a button that is no longer present in the UI * will cause a out-of-sync -situation. *

    */ public static class CustomizedSystemMessages extends SystemMessages implements Serializable { /** * Sets the URL to go to when the session has expired. * * @param sessionExpiredURL * the URL to go to, or null to reload current */ public void setSessionExpiredURL(String sessionExpiredURL) { this.sessionExpiredURL = sessionExpiredURL; } /** * Enables or disables the notification. If disabled, the set URL (or * current) is loaded directly when next transaction between server and * client happens. * * @param sessionExpiredNotificationEnabled * true = enabled, false = disabled */ public void setSessionExpiredNotificationEnabled( boolean sessionExpiredNotificationEnabled) { this.sessionExpiredNotificationEnabled = sessionExpiredNotificationEnabled; } /** * Sets the caption of the notification. Set to null for no caption. If * both caption and message are null, client automatically forwards to * sessionExpiredUrl after timeout timer expires. Timer uses value read * from HTTPSession.getMaxInactiveInterval() * * @param sessionExpiredCaption * the caption */ public void setSessionExpiredCaption(String sessionExpiredCaption) { this.sessionExpiredCaption = sessionExpiredCaption; } /** * Sets the message of the notification. Set to null for no message. If * both caption and message are null, client automatically forwards to * sessionExpiredUrl after timeout timer expires. Timer uses value read * from HTTPSession.getMaxInactiveInterval() * * @param sessionExpiredMessage * the message */ public void setSessionExpiredMessage(String sessionExpiredMessage) { this.sessionExpiredMessage = sessionExpiredMessage; } /** * Sets the URL to go to when there is a authentication error. * * @param authenticationErrorURL * the URL to go to, or null to reload current */ public void setAuthenticationErrorURL(String authenticationErrorURL) { this.authenticationErrorURL = authenticationErrorURL; } /** * Enables or disables the notification. If disabled, the set URL (or * current) is loaded directly. * * @param authenticationErrorNotificationEnabled * true = enabled, false = disabled */ public void setAuthenticationErrorNotificationEnabled( boolean authenticationErrorNotificationEnabled) { this.authenticationErrorNotificationEnabled = authenticationErrorNotificationEnabled; } /** * Sets the caption of the notification. Set to null for no caption. If * both caption and message is null, the notification is disabled; * * @param authenticationErrorCaption * the caption */ public void setAuthenticationErrorCaption( String authenticationErrorCaption) { this.authenticationErrorCaption = authenticationErrorCaption; } /** * Sets the message of the notification. Set to null for no message. If * both caption and message is null, the notification is disabled; * * @param authenticationErrorMessage * the message */ public void setAuthenticationErrorMessage( String authenticationErrorMessage) { this.authenticationErrorMessage = authenticationErrorMessage; } /** * Sets the URL to go to when there is a communication error. * * @param communicationErrorURL * the URL to go to, or null to reload current */ public void setCommunicationErrorURL(String communicationErrorURL) { this.communicationErrorURL = communicationErrorURL; } /** * Enables or disables the notification. If disabled, the set URL (or * current) is loaded directly. * * @param communicationErrorNotificationEnabled * true = enabled, false = disabled */ public void setCommunicationErrorNotificationEnabled( boolean communicationErrorNotificationEnabled) { this.communicationErrorNotificationEnabled = communicationErrorNotificationEnabled; } /** * Sets the caption of the notification. Set to null for no caption. If * both caption and message is null, the notification is disabled; * * @param communicationErrorCaption * the caption */ public void setCommunicationErrorCaption( String communicationErrorCaption) { this.communicationErrorCaption = communicationErrorCaption; } /** * Sets the message of the notification. Set to null for no message. If * both caption and message is null, the notification is disabled; * * @param communicationErrorMessage * the message */ public void setCommunicationErrorMessage( String communicationErrorMessage) { this.communicationErrorMessage = communicationErrorMessage; } /** * Sets the URL to go to when an internal error occurs. * * @param internalErrorURL * the URL to go to, or null to reload current */ public void setInternalErrorURL(String internalErrorURL) { this.internalErrorURL = internalErrorURL; } /** * Enables or disables the notification. If disabled, the set URL (or * current) is loaded directly. * * @param internalErrorNotificationEnabled * true = enabled, false = disabled */ public void setInternalErrorNotificationEnabled( boolean internalErrorNotificationEnabled) { this.internalErrorNotificationEnabled = internalErrorNotificationEnabled; } /** * Sets the caption of the notification. Set to null for no caption. If * both caption and message is null, the notification is disabled; * * @param internalErrorCaption * the caption */ public void setInternalErrorCaption(String internalErrorCaption) { this.internalErrorCaption = internalErrorCaption; } /** * Sets the message of the notification. Set to null for no message. If * both caption and message is null, the notification is disabled; * * @param internalErrorMessage * the message */ public void setInternalErrorMessage(String internalErrorMessage) { this.internalErrorMessage = internalErrorMessage; } /** * Sets the URL to go to when the client is out-of-sync. * * @param outOfSyncURL * the URL to go to, or null to reload current */ public void setOutOfSyncURL(String outOfSyncURL) { this.outOfSyncURL = outOfSyncURL; } /** * Enables or disables the notification. If disabled, the set URL (or * current) is loaded directly. * * @param outOfSyncNotificationEnabled * true = enabled, false = disabled */ public void setOutOfSyncNotificationEnabled( boolean outOfSyncNotificationEnabled) { this.outOfSyncNotificationEnabled = outOfSyncNotificationEnabled; } /** * Sets the caption of the notification. Set to null for no caption. If * both caption and message is null, the notification is disabled; * * @param outOfSyncCaption * the caption */ public void setOutOfSyncCaption(String outOfSyncCaption) { this.outOfSyncCaption = outOfSyncCaption; } /** * Sets the message of the notification. Set to null for no message. If * both caption and message is null, the notification is disabled; * * @param outOfSyncMessage * the message */ public void setOutOfSyncMessage(String outOfSyncMessage) { this.outOfSyncMessage = outOfSyncMessage; } /** * Sets the URL to redirect to when the browser has cookies disabled. * * @param cookiesDisabledURL * the URL to redirect to, or null to reload the current URL */ public void setCookiesDisabledURL(String cookiesDisabledURL) { this.cookiesDisabledURL = cookiesDisabledURL; } /** * Enables or disables the notification for "cookies disabled" messages. * If disabled, the URL returned by {@link #getCookiesDisabledURL()} is * loaded directly. * * @param cookiesDisabledNotificationEnabled * true to enable "cookies disabled" messages, false * otherwise */ public void setCookiesDisabledNotificationEnabled( boolean cookiesDisabledNotificationEnabled) { this.cookiesDisabledNotificationEnabled = cookiesDisabledNotificationEnabled; } /** * Sets the caption of the "cookies disabled" notification. Set to null * for no caption. If both caption and message is null, the notification * is disabled. * * @param cookiesDisabledCaption * the caption for the "cookies disabled" notification */ public void setCookiesDisabledCaption(String cookiesDisabledCaption) { this.cookiesDisabledCaption = cookiesDisabledCaption; } /** * Sets the message of the "cookies disabled" notification. Set to null * for no message. If both caption and message is null, the notification * is disabled. * * @param cookiesDisabledMessage * the message for the "cookies disabled" notification */ public void setCookiesDisabledMessage(String cookiesDisabledMessage) { this.cookiesDisabledMessage = cookiesDisabledMessage; } } /** * Application error is an error message defined on the application level. * * When an error occurs on the application level, this error message type * should be used. This indicates that the problem is caused by the * application - not by the user. */ public class ApplicationError implements Terminal.ErrorEvent { private final Throwable throwable; public ApplicationError(Throwable throwable) { this.throwable = throwable; } @Override public Throwable getThrowable() { return throwable; } } /** * Gets a root for a request for which no root is already known. This method * is called when the framework processes a request that does not originate * from an existing root instance. This typically happens when a host page * is requested. * *

    * Subclasses of Application may override this method to provide custom * logic for choosing how to create a suitable root or for picking an * already created root. If an existing root is picked, care should be taken * to avoid keeping the same root open in multiple browser windows, as that * will cause the states to go out of sync. *

    * *

    * If {@link BrowserDetails} are required to create a Root, the * implementation can throw a {@link RootRequiresMoreInformationException} * exception. In this case, the framework will instruct the browser to send * the additional details, whereupon this method is invoked again with the * browser details present in the wrapped request. Throwing the exception if * the browser details are already available is not supported. *

    * *

    * The default implementation in {@link Application} creates a new instance * of the Root class returned by {@link #getRootClassName(WrappedRequest)}, * which in turn uses the {@value #ROOT_PARAMETER} parameter from web.xml. * If {@link DeploymentConfiguration#getClassLoader()} for the request * returns a {@link ClassLoader}, it is used for loading the Root class. * Otherwise the {@link ClassLoader} used to load this class is used. *

    * * @param request * the wrapped request for which a root is needed * @return a root instance to use for the request * @throws RootRequiresMoreInformationException * may be thrown by an implementation to indicate that * {@link BrowserDetails} are required to create a root * * @see #getRootClassName(WrappedRequest) * @see Root * @see RootRequiresMoreInformationException * @see WrappedRequest#getBrowserDetails() * * @since 7.0 */ protected Root getRoot(WrappedRequest request) throws RootRequiresMoreInformationException { String rootClassName = getRootClassName(request); try { ClassLoader classLoader = request.getDeploymentConfiguration() .getClassLoader(); if (classLoader == null) { classLoader = getClass().getClassLoader(); } Class rootClass = Class.forName(rootClassName, true, classLoader).asSubclass(Root.class); try { Root root = rootClass.newInstance(); return root; } catch (Exception e) { throw new RuntimeException("Could not instantiate root class " + rootClassName, e); } } catch (ClassNotFoundException e) { throw new RuntimeException("Could not load root class " + rootClassName, e); } } /** * Provides the name of the Root class that should be used for * a request. The class must have an accessible no-args constructor. *

    * The default implementation uses the {@value #ROOT_PARAMETER} parameter * from web.xml. *

    *

    * This method is mainly used by the default implementation of * {@link #getRoot(WrappedRequest)}. If you override that method with your * own functionality, the results of this method might not be used. *

    * * @param request * the request for which a new root is required * @return the name of the root class to use * * @since 7.0 */ protected String getRootClassName(WrappedRequest request) { Object rootClassNameObj = properties.get(ROOT_PARAMETER); if (rootClassNameObj instanceof String) { return (String) rootClassNameObj; } else { throw new RuntimeException("No " + ROOT_PARAMETER + " defined in web.xml"); } } /** * Finds the theme to use for a specific root. If no specific theme is * required, null is returned. * * TODO Tell what the default implementation does once it does something. * * @param root * the root to get a theme for * @return the name of the theme, or null if the default theme * should be used * * @since 7.0 */ public String getThemeForRoot(Root root) { Theme rootTheme = getAnnotationFor(root.getClass(), Theme.class); if (rootTheme != null) { return rootTheme.value(); } else { return null; } } /** * Finds the widgetset to use for a specific root. If no specific widgetset * is required, null is returned. * * TODO Tell what the default implementation does once it does something. * * @param root * the root to get a widgetset for * @return the name of the widgetset, or null if the default * widgetset should be used * * @since 7.0 */ public String getWidgetsetForRoot(Root root) { Widgetset rootWidgetset = getAnnotationFor(root.getClass(), Widgetset.class); if (rootWidgetset != null) { return rootWidgetset.value(); } else { return null; } } /** * Helper to get an annotation for a class. If the annotation is not present * on the target class, it's superclasses and implemented interfaces are * also searched for the annotation. * * @param type * the target class from which the annotation should be found * @param annotationType * the annotation type to look for * @return an annotation of the given type, or null if the * annotation is not present on the class */ private static T getAnnotationFor(Class type, Class annotationType) { // Find from the class hierarchy Class currentType = type; while (currentType != Object.class) { T annotation = currentType.getAnnotation(annotationType); if (annotation != null) { return annotation; } else { currentType = currentType.getSuperclass(); } } // Find from an implemented interface for (Class iface : type.getInterfaces()) { T annotation = iface.getAnnotation(annotationType); if (annotation != null) { return annotation; } } return null; } /** * Handles a request by passing it to each registered {@link RequestHandler} * in turn until one produces a response. This method is used for requests * that have not been handled by any specific functionality in the terminal * implementation (e.g. {@link AbstractApplicationServlet}). *

    * The request handlers are invoked in the revere order in which they were * added to the application until a response has been produced. This means * that the most recently added handler is used first and the first request * handler that was added to the application is invoked towards the end * unless any previous handler has already produced a response. *

    * * @param request * the wrapped request to get information from * @param response * the response to which data can be written * @return returns true if a {@link RequestHandler} has * produced a response and false if no response has * been written. * @throws IOException * * @see #addRequestHandler(RequestHandler) * @see RequestHandler * * @since 7.0 */ public boolean handleRequest(WrappedRequest request, WrappedResponse response) throws IOException { // Use a copy to avoid ConcurrentModificationException for (RequestHandler handler : new ArrayList( requestHandlers)) { if (handler.handleRequest(this, request, response)) { return true; } } // If not handled return false; } /** * Adds a request handler to this application. Request handlers can be added * to provide responses to requests that are not handled by the default * functionality of the framework. *

    * Handlers are called in reverse order of addition, so the most recently * added handler will be called first. *

    * * @param handler * the request handler to add * * @see #handleRequest(WrappedRequest, WrappedResponse) * @see #removeRequestHandler(RequestHandler) * * @since 7.0 */ public void addRequestHandler(RequestHandler handler) { requestHandlers.addFirst(handler); } /** * Removes a request handler from the application. * * @param handler * the request handler to remove * * @since 7.0 */ public void removeRequestHandler(RequestHandler handler) { requestHandlers.remove(handler); } /** * Gets the request handlers that are registered to the application. The * iteration order of the returned collection is the same as the order in * which the request handlers will be invoked when a request is handled. * * @return a collection of request handlers, with the iteration order * according to the order they would be invoked * * @see #handleRequest(WrappedRequest, WrappedResponse) * @see #addRequestHandler(RequestHandler) * @see #removeRequestHandler(RequestHandler) * * @since 7.0 */ public Collection getRequestHandlers() { return Collections.unmodifiableCollection(requestHandlers); } /** * Find an application resource with a given key. * * @param key * The key of the resource * @return The application resource corresponding to the provided key, or * null if no resource is registered for the key * * @since 7.0 */ public ApplicationResource getResource(String key) { return keyResourceMap.get(key); } /** * Thread local for keeping track of currently used application instance * * @since 7.0 */ private static final ThreadLocal currentApplication = new ThreadLocal(); private boolean rootPreserved = false; /** * Gets the currently used application. The current application is * automatically defined when processing requests to the server. In other * cases, (e.g. from background threads), the current application is not * automatically defined. * * @return the current application instance if available, otherwise * null * * @see #setCurrent(Application) * * @since 7.0 */ public static Application getCurrent() { return currentApplication.get(); } /** * Sets the thread local for the current application. This method is used by * the framework to set the current application whenever a new request is * processed and it is cleared when the request has been processed. *

    * The application developer can also use this method to define the current * application outside the normal request handling, e.g. when initiating * custom background threads. *

    * * @param application * * @see #getCurrent() * @see ThreadLocal * * @since 7.0 */ public static void setCurrent(Application application) { currentApplication.set(application); } /** * Check whether this application is in production mode. If an application * is in production mode, certain debugging facilities are not available. * * @return the status of the production mode flag * * @since 7.0 */ public boolean isProductionMode() { return productionMode; } /** * Finds the {@link Root} to which a particular request belongs. If the * request originates from an existing Root, that root is returned. In other * cases, the method attempts to create and initialize a new root and might * throw a {@link RootRequiresMoreInformationException} if all required * information is not available. *

    * Please note that this method can also return a newly created * Root which has not yet been initialized. You can use * {@link #isRootInitPending(int)} with the root's id ( * {@link Root#getRootId()} to check whether the initialization is still * pending. *

    * * @param request * the request for which a root is desired * @return a root belonging to the request * @throws RootRequiresMoreInformationException * if no existing root could be found and creating a new root * requires additional information from the browser * * @see #getRoot(WrappedRequest) * @see RootRequiresMoreInformationException * * @since 7.0 */ public Root getRootForRequest(WrappedRequest request) throws RootRequiresMoreInformationException { Root root = Root.getCurrent(); if (root != null) { return root; } Integer rootId = getRootId(request); synchronized (this) { BrowserDetails browserDetails = request.getBrowserDetails(); boolean hasBrowserDetails = browserDetails != null && browserDetails.getUriFragment() != null; root = roots.get(rootId); if (root == null && isRootPreserved()) { // Check for a known root if (!retainOnRefreshRoots.isEmpty()) { Integer retainedRootId; if (!hasBrowserDetails) { throw new RootRequiresMoreInformationException(); } else { String windowName = browserDetails.getWindowName(); retainedRootId = retainOnRefreshRoots.get(windowName); } if (retainedRootId != null) { rootId = retainedRootId; root = roots.get(rootId); } } } if (root == null) { // Throws exception if root can not yet be created root = getRoot(request); // Initialize some fields for a newly created root if (root.getApplication() == null) { root.setApplication(this); } if (root.getRootId() < 0) { if (rootId == null) { // Get the next id if none defined rootId = Integer.valueOf(nextRootId++); } root.setRootId(rootId.intValue()); roots.put(rootId, root); } } // Set thread local here so it is available in init Root.setCurrent(root); if (!initedRoots.contains(rootId)) { boolean initRequiresBrowserDetails = isRootPreserved() || !root.getClass() .isAnnotationPresent(EagerInit.class); if (!initRequiresBrowserDetails || hasBrowserDetails) { root.doInit(request); // Remember that this root has been initialized initedRoots.add(rootId); // init() might turn on preserve so do this afterwards if (isRootPreserved()) { // Remember this root String windowName = request.getBrowserDetails() .getWindowName(); retainOnRefreshRoots.put(windowName, rootId); } } } } // end synchronized block return root; } /** * Internal helper to finds the root id for a request. * * @param request * the request to get the root id for * @return a root id, or null if no root id is defined * * @since 7.0 */ private static Integer getRootId(WrappedRequest request) { if (request instanceof CombinedRequest) { // Combined requests has the rootid parameter in the second request CombinedRequest combinedRequest = (CombinedRequest) request; request = combinedRequest.getSecondRequest(); } String rootIdString = request .getParameter(ApplicationConstants.ROOT_ID_PARAMETER); Integer rootId = rootIdString == null ? null : new Integer(rootIdString); return rootId; } /** * Sets whether the same Root state should be reused if the framework can * detect that the application is opened in a browser window where it has * previously been open. The framework attempts to discover this by checking * the value of window.name in the browser. *

    * NOTE that you should avoid turning this feature on/off on-the-fly when * the UI is already shown, as it might not be retained as intended. *

    * * @param rootPreserved * trueif the same Root instance should be reused * e.g. when the browser window is refreshed. */ public void setRootPreserved(boolean rootPreserved) { this.rootPreserved = rootPreserved; if (!rootPreserved) { retainOnRefreshRoots.clear(); } } /** * Checks whether the same Root state should be reused if the framework can * detect that the application is opened in a browser window where it has * previously been open. The framework attempts to discover this by checking * the value of window.name in the browser. * * @return trueif the same Root instance should be reused e.g. * when the browser window is refreshed. */ public boolean isRootPreserved() { return rootPreserved; } /** * Checks whether there's a pending initialization for the root with the * given id. * * @param rootId * root id to check for * @return true of the initialization is pending, * false if the root id is not registered or if the * root has already been initialized * * @see #getRootForRequest(WrappedRequest) */ public boolean isRootInitPending(int rootId) { return !initedRoots.contains(Integer.valueOf(rootId)); } /** * Gets all the roots of this application. This includes roots that have * been requested but not yet initialized. Please note, that roots are not * automatically removed e.g. if the browser window is closed and that there * is no way to manually remove a root. Inactive roots will thus not be * released for GC until the entire application is released when the session * has timed out (unless there are dangling references). Improved support * for releasing unused roots is planned for an upcoming alpha release of * Vaadin 7. * * @return a collection of roots belonging to this application * * @since 7.0 */ public Collection getRoots() { return Collections.unmodifiableCollection(roots.values()); } private int connectorIdSequence = 0; /** * Generate an id for the given Connector. Connectors must not call this * method more than once, the first time they need an id. * * @param connector * A connector that has not yet been assigned an id. * @return A new id for the connector */ public String createConnectorId(ClientConnector connector) { return String.valueOf(connectorIdSequence++); } private static final Logger getLogger() { return Logger.getLogger(Application.class.getName()); } /** * Returns a Root with the given id. *

    * This is meant for framework internal use. *

    * * @param rootId * The root id * @return The root with the given id or null if not found */ public Root getRootById(int rootId) { return roots.get(rootId); } /** * Adds a listener that will be invoked when the bootstrap HTML is about to * be generated. This can be used to modify the contents of the HTML that * loads the Vaadin application in the browser and the HTTP headers that are * included in the response serving the HTML. * * @see BootstrapListener#modifyBootstrapFragment(BootstrapFragmentResponse) * @see BootstrapListener#modifyBootstrapPage(BootstrapPageResponse) * * @param listener * the bootstrap listener to add */ public void addBootstrapListener(BootstrapListener listener) { eventRouter.addListener(BootstrapFragmentResponse.class, listener, BOOTSTRAP_FRAGMENT_METHOD); eventRouter.addListener(BootstrapPageResponse.class, listener, BOOTSTRAP_PAGE_METHOD); } /** * Remove a bootstrap listener that was previously added. * * @see #addBootstrapListener(BootstrapListener) * * @param listener * the bootstrap listener to remove */ public void removeBootstrapListener(BootstrapListener listener) { eventRouter.removeListener(BootstrapFragmentResponse.class, listener, BOOTSTRAP_FRAGMENT_METHOD); eventRouter.removeListener(BootstrapPageResponse.class, listener, BOOTSTRAP_PAGE_METHOD); } /** * Fires a bootstrap event to all registered listeners. There are currently * two supported events, both inheriting from {@link BootstrapResponse}: * {@link BootstrapFragmentResponse} and {@link BootstrapPageResponse}. * * @param response * the bootstrap response event for which listeners should be * fired */ public void modifyBootstrapResponse(BootstrapResponse response) { eventRouter.fireEvent(response); } }