/* * Copyright 2011 Vaadin Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ 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.EventObject; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.vaadin.annotations.PreserveOnRefresh; import com.vaadin.annotations.Theme; import com.vaadin.annotations.Title; 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.server.AbstractErrorMessage; import com.vaadin.server.ApplicationContext; import com.vaadin.server.BootstrapFragmentResponse; import com.vaadin.server.BootstrapListener; import com.vaadin.server.BootstrapPageResponse; import com.vaadin.server.BootstrapResponse; import com.vaadin.server.ChangeVariablesErrorEvent; import com.vaadin.server.ClientConnector; import com.vaadin.server.CombinedRequest; import com.vaadin.server.DeploymentConfiguration; import com.vaadin.server.GlobalResourceHandler; import com.vaadin.server.RequestHandler; import com.vaadin.server.ServletApplicationContext; import com.vaadin.server.Terminal; import com.vaadin.server.UIProvider; import com.vaadin.server.VaadinServlet; import com.vaadin.server.VariableOwner; import com.vaadin.server.WrappedRequest; import com.vaadin.server.WrappedRequest.BrowserDetails; import com.vaadin.server.WrappedResponse; import com.vaadin.shared.ui.ui.UIConstants; import com.vaadin.tools.ReflectTools; import com.vaadin.ui.AbstractComponent; import com.vaadin.ui.AbstractField; import com.vaadin.ui.Table; import com.vaadin.ui.UI; 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.server.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 UI} class. */ public static final String UI_PARAMETER = "UI"; 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 UI.LegacyWindow mainWindow; private String theme; private Map* 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 UI used as the default window */ public UI.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}
*/
@Override
protected
* {@inheritDoc}
*/
@Override
public UI getUIForRequest(WrappedRequest request) {
UI uiInstance = getUIInstance(request);
if (uiInstance.getUIId() == -1) {
// Not initialized -> Let go through createUIInstance to make it
// initialized
return null;
} else {
return uiInstance;
}
}
/**
* 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}
*/
@Override
public Class extends UI> getUIClass(WrappedRequest request) {
return getUIInstance(request).getClass();
}
/**
* Sets the application's theme.
*
* Note that this theme can be overridden for a specific UI with
* {@link Application#getThemeForUI(UI)}. Setting theme to be
*
* {@inheritDoc}
*/
@Override
public String getThemeForUI(WrappedRequest request,
Class extends UI> uiClass) {
return theme;
}
/**
*
* Gets a UI by name. Returns
* Note that removing window from the application does not close the
* browser window - the window is only removed from the server-side.
*
* Note that the returned set of windows can not be modified.
*
* 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()}).
*
* In effect this will cause the application stop returning any windows when
* asked. When the application is closed, close events are fired for its
* UIs, 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;
for (UI ui : getUIs()) {
ui.fireCloseEvent();
}
}
/**
* 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}.
*
* Application starts running when its
* {@link #start(URL, Properties, ApplicationContext)} method has been
* called and stops when the {@link #close()} is called.
*
* Main initializer of the application. The
* See {@link #start(URL, Properties, ApplicationContext)} how properties
* are defined.
*
* Desktop application just closes the application window and
* web-application redirects the browser to application main URL.
*
* Invoked by the terminal on any exception that occurs in application and
* is thrown by the
* You can safely override this method in your application in order to
* direct the errors to some other destination (for example log).
*
* 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 ServletApplicationContext}
* - 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}.
*
* 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:
* null
selects the default theme. For the available theme
* names, see the contents of the VAADIN/themes directory.
* 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)}
* null
if the application is
* not running or it does not contain a window corresponding to the
* name.
* null
to use
* the default window
*/
public UI.LegacyWindow getWindow(String name) {
return legacyUINames.get(name);
}
/**
* Counter to get unique names for windows with no explicit name
*/
private int namelessUIIndex = 0;
/**
* Adds a new browser level window to this application. Please note that
* UI doesn't have a name that is used in the URL - to add a named
* window you should instead use {@link #addWindow(UI, String)}
*
* @param uI
* the UI window to add to the application
* @return returns the name that has been assigned to the window
*
* @see #addWindow(UI, String)
*/
public void addWindow(UI.LegacyWindow uI) {
if (uI.getName() == null) {
String name = Integer.toString(namelessUIIndex++);
uI.setName(name);
}
legacyUINames.put(uI.getName(), uI);
uI.setApplication(this);
}
/**
* Removes the specified window from the application. This also removes
* all name mappings for the window (see {@link #addWindow(UI, String)
* and #getWindowName(UI)}.
*
* null
if the URL is not defined.
*
* @see Application#getURL()
*/
public URL getApplicationUrl() {
return applicationUrl;
}
/**
* Returns the deployment configuration used by this application.
*
* @return the deployment configuration.
*/
public DeploymentConfiguration getConfiguration() {
return configuration;
}
/**
* Gets the context application will be running in.
*
* @return the context application will be running in.
*
* @see Application#getContext()
*/
public ApplicationContext getContext() {
return context;
}
}
private final static Logger logger = Logger.getLogger(Application.class
.getName());
/**
* Application context the application is running in.
*/
private ApplicationContext context;
/**
* Deployment configuration for the application.
*/
private DeploymentConfiguration configuration;
/**
* The application's URL.
*/
private URL applicationUrl;
/**
* Application status.
*/
private volatile boolean applicationIsRunning = false;
/**
* Default locale of the application.
*/
private Locale locale;
/**
* URL where the user is redirected to on application close, or null if
* application is just closed without redirection.
*/
private String logoutURL = null;
/**
* 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 LinkedListtrue
if the application is running,
* false
if not.
*/
public boolean isRunning() {
return applicationIsRunning;
}
/**
* init
method is
* called by the framework when the application is started, and it should
* perform whatever initialization operations the application needs.
* 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 getProperties().getProperty(name);
}
/**
* 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;
}
/**
* 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.
* 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;
}
/**
* 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.
*
*
*
* Take note of any unsaved data, and click here to continue."
* Take note of any unsaved data, and click here to re-sync."
* Please enable cookies in your browser and click here to try again.
*
* 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: *
* Subclasses of Application may override this method to provide custom * logic for choosing what kind of UI to use. *
* The default implementation in {@link Application} uses the
* {@value #UI_PARAMETER} parameter from web.xml for finding the name of the
* UI class. If {@link DeploymentConfiguration#getClassLoader()} does not
* return null
, the returned {@link ClassLoader} is used for
* loading the UI class. Otherwise the {@link ClassLoader} used to load this
* class is used.
*
*
* Subclasses of Application may override this method to provide custom * logic for choosing how to create a suitable UI or for picking an already * created UI. If an existing UI is picked, care should be taken to avoid * keeping the same UI open in multiple browser windows, as that will cause * the states to go out of sync. *
* * @param request * @param uiClass * @return */ protectednull
is returned.
*
* TODO Tell what the default implementation does once it does something.
*
* @param uI
* the UI 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 getThemeForUI(WrappedRequest request,
Class extends UI> uiClass) {
Theme uiTheme = getAnnotationFor(uiClass, Theme.class);
if (uiTheme != null) {
return uiTheme.value();
} else {
return null;
}
}
/**
* Finds the widgetset to use for a specific UI. If no specific widgetset is
* required, null
is returned.
*
* The default implementation uses the @{@link Widgetset} annotation if it's
* defined for the UI class.
*
* @param request
* the wrapped request for which to get a widgetset
* @param uiClass
* the UI class to get a widgetset for
* @return the name of the widgetset, or
* 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.
*
* Handlers are called in reverse order of addition, so the most recently
* added handler will be called first.
*
* 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.
*
* Please note that this method can also return a newly created
*
* This is meant for framework internal use.
*
* Called by the framework at the end of every request.
*
* @see UI.CloseEvent
* @see UI.CloseListener
* @see #isUIAlive(UI)
*
* @since 7.0.0
*/
public void closeInactiveUIs() {
for (Iterator
* This timeout only has effect if cleanup of inactive UIs is enabled;
* otherwise heartbeat requests are enough to extend UI lifetime
* indefinitely.
*
* @see DeploymentConfiguration#isIdleUICleanupEnabled()
* @see #getHeartbeatTimeout()
* @see #closeInactiveUIs()
*
* @since 7.0.0
*
* @return The UIDL request timeout in seconds, or a negative number if
* timeout never occurs.
*/
protected int getUidlRequestTimeout() {
return configuration.isIdleUICleanupEnabled() ? getContext()
.getMaxInactiveInterval() : -1;
}
/**
* Returns whether the given UI is alive (the client-side actively
* communicates with the server) or whether it can be removed from the
* application and eventually collected.
*
* @since 7.0.0
*
* @param ui
* The UI whose status to check
* @return true if the UI is alive, false if it could be removed.
*/
protected boolean isUIAlive(UI ui) {
long now = System.currentTimeMillis();
if (getHeartbeatTimeout() >= 0
&& now - ui.getLastHeartbeatTime() > 1000 * getHeartbeatTimeout()) {
return false;
}
if (getUidlRequestTimeout() >= 0
&& now - ui.getLastUidlRequestTime() > 1000 * getUidlRequestTimeout()) {
return false;
}
return true;
}
/**
* Gets this application's global resource handler that takes care of
* serving connector resources that are not served by any single connector
* because e.g. because they are served with strong caching or because of
* legacy reasons.
*
* @param createOnDemand
* null
if the default
* widgetset should be used
*
* @since 7.0
*/
public String getWidgetsetForUI(WrappedRequest request,
Class extends UI> uiClass) {
Widgetset uiWidgetset = getAnnotationFor(uiClass, Widgetset.class);
if (uiWidgetset != null) {
return uiWidgetset.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 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 ArrayListnull
*
* @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.
* UI
which has not yet been initialized. You can use
* {@link #isUIInitPending(int)} with the UI's id ( {@link UI#getUIId()} to
* check whether the initialization is still pending.
* null
if no UI id is defined
*
* @since 7.0
*/
private static Integer getUIId(WrappedRequest request) {
if (request instanceof CombinedRequest) {
// Combined requests has the uiId parameter in the second request
CombinedRequest combinedRequest = (CombinedRequest) request;
request = combinedRequest.getSecondRequest();
}
String uiIdString = request.getParameter(UIConstants.UI_ID_PARAMETER);
Integer uiId = uiIdString == null ? null : new Integer(uiIdString);
return uiId;
}
/**
* Checks whether the same UI 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.
*
* @param request
* @param uiClass
*
* @return true
if the same UI instance should be reused e.g.
* when the browser window is refreshed.
*/
public boolean isUiPreserved(WrappedRequest request,
Class extends UI> uiClass) {
PreserveOnRefresh preserveOnRefresh = getAnnotationFor(uiClass,
PreserveOnRefresh.class);
return preserveOnRefresh != null;
}
/**
* Gets all the uIs of this application. This includes uIs that have been
* requested but not yet initialized. Please note, that uIs are not
* automatically removed e.g. if the browser window is closed and that there
* is no way to manually remove a UI. Inactive uIs 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 uIs is planned for an upcoming alpha release of Vaadin
* 7.
*
* @return a collection of uIs belonging to this application
*
* @since 7.0
*/
public Collectiontrue
if a resource handler should be initialized
* if there is no handler associated with this application.
* false if null should be returned
* if there is no registered handler.
* @return this application's global resource handler, or null
* if there is no handler and the createOnDemand parameter is
* false
.
*
* @since 7.0.0
*/
public GlobalResourceHandler getGlobalResourceHandler(boolean createOnDemand) {
if (globalResourceHandler == null && createOnDemand) {
globalResourceHandler = new GlobalResourceHandler();
addRequestHandler(globalResourceHandler);
}
return globalResourceHandler;
}
public String getPageTitleForUI(WrappedRequest request,
Class extends UI> uiClass) {
Title titleAnnotation = getAnnotationFor(uiClass, Title.class);
if (titleAnnotation == null) {
return null;
} else {
return titleAnnotation.value();
}
}
}