summaryrefslogtreecommitdiffstats
path: root/server/src/com/vaadin/Application.java
diff options
context:
space:
mode:
Diffstat (limited to 'server/src/com/vaadin/Application.java')
-rw-r--r--server/src/com/vaadin/Application.java2426
1 files changed, 2426 insertions, 0 deletions
diff --git a/server/src/com/vaadin/Application.java b/server/src/com/vaadin/Application.java
new file mode 100644
index 0000000000..1d31410185
--- /dev/null
+++ b/server/src/com/vaadin/Application.java
@@ -0,0 +1,2426 @@
+/*
+@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.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.client.ApplicationConnection;
+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;
+
+/**
+ * <p>
+ * 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.
+ * </p>
+ *
+ * <p>
+ * 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 <code>Application</code>
+ * needs to do is implement the <code>init</code> 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.
+ * </p>
+ *
+ * <p>
+ * See the class <code>com.vaadin.demo.HelloWorld</code> for a simple example of
+ * a fully working application.
+ * </p>
+ *
+ * <p>
+ * <strong>Window access.</strong> <code>Application</code> provides methods to
+ * list, add and remove the windows it contains.
+ * </p>
+ *
+ * <p>
+ * <strong>Execution control.</strong> 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.
+ * </p>
+ *
+ * <p>
+ * <strong>Theme selection.</strong> 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.
+ * </p>
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @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<String, Root.LegacyWindow> legacyRootNames = new HashMap<String, Root.LegacyWindow>();
+
+ /**
+ * 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.
+ *
+ * <p>
+ * The main window is the window attached to the application URL (
+ * {@link #getURL()}) and thus which is show by default to the user.
+ * </p>
+ * <p>
+ * Note that each application must have at least one main window.
+ * </p>
+ *
+ * @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.
+ * <p>
+ * Note that this theme can be overridden for a specific root with
+ * {@link Application#getThemeForRoot(Root)}. Setting theme to be
+ * <code>null</code> selects the default theme. For the available theme
+ * names, see the contents of the VAADIN/themes directory.
+ * </p>
+ *
+ * @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,
+ * <code>null</code> 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)}
+ * <p>
+ * {@inheritDoc}
+ */
+
+ @Override
+ public String getThemeForRoot(Root root) {
+ return theme;
+ }
+
+ /**
+ * <p>
+ * Gets a root by name. Returns <code>null</code> if the application is
+ * not running or it does not contain a window corresponding to the
+ * name.
+ * </p>
+ *
+ * @param name
+ * the name of the requested window
+ * @return a root corresponding to the name, or <code>null</code> 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)}.
+ *
+ * <p>
+ * Note that removing window from the application does not close the
+ * browser window - the window is only removed from the server-side.
+ * </p>
+ *
+ * @param root
+ * the root to remove
+ */
+ public void removeWindow(Root.LegacyWindow root) {
+ for (Entry<String, Root.LegacyWindow> entry : legacyRootNames
+ .entrySet()) {
+ if (entry.getValue() == root) {
+ legacyRootNames.remove(entry.getKey());
+ }
+ }
+ }
+
+ /**
+ * Gets the set of windows contained by the application.
+ *
+ * <p>
+ * Note that the returned set of windows can not be modified.
+ * </p>
+ *
+ * @return the unmodifiable collection of windows.
+ */
+ public Collection<Root.LegacyWindow> 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
+ * <code>null</code> 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 <code>true</code> if in production mode, else
+ * <code>false</code>
+ *
+ * @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 <code>null</code> 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<UserChangeListener> userChangeListeners = null;
+
+ /**
+ * Application resource mapping: key <-> resource.
+ */
+ private final Hashtable<ApplicationResource, String> resourceKeyMap = new Hashtable<ApplicationResource, String>();
+
+ private final Hashtable<String, ApplicationResource> keyResourceMap = new Hashtable<String, ApplicationResource>();
+
+ 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<RequestHandler> requestHandlers = new LinkedList<RequestHandler>();
+
+ private int nextRootId = 0;
+ private Map<Integer, Root> roots = new HashMap<Integer, Root>();
+
+ private boolean productionMode = true;
+
+ private final Map<String, Integer> retainOnRefreshRoots = new HashMap<String, Integer>();
+
+ private final EventRouter eventRouter = new EventRouter();
+
+ /**
+ * Keeps track of which roots have been inited.
+ * <p>
+ * TODO Investigate whether this might be derived from the different states
+ * in getRootForRrequest.
+ * </p>
+ */
+ private Set<Integer> initedRoots = new HashSet<Integer>();
+
+ /**
+ * Gets the user of the application.
+ *
+ * <p>
+ * 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)}.
+ * </p>
+ *
+ * @return the User of the application.
+ */
+ public Object getUser() {
+ return user;
+ }
+
+ /**
+ * <p>
+ * 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.
+ * </p>
+ * <p>
+ * 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.
+ * </p>
+ * <p>
+ * 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()}.
+ * </p>
+ *
+ * @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.
+ *
+ * <p>
+ * 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()}).
+ * </p>
+ *
+ * @return the application's URL.
+ */
+ public URL getURL() {
+ return applicationUrl;
+ }
+
+ /**
+ * Ends the Application.
+ *
+ * <p>
+ * 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.
+ * </p>
+ * .
+ */
+ public void close() {
+ applicationIsRunning = false;
+ }
+
+ /**
+ * Starts the application on the given URL.
+ *
+ * <p>
+ * 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.
+ * </p>
+ *
+ * <p>
+ * 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}.
+ * </p>
+ *
+ * @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.
+ *
+ * <p>
+ * Application starts running when its
+ * {@link #start(URL, Properties, ApplicationContext)} method has been
+ * called and stops when the {@link #close()} is called.
+ * </p>
+ *
+ * @return <code>true</code> if the application is running,
+ * <code>false</code> if not.
+ */
+ public boolean isRunning() {
+ return applicationIsRunning;
+ }
+
+ /**
+ * <p>
+ * Main initializer of the application. The <code>init</code> method is
+ * called by the framework when the application is started, and it should
+ * perform whatever initialization operations the application needs.
+ * </p>
+ */
+ public void init() {
+ // Default implementation does nothing
+ }
+
+ /**
+ * Returns an enumeration of all the names in this application.
+ *
+ * <p>
+ * See {@link #start(URL, Properties, ApplicationContext)} how properties
+ * are defined.
+ * </p>
+ *
+ * @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 <code>null</code> 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;
+ }
+
+ /**
+ * <p>
+ * An event that characterizes a change in the current selection.
+ * </p>
+ * 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 returns <code>null</code>
+ */
+ public Object getPreviousUser() {
+ return prevUser;
+ }
+
+ /**
+ * Gets the application where the user change occurred.
+ *
+ * @return the Application.
+ */
+ public Application getApplication() {
+ return (Application) getSource();
+ }
+ }
+
+ /**
+ * The <code>UserChangeListener</code> interface for listening application
+ * user changes.
+ *
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ public interface UserChangeListener extends EventListener, Serializable {
+
+ /**
+ * The <code>applicationUserChanged</code> 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<UserChangeListener>();
+ }
+ 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
+ * <code>null</code>, the application is closed normally as defined by the
+ * application running environment.
+ * <p>
+ * Desktop application just closes the application window and
+ * web-application redirects the browser to application main URL.
+ * </p>
+ *
+ * @return the URL.
+ */
+ public String getLogoutURL() {
+ return logoutURL;
+ }
+
+ /**
+ * Sets the URL user is redirected to on application close. If the URL is
+ * <code>null</code>, 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;
+ }
+
+ /**
+ * <p>
+ * Invoked by the terminal on any exception that occurs in application and
+ * is thrown by the <code>setVariable</code> to the terminal. The default
+ * implementation sets the exceptions as <code>ComponentErrors</code> to the
+ * component that initiated the exception and prints stack trace to standard
+ * error stream.
+ * </p>
+ * <p>
+ * You can safely override this method in your application in order to
+ * direct the errors to some other destination (for example log).
+ * </p>
+ *
+ * @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.
+ * <p>
+ * 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.
+ * </p>
+ * <p>
+ * 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}.
+ * </p>
+ *
+ * @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.
+ * <p>
+ * 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).
+ * </p>
+ * <p>
+ * 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)}.
+ * </p>
+ * <p>
+ * 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.
+ * <p>
+ * Customize by overriding the static
+ * {@link Application#getSystemMessages()} and returning
+ * {@link CustomizedSystemMessages}.
+ * </p>
+ * <p>
+ * The defaults defined in this class are:
+ * <ul>
+ * <li><b>sessionExpiredURL</b> = null</li>
+ * <li><b>sessionExpiredNotificationEnabled</b> = true</li>
+ * <li><b>sessionExpiredCaption</b> = ""</li>
+ * <li><b>sessionExpiredMessage</b> =
+ * "Take note of any unsaved data, and <u>click here</u> to continue."</li>
+ * <li><b>communicationErrorURL</b> = null</li>
+ * <li><b>communicationErrorNotificationEnabled</b> = true</li>
+ * <li><b>communicationErrorCaption</b> = "Communication problem"</li>
+ * <li><b>communicationErrorMessage</b> =
+ * "Take note of any unsaved data, and <u>click here</u> to continue."</li>
+ * <li><b>internalErrorURL</b> = null</li>
+ * <li><b>internalErrorNotificationEnabled</b> = true</li>
+ * <li><b>internalErrorCaption</b> = "Internal error"</li>
+ * <li><b>internalErrorMessage</b> = "Please notify the administrator.<br/>
+ * Take note of any unsaved data, and <u>click here</u> to continue."</li>
+ * <li><b>outOfSyncURL</b> = null</li>
+ * <li><b>outOfSyncNotificationEnabled</b> = true</li>
+ * <li><b>outOfSyncCaption</b> = "Out of sync"</li>
+ * <li><b>outOfSyncMessage</b> = "Something has caused us to be out of sync
+ * with the server.<br/>
+ * Take note of any unsaved data, and <u>click here</u> to re-sync."</li>
+ * <li><b>cookiesDisabledURL</b> = null</li>
+ * <li><b>cookiesDisabledNotificationEnabled</b> = true</li>
+ * <li><b>cookiesDisabledCaption</b> = "Cookies disabled"</li>
+ * <li><b>cookiesDisabledMessage</b> = "This application requires cookies to
+ * function.<br/>
+ * Please enable cookies in your browser and <u>click here</u> to try again.
+ * </li>
+ * </ul>
+ * </p>
+ *
+ */
+ 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 <u>click here</u> 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 <u>click here</u> 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 <u>click here</u> to continue.";
+
+ protected String internalErrorURL = null;
+ protected boolean internalErrorNotificationEnabled = true;
+ protected String internalErrorCaption = "Internal error";
+ protected String internalErrorMessage = "Please notify the administrator.<br/>Take note of any unsaved data, and <u>click here</u> 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.<br/>Take note of any unsaved data, and <u>click here</u> 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.<br/>Please enable cookies in your browser and <u>click here</u> 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 <u>click here</u> 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 <u>click here</u> 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 <u>click here</u> 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.<br/>
+ * Take note of any unsaved data, and <u>click here</u> 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.<br/>
+ * Take note of any unsaved data, and <u>click here</u> 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.
+ * <p>
+ * 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.
+ * </p>
+ * <p>
+ * The default behavior is to show a notification, and restart the
+ * application the the user clicks the message. <br/>
+ * Instead of restarting the application, you can set a specific URL that
+ * the user is taken to.<br/>
+ * 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.
+ * </p>
+ * <p>
+ * The situations are:
+ * <li>Session expired: the user session has expired, usually due to
+ * inactivity.</li>
+ * <li>Communication error: the client failed to contact the server, or the
+ * server returned and invalid response.</li>
+ * <li>Internal error: unhandled critical server error (e.g out of memory,
+ * database crash)
+ * <li>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.
+ * </p>
+ */
+
+ 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.
+ *
+ * <p>
+ * 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.
+ * </p>
+ *
+ * <p>
+ * 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.
+ * </p>
+ *
+ * <p>
+ * 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.
+ * </p>
+ *
+ * @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<? extends Root> 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 <code>Root</code> class that should be used for
+ * a request. The class must have an accessible no-args constructor.
+ * <p>
+ * The default implementation uses the {@value #ROOT_PARAMETER} parameter
+ * from web.xml.
+ * </p>
+ * <p>
+ * 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.
+ * </p>
+ *
+ * @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, <code>null</code> 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 <code>null</code> 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, <code>null</code> 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 <code>null</code> 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 <code>null</code> if the
+ * annotation is not present on the class
+ */
+ private static <T extends Annotation> T getAnnotationFor(Class<?> type,
+ Class<T> 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}).
+ * <p>
+ * 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.
+ * </p>
+ *
+ * @param request
+ * the wrapped request to get information from
+ * @param response
+ * the response to which data can be written
+ * @return returns <code>true</code> if a {@link RequestHandler} has
+ * produced a response and <code>false</code> 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<RequestHandler>(
+ 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.
+ * <p>
+ * Handlers are called in reverse order of addition, so the most recently
+ * added handler will be called first.
+ * </p>
+ *
+ * @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<RequestHandler> 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
+ * <code>null</code> 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<Application> currentApplication = new ThreadLocal<Application>();
+
+ 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
+ * <code>null</code>
+ *
+ * @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.
+ * <p>
+ * 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.
+ * </p>
+ *
+ * @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.
+ * <p>
+ * Please note that this method can also return a newly created
+ * <code>Root</code> 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.
+ * </p>
+ *
+ * @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 <code>null</code> 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(ApplicationConnection.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.
+ * <p>
+ * 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.
+ * </p>
+ *
+ * @param rootPreserved
+ * <code>true</code>if 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 <code>true</code>if 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 <code>true</code> of the initialization is pending,
+ * <code>false</code> 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<Root> 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.
+ * <p>
+ * This is meant for framework internal use.
+ * </p>
+ *
+ * @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);
+ }
+
+ public void addBootstrapListener(BootstrapListener listener) {
+ eventRouter.addListener(BootstrapFragmentResponse.class, listener,
+ BOOTSTRAP_FRAGMENT_METHOD);
+ eventRouter.addListener(BootstrapPageResponse.class, listener,
+ BOOTSTRAP_PAGE_METHOD);
+ }
+
+ public void removeBootstrapListener(BootstrapListener listener) {
+ eventRouter.removeListener(BootstrapFragmentResponse.class, listener,
+ BOOTSTRAP_FRAGMENT_METHOD);
+ eventRouter.removeListener(BootstrapPageResponse.class, listener,
+ BOOTSTRAP_PAGE_METHOD);
+ }
+
+ public void modifyBootstrapResponse(BootstrapResponse response) {
+ eventRouter.fireEvent(response);
+ }
+}