+ * Base class required for all Vaadin applications. This class provides all the
+ * basic services required by Vaadin. These services allow external discovery
+ * and manipulation of the user, {@link com.vaadin.ui.Window windows} and
+ * themes, and starting and stopping the application.
+ *
+ *
+ *
+ * As mentioned, all Vaadin applications must inherit this class. However, this
+ * is almost all of what one needs to do to create a fully functional
+ * application. The only thing a class inheriting the Application
+ * needs to do is implement the init method where it creates the
+ * windows it needs to perform its function. Note that all applications must
+ * have at least one window: the main window. The first unnamed window
+ * constructed by an application automatically becomes the main window which
+ * behaves just like other windows with one exception: when accessing windows
+ * using URLs the main window corresponds to the application URL whereas other
+ * windows correspond to a URL gotten by catenating the window's name to the
+ * application URL.
+ *
+ *
+ *
+ * See the class com.vaadin.demo.HelloWorld for a simple example of
+ * a fully working application.
+ *
+ *
+ *
+ * Window access.Application provides methods to
+ * list, add and remove the windows it contains.
+ *
+ *
+ *
+ * Execution control. This class includes method to start and
+ * finish the execution of the application. Being finished means basically that
+ * no windows will be available from the application anymore.
+ *
+ *
+ *
+ * Theme selection. The theme selection process allows a theme
+ * to be specified at three different levels. When a window's theme needs to be
+ * found out, the window itself is queried for a preferred theme. If the window
+ * does not prefer a specific theme, the application containing the window is
+ * queried. If neither the application prefers a theme, the default theme for
+ * the {@link com.vaadin.terminal.Terminal terminal} is used. The terminal
+ * always defines a default theme.
+ *
+ *
+ * @author Vaadin Ltd.
+ * @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 legacyRootNames = new HashMap();
+
+ /**
+ * Sets the main window of this application. Setting window as a main
+ * window of this application also adds the window to this application.
+ *
+ * @param mainWindow
+ * the root to set as the default window
+ */
+ public void setMainWindow(Root.LegacyWindow mainWindow) {
+ if (this.mainWindow != null) {
+ throw new IllegalStateException(
+ "mainWindow has already been set");
+ }
+ if (mainWindow.getApplication() == null) {
+ mainWindow.setApplication(this);
+ } else if (mainWindow.getApplication() != this) {
+ throw new IllegalStateException(
+ "mainWindow is attached to another application");
+ }
+ this.mainWindow = mainWindow;
+ }
+
+ /**
+ * Gets the mainWindow of the application.
+ *
+ *
+ * The main window is the window attached to the application URL (
+ * {@link #getURL()}) and thus which is show by default to the user.
+ *
+ *
+ * Note that each application must have at least one main window.
+ *
+ *
+ * @return the root used as the default window
+ */
+ public Root.LegacyWindow getMainWindow() {
+ return mainWindow;
+ }
+
+ /**
+ * This implementation simulates the way of finding a window for a
+ * request by extracting a window name from the requested path and
+ * passes that name to {@link #getWindow(String)}.
+ *
+ * {@inheritDoc}
+ *
+ * @see #getWindow(String)
+ * @see Application#getRoot(WrappedRequest)
+ */
+
+ @Override
+ public Root.LegacyWindow getRoot(WrappedRequest request) {
+ String pathInfo = request.getRequestPathInfo();
+ String name = null;
+ if (pathInfo != null && pathInfo.length() > 0) {
+ Matcher matcher = WINDOW_NAME_PATTERN.matcher(pathInfo);
+ if (matcher.matches()) {
+ // Skip the initial slash
+ name = matcher.group(1);
+ }
+ }
+ Root.LegacyWindow window = getWindow(name);
+ if (window != null) {
+ return window;
+ }
+ return mainWindow;
+ }
+
+ /**
+ * Sets the application's theme.
+ *
+ * Note that this theme can be overridden for a specific root with
+ * {@link Application#getThemeForRoot(Root)}. Setting theme to be
+ * null selects the default theme. For the available theme
+ * names, see the contents of the VAADIN/themes directory.
+ *
+ *
+ * @param theme
+ * the new theme for this application.
+ */
+ public void setTheme(String theme) {
+ this.theme = theme;
+ }
+
+ /**
+ * Gets the application's theme. The application's theme is the default
+ * theme used by all the roots for which a theme is not explicitly
+ * defined. If the application theme is not explicitly set,
+ * null is returned.
+ *
+ * @return the name of the application's theme.
+ */
+ public String getTheme() {
+ return theme;
+ }
+
+ /**
+ * This implementation returns the theme that has been set using
+ * {@link #setTheme(String)}
+ *
+ * Gets a root by name. Returns null if the application is
+ * not running or it does not contain a window corresponding to the
+ * name.
+ *
+ *
+ * @param name
+ * the name of the requested window
+ * @return a root corresponding to the name, or null to use
+ * the default window
+ */
+ public Root.LegacyWindow getWindow(String name) {
+ return legacyRootNames.get(name);
+ }
+
+ /**
+ * Counter to get unique names for windows with no explicit name
+ */
+ private int namelessRootIndex = 0;
+
+ /**
+ * Adds a new browser level window to this application. Please note that
+ * Root doesn't have a name that is used in the URL - to add a named
+ * window you should instead use {@link #addWindow(Root, String)}
+ *
+ * @param root
+ * the root window to add to the application
+ * @return returns the name that has been assigned to the window
+ *
+ * @see #addWindow(Root, String)
+ */
+ public void addWindow(Root.LegacyWindow root) {
+ if (root.getName() == null) {
+ String name = Integer.toString(namelessRootIndex++);
+ root.setName(name);
+ }
+
+ legacyRootNames.put(root.getName(), root);
+ root.setApplication(this);
+ }
+
+ /**
+ * Removes the specified window from the application. This also removes
+ * all name mappings for the window (see
+ * {@link #addWindow(Root, String) and #getWindowName(Root)}.
+ *
+ *
+ * Note that removing window from the application does not close the
+ * browser window - the window is only removed from the server-side.
+ *
+ *
+ * @param root
+ * the root to remove
+ */
+ public void removeWindow(Root.LegacyWindow root) {
+ for (Entry entry : legacyRootNames
+ .entrySet()) {
+ if (entry.getValue() == root) {
+ legacyRootNames.remove(entry.getKey());
+ }
+ }
+ }
+
+ /**
+ * Gets the set of windows contained by the application.
+ *
+ *
+ * Note that the returned set of windows can not be modified.
+ *
+ *
+ * @return the unmodifiable collection of windows.
+ */
+ public Collection getWindows() {
+ return Collections.unmodifiableCollection(legacyRootNames.values());
+ }
+ }
+
+ /**
+ * An event sent to {@link #start(ApplicationStartEvent)} when a new
+ * Application is being started.
+ *
+ * @since 7.0
+ */
+ public static class ApplicationStartEvent implements Serializable {
+ private final URL applicationUrl;
+
+ private final Properties applicationProperties;
+
+ private final ApplicationContext context;
+
+ private final boolean productionMode;
+
+ /**
+ * @param applicationUrl
+ * the URL the application should respond to.
+ * @param applicationProperties
+ * the Application properties as specified by the deployment
+ * configuration.
+ * @param context
+ * the context application will be running in.
+ * @param productionMode
+ * flag indicating whether the application is running in
+ * production mode.
+ */
+ public ApplicationStartEvent(URL applicationUrl,
+ Properties applicationProperties, ApplicationContext context,
+ boolean productionMode) {
+ this.applicationUrl = applicationUrl;
+ this.applicationProperties = applicationProperties;
+ this.context = context;
+ this.productionMode = productionMode;
+ }
+
+ /**
+ * Gets the URL the application should respond to.
+ *
+ * @return the URL the application should respond to or
+ * null if the URL is not defined.
+ *
+ * @see Application#getURL()
+ */
+ public URL getApplicationUrl() {
+ return applicationUrl;
+ }
+
+ /**
+ * Gets the Application properties as specified by the deployment
+ * configuration.
+ *
+ * @return the properties configured for the applciation.
+ *
+ * @see Application#getProperty(String)
+ */
+ public Properties getApplicationProperties() {
+ return applicationProperties;
+ }
+
+ /**
+ * Gets the context application will be running in.
+ *
+ * @return the context application will be running in.
+ *
+ * @see Application#getContext()
+ */
+ public ApplicationContext getContext() {
+ return context;
+ }
+
+ /**
+ * Checks whether the application is running in production mode.
+ *
+ * @return true if in production mode, else
+ * false
+ *
+ * @see Application#isProductionMode()
+ */
+ public boolean isProductionMode() {
+ return productionMode;
+ }
+ }
+
+ private final static Logger logger = Logger.getLogger(Application.class
+ .getName());
+
+ /**
+ * Application context the application is running in.
+ */
+ private ApplicationContext context;
+
+ /**
+ * The current user or null if no user has logged in.
+ */
+ private Object user;
+
+ /**
+ * The application's URL.
+ */
+ private URL applicationUrl;
+
+ /**
+ * Application status.
+ */
+ private volatile boolean applicationIsRunning = false;
+
+ /**
+ * Application properties.
+ */
+ private Properties properties;
+
+ /**
+ * Default locale of the application.
+ */
+ private Locale locale;
+
+ /**
+ * List of listeners listening user changes.
+ */
+ private LinkedList userChangeListeners = null;
+
+ /**
+ * Application resource mapping: key <-> resource.
+ */
+ private final Hashtable resourceKeyMap = new Hashtable();
+
+ private final Hashtable keyResourceMap = new Hashtable();
+
+ private long lastResourceKeyNumber = 0;
+
+ /**
+ * URL where the user is redirected to on application close, or null if
+ * application is just closed without redirection.
+ */
+ private String logoutURL = null;
+
+ /**
+ * The default SystemMessages (read-only). Change by overriding
+ * getSystemMessages() and returning CustomizedSystemMessages
+ */
+ private static final SystemMessages DEFAULT_SYSTEM_MESSAGES = new SystemMessages();
+
+ /**
+ * Application wide error handler which is used by default if an error is
+ * left unhandled.
+ */
+ private Terminal.ErrorListener errorHandler = this;
+
+ /**
+ * The converter factory that is used to provide default converters for the
+ * application.
+ */
+ private ConverterFactory converterFactory = new DefaultConverterFactory();
+
+ private LinkedList requestHandlers = new LinkedList();
+
+ private int nextRootId = 0;
+ private Map roots = new HashMap();
+
+ private boolean productionMode = true;
+
+ private final Map retainOnRefreshRoots = new HashMap();
+
+ private final EventRouter eventRouter = new EventRouter();
+
+ /**
+ * Keeps track of which roots have been inited.
+ *
+ * TODO Investigate whether this might be derived from the different states
+ * in getRootForRrequest.
+ *
+ */
+ private Set initedRoots = new HashSet();
+
+ /**
+ * Gets the user of the application.
+ *
+ *
+ * Vaadin doesn't define of use user object in any way - it only provides
+ * this getter and setter methods for convenience. The user is any object
+ * that has been stored to the application with {@link #setUser(Object)}.
+ *
+ *
+ * @return the User of the application.
+ */
+ public Object getUser() {
+ return user;
+ }
+
+ /**
+ *
+ * Sets the user of the application instance. An application instance may
+ * have a user associated to it. This can be set in login procedure or
+ * application initialization.
+ *
+ *
+ * A component performing the user login procedure can assign the user
+ * property of the application and make the user object available to other
+ * components of the application.
+ *
+ *
+ * Vaadin doesn't define of use user object in any way - it only provides
+ * getter and setter methods for convenience. The user reference stored to
+ * the application can be read with {@link #getUser()}.
+ *
+ *
+ * @param user
+ * the new user.
+ */
+ public void setUser(Object user) {
+ final Object prevUser = this.user;
+ if (user == prevUser || (user != null && user.equals(prevUser))) {
+ return;
+ }
+
+ this.user = user;
+ if (userChangeListeners != null) {
+ final Object[] listeners = userChangeListeners.toArray();
+ final UserChangeEvent event = new UserChangeEvent(this, user,
+ prevUser);
+ for (int i = 0; i < listeners.length; i++) {
+ ((UserChangeListener) listeners[i])
+ .applicationUserChanged(event);
+ }
+ }
+ }
+
+ /**
+ * Gets the URL of the application.
+ *
+ *
+ * This is the URL what can be entered to a browser window to start the
+ * application. Navigating to the application URL shows the main window (
+ * {@link #getMainWindow()}) of the application. Note that the main window
+ * can also be shown by navigating to the window url (
+ * {@link com.vaadin.ui.Window#getURL()}).
+ *
+ * In effect this will cause the application stop returning any windows when
+ * asked. When the application is closed, its state is removed from the
+ * session and the browser window is redirected to the application logout
+ * url set with {@link #setLogoutURL(String)}. If the logout url has not
+ * been set, the browser window is reloaded and the application is
+ * restarted.
+ *
+ * .
+ */
+ public void close() {
+ applicationIsRunning = false;
+ }
+
+ /**
+ * Starts the application on the given URL.
+ *
+ *
+ * This method is called by Vaadin framework when a user navigates to the
+ * application. After this call the application corresponds to the given URL
+ * and it will return windows when asked for them. There is no need to call
+ * this method directly.
+ *
+ *
+ *
+ * Application properties are defined by servlet configuration object
+ * {@link javax.servlet.ServletConfig} and they are overridden by
+ * context-wide initialization parameters
+ * {@link javax.servlet.ServletContext}.
+ *
+ *
+ * @param event
+ * the application start event containing details required for
+ * starting the application.
+ *
+ */
+ public void start(ApplicationStartEvent event) {
+ applicationUrl = event.getApplicationUrl();
+ productionMode = event.isProductionMode();
+ properties = event.getApplicationProperties();
+ context = event.getContext();
+ init();
+ applicationIsRunning = true;
+ }
+
+ /**
+ * Tests if the application is running or if it has been finished.
+ *
+ *
+ * Application starts running when its
+ * {@link #start(URL, Properties, ApplicationContext)} method has been
+ * called and stops when the {@link #close()} is called.
+ *
+ *
+ * @return true if the application is running,
+ * false if not.
+ */
+ public boolean isRunning() {
+ return applicationIsRunning;
+ }
+
+ /**
+ *
+ * Main initializer of the application. The init method is
+ * called by the framework when the application is started, and it should
+ * perform whatever initialization operations the application needs.
+ *
+ */
+ public void init() {
+ // Default implementation does nothing
+ }
+
+ /**
+ * Returns an enumeration of all the names in this application.
+ *
+ *
+ * See {@link #start(URL, Properties, ApplicationContext)} how properties
+ * are defined.
+ *
+ *
+ * @return an enumeration of all the keys in this property list, including
+ * the keys in the default property list.
+ *
+ */
+ public Enumeration> getPropertyNames() {
+ return properties.propertyNames();
+ }
+
+ /**
+ * Searches for the property with the specified name in this application.
+ * This method returns null if the property is not found.
+ *
+ * See {@link #start(URL, Properties, ApplicationContext)} how properties
+ * are defined.
+ *
+ * @param name
+ * the name of the property.
+ * @return the value in this property list with the specified key value.
+ */
+ public String getProperty(String name) {
+ return properties.getProperty(name);
+ }
+
+ /**
+ * Adds new resource to the application. The resource can be accessed by the
+ * user of the application.
+ *
+ * @param resource
+ * the resource to add.
+ */
+ public void addResource(ApplicationResource resource) {
+
+ // Check if the resource is already mapped
+ if (resourceKeyMap.containsKey(resource)) {
+ return;
+ }
+
+ // Generate key
+ final String key = String.valueOf(++lastResourceKeyNumber);
+
+ // Add the resource to mappings
+ resourceKeyMap.put(resource, key);
+ keyResourceMap.put(key, resource);
+ }
+
+ /**
+ * Removes the resource from the application.
+ *
+ * @param resource
+ * the resource to remove.
+ */
+ public void removeResource(ApplicationResource resource) {
+ final Object key = resourceKeyMap.get(resource);
+ if (key != null) {
+ resourceKeyMap.remove(resource);
+ keyResourceMap.remove(key);
+ }
+ }
+
+ /**
+ * Gets the relative uri of the resource. This method is intended to be
+ * called only be the terminal implementation.
+ *
+ * This method can only be called from within the processing of a UIDL
+ * request, not from a background thread.
+ *
+ * @param resource
+ * the resource to get relative location.
+ * @return the relative uri of the resource or null if called in a
+ * background thread
+ *
+ * @deprecated this method is intended to be used by the terminal only. It
+ * may be removed or moved in the future.
+ */
+ @Deprecated
+ public String getRelativeLocation(ApplicationResource resource) {
+
+ // Gets the key
+ final String key = resourceKeyMap.get(resource);
+
+ // If the resource is not registered, return null
+ if (key == null) {
+ return null;
+ }
+
+ return context.generateApplicationResourceURL(resource, key);
+ }
+
+ /**
+ * Gets the default locale for this application.
+ *
+ * By default this is the preferred locale of the user using the
+ * application. In most cases it is read from the browser defaults.
+ *
+ * @return the locale of this application.
+ */
+ public Locale getLocale() {
+ if (locale != null) {
+ return locale;
+ }
+ return Locale.getDefault();
+ }
+
+ /**
+ * Sets the default locale for this application.
+ *
+ * By default this is the preferred locale of the user using the
+ * application. In most cases it is read from the browser defaults.
+ *
+ * @param locale
+ * the Locale object.
+ *
+ */
+ public void setLocale(Locale locale) {
+ this.locale = locale;
+ }
+
+ /**
+ *
+ * An event that characterizes a change in the current selection.
+ *
+ * Application user change event sent when the setUser is called to change
+ * the current user of the application.
+ *
+ * @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 null
+ */
+ public Object getPreviousUser() {
+ return prevUser;
+ }
+
+ /**
+ * Gets the application where the user change occurred.
+ *
+ * @return the Application.
+ */
+ public Application getApplication() {
+ return (Application) getSource();
+ }
+ }
+
+ /**
+ * The UserChangeListener interface for listening application
+ * user changes.
+ *
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ public interface UserChangeListener extends EventListener, Serializable {
+
+ /**
+ * The applicationUserChanged method Invoked when the
+ * application user has changed.
+ *
+ * @param event
+ * the change event.
+ */
+ public void applicationUserChanged(Application.UserChangeEvent event);
+ }
+
+ /**
+ * Adds the user change listener.
+ *
+ * This allows one to get notification each time {@link #setUser(Object)} is
+ * called.
+ *
+ * @param listener
+ * the user change listener to add.
+ */
+ public void addListener(UserChangeListener listener) {
+ if (userChangeListeners == null) {
+ userChangeListeners = new LinkedList();
+ }
+ userChangeListeners.add(listener);
+ }
+
+ /**
+ * Removes the user change listener.
+ *
+ * @param listener
+ * the user change listener to remove.
+ */
+ public void removeListener(UserChangeListener listener) {
+ if (userChangeListeners == null) {
+ return;
+ }
+ userChangeListeners.remove(listener);
+ if (userChangeListeners.isEmpty()) {
+ userChangeListeners = null;
+ }
+ }
+
+ /**
+ * Window detach event.
+ *
+ * This event is sent each time a window is removed from the application
+ * with {@link com.vaadin.Application#removeWindow(Window)}.
+ */
+ public class WindowDetachEvent extends EventObject {
+
+ private final Window window;
+
+ /**
+ * Creates a event.
+ *
+ * @param window
+ * the Detached window.
+ */
+ public WindowDetachEvent(Window window) {
+ super(Application.this);
+ this.window = window;
+ }
+
+ /**
+ * Gets the detached window.
+ *
+ * @return the detached window.
+ */
+ public Window getWindow() {
+ return window;
+ }
+
+ /**
+ * Gets the application from which the window was detached.
+ *
+ * @return the Application.
+ */
+ public Application getApplication() {
+ return (Application) getSource();
+ }
+ }
+
+ /**
+ * Window attach event.
+ *
+ * This event is sent each time a window is attached tothe application with
+ * {@link com.vaadin.Application#addWindow(Window)}.
+ */
+ public class WindowAttachEvent extends EventObject {
+
+ private final Window window;
+
+ /**
+ * Creates a event.
+ *
+ * @param window
+ * the Attached window.
+ */
+ public WindowAttachEvent(Window window) {
+ super(Application.this);
+ this.window = window;
+ }
+
+ /**
+ * Gets the attached window.
+ *
+ * @return the attached window.
+ */
+ public Window getWindow() {
+ return window;
+ }
+
+ /**
+ * Gets the application to which the window was attached.
+ *
+ * @return the Application.
+ */
+ public Application getApplication() {
+ return (Application) getSource();
+ }
+ }
+
+ /**
+ * Window attach listener interface.
+ */
+ public interface WindowAttachListener extends Serializable {
+
+ /**
+ * Window attached
+ *
+ * @param event
+ * the window attach event.
+ */
+ public void windowAttached(WindowAttachEvent event);
+ }
+
+ /**
+ * Window detach listener interface.
+ */
+ public interface WindowDetachListener extends Serializable {
+
+ /**
+ * Window detached.
+ *
+ * @param event
+ * the window detach event.
+ */
+ public void windowDetached(WindowDetachEvent event);
+ }
+
+ /**
+ * Returns the URL user is redirected to on application close. If the URL is
+ * null, the application is closed normally as defined by the
+ * application running environment.
+ *
+ * Desktop application just closes the application window and
+ * web-application redirects the browser to application main URL.
+ *
+ *
+ * @return the URL.
+ */
+ public String getLogoutURL() {
+ return logoutURL;
+ }
+
+ /**
+ * Sets the URL user is redirected to on application close. If the URL is
+ * null, the application is closed normally as defined by the
+ * application running environment: Desktop application just closes the
+ * application window and web-application redirects the browser to
+ * application main URL.
+ *
+ * @param logoutURL
+ * the logoutURL to set.
+ */
+ public void setLogoutURL(String logoutURL) {
+ this.logoutURL = logoutURL;
+ }
+
+ /**
+ * Gets the SystemMessages for this application. SystemMessages are used to
+ * notify the user of various critical situations that can occur, such as
+ * session expiration, client/server out of sync, and internal server error.
+ *
+ * You can customize the messages by "overriding" this method and returning
+ * {@link CustomizedSystemMessages}. To "override" this method, re-implement
+ * this method in your application (the class that extends
+ * {@link Application}). Even though overriding static methods is not
+ * possible in Java, Vaadin selects to call the static method from the
+ * subclass instead of the original {@link #getSystemMessages()} if such a
+ * method exists.
+ *
+ * @return the SystemMessages for this application
+ */
+ public static SystemMessages getSystemMessages() {
+ return DEFAULT_SYSTEM_MESSAGES;
+ }
+
+ /**
+ *
+ * Invoked by the terminal on any exception that occurs in application and
+ * is thrown by the setVariable to the terminal. The default
+ * implementation sets the exceptions as ComponentErrors to the
+ * component that initiated the exception and prints stack trace to standard
+ * error stream.
+ *
+ *
+ * You can safely override this method in your application in order to
+ * direct the errors to some other destination (for example log).
+ *
+ *
+ * @param event
+ * the change event.
+ * @see com.vaadin.terminal.Terminal.ErrorListener#terminalError(com.vaadin.terminal.Terminal.ErrorEvent)
+ */
+
+ @Override
+ public void terminalError(Terminal.ErrorEvent event) {
+ final Throwable t = event.getThrowable();
+ if (t instanceof SocketException) {
+ // Most likely client browser closed socket
+ getLogger().info(
+ "SocketException in CommunicationManager."
+ + " Most likely client (browser) closed socket.");
+ return;
+ }
+
+ // Finds the original source of the error/exception
+ Object owner = null;
+ if (event instanceof VariableOwner.ErrorEvent) {
+ owner = ((VariableOwner.ErrorEvent) event).getVariableOwner();
+ } else if (event instanceof ChangeVariablesErrorEvent) {
+ owner = ((ChangeVariablesErrorEvent) event).getComponent();
+ }
+
+ // Shows the error in AbstractComponent
+ if (owner instanceof AbstractComponent) {
+ ((AbstractComponent) owner).setComponentError(AbstractErrorMessage
+ .getErrorMessageForException(t));
+ }
+
+ // also print the error on console
+ getLogger().log(Level.SEVERE, "Terminal error:", t);
+ }
+
+ /**
+ * Gets the application context.
+ *
+ * The application context is the environment where the application is
+ * running in. The actual implementation class of may contains quite a lot
+ * more functionality than defined in the {@link ApplicationContext}
+ * interface.
+ *
+ *
+ * By default, when you are deploying your application to a servlet
+ * container, the implementation class is {@link WebApplicationContext} -
+ * you can safely cast to this class and use the methods from there. When
+ * you are deploying your application as a portlet, context implementation
+ * is {@link PortletApplicationContext}.
+ *
+ *
+ * @return the application context.
+ */
+ public ApplicationContext getContext() {
+ return context;
+ }
+
+ /**
+ * Override this method to return correct version number of your
+ * Application. Version information is delivered for example to Testing
+ * Tools test results. By default this returns a string "NONVERSIONED".
+ *
+ * @return version string
+ */
+ public String getVersion() {
+ return "NONVERSIONED";
+ }
+
+ /**
+ * Gets the application error handler.
+ *
+ * The default error handler is the application itself.
+ *
+ * @return Application error handler
+ */
+ public Terminal.ErrorListener getErrorHandler() {
+ return errorHandler;
+ }
+
+ /**
+ * Sets the application error handler.
+ *
+ * The default error handler is the application itself. By overriding this,
+ * you can redirect the error messages to your selected target (log for
+ * example).
+ *
+ * @param errorHandler
+ */
+ public void setErrorHandler(Terminal.ErrorListener errorHandler) {
+ this.errorHandler = errorHandler;
+ }
+
+ /**
+ * Gets the {@link ConverterFactory} used to locate a suitable
+ * {@link Converter} for fields in the application.
+ *
+ * See {@link #setConverterFactory(ConverterFactory)} for more details
+ *
+ * @return The converter factory used in the application
+ */
+ public ConverterFactory getConverterFactory() {
+ return converterFactory;
+ }
+
+ /**
+ * Sets the {@link ConverterFactory} used to locate a suitable
+ * {@link Converter} for fields in the application.
+ *
+ * The {@link ConverterFactory} is used to find a suitable converter when
+ * binding data to a UI component and the data type does not match the UI
+ * component type, e.g. binding a Double to a TextField (which is based on a
+ * String).
+ *
+ *
+ * The {@link Converter} for an individual field can be overridden using
+ * {@link AbstractField#setConverter(Converter)} and for individual property
+ * ids in a {@link Table} using
+ * {@link Table#setConverter(Object, Converter)}.
+ *
+ *
+ * The converter factory must never be set to null.
+ *
+ * @param converterFactory
+ * The converter factory used in the application
+ */
+ public void setConverterFactory(ConverterFactory converterFactory) {
+ this.converterFactory = converterFactory;
+ }
+
+ /**
+ * Contains the system messages used to notify the user about various
+ * critical situations that can occur.
+ *
+ * Customize by overriding the static
+ * {@link Application#getSystemMessages()} and returning
+ * {@link CustomizedSystemMessages}.
+ *
+ *
+ * The defaults defined in this class are:
+ *
+ *
sessionExpiredURL = null
+ *
sessionExpiredNotificationEnabled = true
+ *
sessionExpiredCaption = ""
+ *
sessionExpiredMessage =
+ * "Take note of any unsaved data, and click here to continue."
communicationErrorMessage =
+ * "Take note of any unsaved data, and click here to continue."
+ *
internalErrorURL = null
+ *
internalErrorNotificationEnabled = true
+ *
internalErrorCaption = "Internal error"
+ *
internalErrorMessage = "Please notify the administrator.
+ * Take note of any unsaved data, and click here to continue."
+ *
outOfSyncURL = null
+ *
outOfSyncNotificationEnabled = true
+ *
outOfSyncCaption = "Out of sync"
+ *
outOfSyncMessage = "Something has caused us to be out of sync
+ * with the server.
+ * Take note of any unsaved data, and click here to re-sync."
+ *
cookiesDisabledURL = null
+ *
cookiesDisabledNotificationEnabled = true
+ *
cookiesDisabledCaption = "Cookies disabled"
+ *
cookiesDisabledMessage = "This application requires cookies to
+ * function.
+ * Please enable cookies in your browser and click here to try again.
+ *
+ *
+ *
+ *
+ */
+ public static class SystemMessages implements Serializable {
+ protected String sessionExpiredURL = null;
+ protected boolean sessionExpiredNotificationEnabled = true;
+ protected String sessionExpiredCaption = "Session Expired";
+ protected String sessionExpiredMessage = "Take note of any unsaved data, and click here to continue.";
+
+ protected String communicationErrorURL = null;
+ protected boolean communicationErrorNotificationEnabled = true;
+ protected String communicationErrorCaption = "Communication problem";
+ protected String communicationErrorMessage = "Take note of any unsaved data, and click here to continue.";
+
+ protected String authenticationErrorURL = null;
+ protected boolean authenticationErrorNotificationEnabled = true;
+ protected String authenticationErrorCaption = "Authentication problem";
+ protected String authenticationErrorMessage = "Take note of any unsaved data, and click here to continue.";
+
+ protected String internalErrorURL = null;
+ protected boolean internalErrorNotificationEnabled = true;
+ protected String internalErrorCaption = "Internal error";
+ protected String internalErrorMessage = "Please notify the administrator. Take note of any unsaved data, and click here to continue.";
+
+ protected String outOfSyncURL = null;
+ protected boolean outOfSyncNotificationEnabled = true;
+ protected String outOfSyncCaption = "Out of sync";
+ protected String outOfSyncMessage = "Something has caused us to be out of sync with the server. Take note of any unsaved data, and click here to re-sync.";
+
+ protected String cookiesDisabledURL = null;
+ protected boolean cookiesDisabledNotificationEnabled = true;
+ protected String cookiesDisabledCaption = "Cookies disabled";
+ protected String cookiesDisabledMessage = "This application requires cookies to function. Please enable cookies in your browser and click here to try again.";
+
+ /**
+ * Use {@link CustomizedSystemMessages} to customize
+ */
+ private SystemMessages() {
+
+ }
+
+ /**
+ * @return null to indicate that the application will be restarted after
+ * session expired message has been shown.
+ */
+ public String getSessionExpiredURL() {
+ return sessionExpiredURL;
+ }
+
+ /**
+ * @return true to show session expiration message.
+ */
+ public boolean isSessionExpiredNotificationEnabled() {
+ return sessionExpiredNotificationEnabled;
+ }
+
+ /**
+ * @return "" to show no caption.
+ */
+ public String getSessionExpiredCaption() {
+ return (sessionExpiredNotificationEnabled ? sessionExpiredCaption
+ : null);
+ }
+
+ /**
+ * @return
+ * "Take note of any unsaved data, and click here to continue."
+ */
+ public String getSessionExpiredMessage() {
+ return (sessionExpiredNotificationEnabled ? sessionExpiredMessage
+ : null);
+ }
+
+ /**
+ * @return null to reload the application after communication error
+ * message.
+ */
+ public String getCommunicationErrorURL() {
+ return communicationErrorURL;
+ }
+
+ /**
+ * @return true to show the communication error message.
+ */
+ public boolean isCommunicationErrorNotificationEnabled() {
+ return communicationErrorNotificationEnabled;
+ }
+
+ /**
+ * @return "Communication problem"
+ */
+ public String getCommunicationErrorCaption() {
+ return (communicationErrorNotificationEnabled ? communicationErrorCaption
+ : null);
+ }
+
+ /**
+ * @return
+ * "Take note of any unsaved data, and click here to continue."
+ */
+ public String getCommunicationErrorMessage() {
+ return (communicationErrorNotificationEnabled ? communicationErrorMessage
+ : null);
+ }
+
+ /**
+ * @return null to reload the application after authentication error
+ * message.
+ */
+ public String getAuthenticationErrorURL() {
+ return authenticationErrorURL;
+ }
+
+ /**
+ * @return true to show the authentication error message.
+ */
+ public boolean isAuthenticationErrorNotificationEnabled() {
+ return authenticationErrorNotificationEnabled;
+ }
+
+ /**
+ * @return "Authentication problem"
+ */
+ public String getAuthenticationErrorCaption() {
+ return (authenticationErrorNotificationEnabled ? authenticationErrorCaption
+ : null);
+ }
+
+ /**
+ * @return
+ * "Take note of any unsaved data, and click here to continue."
+ */
+ public String getAuthenticationErrorMessage() {
+ return (authenticationErrorNotificationEnabled ? authenticationErrorMessage
+ : null);
+ }
+
+ /**
+ * @return null to reload the current URL after internal error message
+ * has been shown.
+ */
+ public String getInternalErrorURL() {
+ return internalErrorURL;
+ }
+
+ /**
+ * @return true to enable showing of internal error message.
+ */
+ public boolean isInternalErrorNotificationEnabled() {
+ return internalErrorNotificationEnabled;
+ }
+
+ /**
+ * @return "Internal error"
+ */
+ public String getInternalErrorCaption() {
+ return (internalErrorNotificationEnabled ? internalErrorCaption
+ : null);
+ }
+
+ /**
+ * @return "Please notify the administrator.
+ * Take note of any unsaved data, and click here to
+ * continue."
+ */
+ public String getInternalErrorMessage() {
+ return (internalErrorNotificationEnabled ? internalErrorMessage
+ : null);
+ }
+
+ /**
+ * @return null to reload the application after out of sync message.
+ */
+ public String getOutOfSyncURL() {
+ return outOfSyncURL;
+ }
+
+ /**
+ * @return true to enable showing out of sync message
+ */
+ public boolean isOutOfSyncNotificationEnabled() {
+ return outOfSyncNotificationEnabled;
+ }
+
+ /**
+ * @return "Out of sync"
+ */
+ public String getOutOfSyncCaption() {
+ return (outOfSyncNotificationEnabled ? outOfSyncCaption : null);
+ }
+
+ /**
+ * @return "Something has caused us to be out of sync with the server.
+ * Take note of any unsaved data, and click here to
+ * re-sync."
+ */
+ public String getOutOfSyncMessage() {
+ return (outOfSyncNotificationEnabled ? outOfSyncMessage : null);
+ }
+
+ /**
+ * Returns the URL the user should be redirected to after dismissing the
+ * "you have to enable your cookies" message. Typically null.
+ *
+ * @return A URL the user should be redirected to after dismissing the
+ * message or null to reload the current URL.
+ */
+ public String getCookiesDisabledURL() {
+ return cookiesDisabledURL;
+ }
+
+ /**
+ * Determines if "cookies disabled" messages should be shown to the end
+ * user or not. If the notification is disabled the user will be
+ * immediately redirected to the URL returned by
+ * {@link #getCookiesDisabledURL()}.
+ *
+ * @return true to show "cookies disabled" messages to the end user,
+ * false to redirect to the given URL directly
+ */
+ public boolean isCookiesDisabledNotificationEnabled() {
+ return cookiesDisabledNotificationEnabled;
+ }
+
+ /**
+ * Returns the caption of the message shown to the user when cookies are
+ * disabled in the browser.
+ *
+ * @return The caption of the "cookies disabled" message
+ */
+ public String getCookiesDisabledCaption() {
+ return (cookiesDisabledNotificationEnabled ? cookiesDisabledCaption
+ : null);
+ }
+
+ /**
+ * Returns the message shown to the user when cookies are disabled in
+ * the browser.
+ *
+ * @return The "cookies disabled" message
+ */
+ public String getCookiesDisabledMessage() {
+ return (cookiesDisabledNotificationEnabled ? cookiesDisabledMessage
+ : null);
+ }
+
+ }
+
+ /**
+ * Contains the system messages used to notify the user about various
+ * critical situations that can occur.
+ *
+ * Vaadin gets the SystemMessages from your application by calling a static
+ * getSystemMessages() method. By default the
+ * Application.getSystemMessages() is used. You can customize this by
+ * defining a static MyApplication.getSystemMessages() and returning
+ * CustomizedSystemMessages. Note that getSystemMessages() is static -
+ * changing the system messages will by default change the message for all
+ * users of the application.
+ *
+ *
+ * The default behavior is to show a notification, and restart the
+ * application the the user clicks the message.
+ * Instead of restarting the application, you can set a specific URL that
+ * the user is taken to.
+ * Setting both caption and message to null will restart the application (or
+ * go to the specified URL) without displaying a notification.
+ * set*NotificationEnabled(false) will achieve the same thing.
+ *
+ *
+ * The situations are:
+ *
Session expired: the user session has expired, usually due to
+ * inactivity.
+ *
Communication error: the client failed to contact the server, or the
+ * server returned and invalid response.
+ *
Internal error: unhandled critical server error (e.g out of memory,
+ * database crash)
+ *
Out of sync: the client is not in sync with the server. E.g the user
+ * opens two windows showing the same application, but the application does
+ * not support this and uses the same Window instance. When the user makes
+ * changes in one of the windows - the other window is no longer in sync,
+ * and (for instance) pressing a button that is no longer present in the UI
+ * will cause a out-of-sync -situation.
+ *
+ */
+
+ public static class CustomizedSystemMessages extends SystemMessages
+ implements Serializable {
+
+ /**
+ * Sets the URL to go to when the session has expired.
+ *
+ * @param sessionExpiredURL
+ * the URL to go to, or null to reload current
+ */
+ public void setSessionExpiredURL(String sessionExpiredURL) {
+ this.sessionExpiredURL = sessionExpiredURL;
+ }
+
+ /**
+ * Enables or disables the notification. If disabled, the set URL (or
+ * current) is loaded directly when next transaction between server and
+ * client happens.
+ *
+ * @param sessionExpiredNotificationEnabled
+ * true = enabled, false = disabled
+ */
+ public void setSessionExpiredNotificationEnabled(
+ boolean sessionExpiredNotificationEnabled) {
+ this.sessionExpiredNotificationEnabled = sessionExpiredNotificationEnabled;
+ }
+
+ /**
+ * Sets the caption of the notification. Set to null for no caption. If
+ * both caption and message are null, client automatically forwards to
+ * sessionExpiredUrl after timeout timer expires. Timer uses value read
+ * from HTTPSession.getMaxInactiveInterval()
+ *
+ * @param sessionExpiredCaption
+ * the caption
+ */
+ public void setSessionExpiredCaption(String sessionExpiredCaption) {
+ this.sessionExpiredCaption = sessionExpiredCaption;
+ }
+
+ /**
+ * Sets the message of the notification. Set to null for no message. If
+ * both caption and message are null, client automatically forwards to
+ * sessionExpiredUrl after timeout timer expires. Timer uses value read
+ * from HTTPSession.getMaxInactiveInterval()
+ *
+ * @param sessionExpiredMessage
+ * the message
+ */
+ public void setSessionExpiredMessage(String sessionExpiredMessage) {
+ this.sessionExpiredMessage = sessionExpiredMessage;
+ }
+
+ /**
+ * Sets the URL to go to when there is a authentication error.
+ *
+ * @param authenticationErrorURL
+ * the URL to go to, or null to reload current
+ */
+ public void setAuthenticationErrorURL(String authenticationErrorURL) {
+ this.authenticationErrorURL = authenticationErrorURL;
+ }
+
+ /**
+ * Enables or disables the notification. If disabled, the set URL (or
+ * current) is loaded directly.
+ *
+ * @param authenticationErrorNotificationEnabled
+ * true = enabled, false = disabled
+ */
+ public void setAuthenticationErrorNotificationEnabled(
+ boolean authenticationErrorNotificationEnabled) {
+ this.authenticationErrorNotificationEnabled = authenticationErrorNotificationEnabled;
+ }
+
+ /**
+ * Sets the caption of the notification. Set to null for no caption. If
+ * both caption and message is null, the notification is disabled;
+ *
+ * @param authenticationErrorCaption
+ * the caption
+ */
+ public void setAuthenticationErrorCaption(
+ String authenticationErrorCaption) {
+ this.authenticationErrorCaption = authenticationErrorCaption;
+ }
+
+ /**
+ * Sets the message of the notification. Set to null for no message. If
+ * both caption and message is null, the notification is disabled;
+ *
+ * @param authenticationErrorMessage
+ * the message
+ */
+ public void setAuthenticationErrorMessage(
+ String authenticationErrorMessage) {
+ this.authenticationErrorMessage = authenticationErrorMessage;
+ }
+
+ /**
+ * Sets the URL to go to when there is a communication error.
+ *
+ * @param communicationErrorURL
+ * the URL to go to, or null to reload current
+ */
+ public void setCommunicationErrorURL(String communicationErrorURL) {
+ this.communicationErrorURL = communicationErrorURL;
+ }
+
+ /**
+ * Enables or disables the notification. If disabled, the set URL (or
+ * current) is loaded directly.
+ *
+ * @param communicationErrorNotificationEnabled
+ * true = enabled, false = disabled
+ */
+ public void setCommunicationErrorNotificationEnabled(
+ boolean communicationErrorNotificationEnabled) {
+ this.communicationErrorNotificationEnabled = communicationErrorNotificationEnabled;
+ }
+
+ /**
+ * Sets the caption of the notification. Set to null for no caption. If
+ * both caption and message is null, the notification is disabled;
+ *
+ * @param communicationErrorCaption
+ * the caption
+ */
+ public void setCommunicationErrorCaption(
+ String communicationErrorCaption) {
+ this.communicationErrorCaption = communicationErrorCaption;
+ }
+
+ /**
+ * Sets the message of the notification. Set to null for no message. If
+ * both caption and message is null, the notification is disabled;
+ *
+ * @param communicationErrorMessage
+ * the message
+ */
+ public void setCommunicationErrorMessage(
+ String communicationErrorMessage) {
+ this.communicationErrorMessage = communicationErrorMessage;
+ }
+
+ /**
+ * Sets the URL to go to when an internal error occurs.
+ *
+ * @param internalErrorURL
+ * the URL to go to, or null to reload current
+ */
+ public void setInternalErrorURL(String internalErrorURL) {
+ this.internalErrorURL = internalErrorURL;
+ }
+
+ /**
+ * Enables or disables the notification. If disabled, the set URL (or
+ * current) is loaded directly.
+ *
+ * @param internalErrorNotificationEnabled
+ * true = enabled, false = disabled
+ */
+ public void setInternalErrorNotificationEnabled(
+ boolean internalErrorNotificationEnabled) {
+ this.internalErrorNotificationEnabled = internalErrorNotificationEnabled;
+ }
+
+ /**
+ * Sets the caption of the notification. Set to null for no caption. If
+ * both caption and message is null, the notification is disabled;
+ *
+ * @param internalErrorCaption
+ * the caption
+ */
+ public void setInternalErrorCaption(String internalErrorCaption) {
+ this.internalErrorCaption = internalErrorCaption;
+ }
+
+ /**
+ * Sets the message of the notification. Set to null for no message. If
+ * both caption and message is null, the notification is disabled;
+ *
+ * @param internalErrorMessage
+ * the message
+ */
+ public void setInternalErrorMessage(String internalErrorMessage) {
+ this.internalErrorMessage = internalErrorMessage;
+ }
+
+ /**
+ * Sets the URL to go to when the client is out-of-sync.
+ *
+ * @param outOfSyncURL
+ * the URL to go to, or null to reload current
+ */
+ public void setOutOfSyncURL(String outOfSyncURL) {
+ this.outOfSyncURL = outOfSyncURL;
+ }
+
+ /**
+ * Enables or disables the notification. If disabled, the set URL (or
+ * current) is loaded directly.
+ *
+ * @param outOfSyncNotificationEnabled
+ * true = enabled, false = disabled
+ */
+ public void setOutOfSyncNotificationEnabled(
+ boolean outOfSyncNotificationEnabled) {
+ this.outOfSyncNotificationEnabled = outOfSyncNotificationEnabled;
+ }
+
+ /**
+ * Sets the caption of the notification. Set to null for no caption. If
+ * both caption and message is null, the notification is disabled;
+ *
+ * @param outOfSyncCaption
+ * the caption
+ */
+ public void setOutOfSyncCaption(String outOfSyncCaption) {
+ this.outOfSyncCaption = outOfSyncCaption;
+ }
+
+ /**
+ * Sets the message of the notification. Set to null for no message. If
+ * both caption and message is null, the notification is disabled;
+ *
+ * @param outOfSyncMessage
+ * the message
+ */
+ public void setOutOfSyncMessage(String outOfSyncMessage) {
+ this.outOfSyncMessage = outOfSyncMessage;
+ }
+
+ /**
+ * Sets the URL to redirect to when the browser has cookies disabled.
+ *
+ * @param cookiesDisabledURL
+ * the URL to redirect to, or null to reload the current URL
+ */
+ public void setCookiesDisabledURL(String cookiesDisabledURL) {
+ this.cookiesDisabledURL = cookiesDisabledURL;
+ }
+
+ /**
+ * Enables or disables the notification for "cookies disabled" messages.
+ * If disabled, the URL returned by {@link #getCookiesDisabledURL()} is
+ * loaded directly.
+ *
+ * @param cookiesDisabledNotificationEnabled
+ * true to enable "cookies disabled" messages, false
+ * otherwise
+ */
+ public void setCookiesDisabledNotificationEnabled(
+ boolean cookiesDisabledNotificationEnabled) {
+ this.cookiesDisabledNotificationEnabled = cookiesDisabledNotificationEnabled;
+ }
+
+ /**
+ * Sets the caption of the "cookies disabled" notification. Set to null
+ * for no caption. If both caption and message is null, the notification
+ * is disabled.
+ *
+ * @param cookiesDisabledCaption
+ * the caption for the "cookies disabled" notification
+ */
+ public void setCookiesDisabledCaption(String cookiesDisabledCaption) {
+ this.cookiesDisabledCaption = cookiesDisabledCaption;
+ }
+
+ /**
+ * Sets the message of the "cookies disabled" notification. Set to null
+ * for no message. If both caption and message is null, the notification
+ * is disabled.
+ *
+ * @param cookiesDisabledMessage
+ * the message for the "cookies disabled" notification
+ */
+ public void setCookiesDisabledMessage(String cookiesDisabledMessage) {
+ this.cookiesDisabledMessage = cookiesDisabledMessage;
+ }
+
+ }
+
+ /**
+ * Application error is an error message defined on the application level.
+ *
+ * When an error occurs on the application level, this error message type
+ * should be used. This indicates that the problem is caused by the
+ * application - not by the user.
+ */
+ public class ApplicationError implements Terminal.ErrorEvent {
+ private final Throwable throwable;
+
+ public ApplicationError(Throwable throwable) {
+ this.throwable = throwable;
+ }
+
+ @Override
+ public Throwable getThrowable() {
+ return throwable;
+ }
+
+ }
+
+ /**
+ * Gets a root for a request for which no root is already known. This method
+ * is called when the framework processes a request that does not originate
+ * from an existing root instance. This typically happens when a host page
+ * is requested.
+ *
+ *
+ * Subclasses of Application may override this method to provide custom
+ * logic for choosing how to create a suitable root or for picking an
+ * already created root. If an existing root is picked, care should be taken
+ * to avoid keeping the same root open in multiple browser windows, as that
+ * will cause the states to go out of sync.
+ *
+ *
+ *
+ * If {@link BrowserDetails} are required to create a Root, the
+ * implementation can throw a {@link RootRequiresMoreInformationException}
+ * exception. In this case, the framework will instruct the browser to send
+ * the additional details, whereupon this method is invoked again with the
+ * browser details present in the wrapped request. Throwing the exception if
+ * the browser details are already available is not supported.
+ *
+ *
+ *
+ * The default implementation in {@link Application} creates a new instance
+ * of the Root class returned by {@link #getRootClassName(WrappedRequest)},
+ * which in turn uses the {@value #ROOT_PARAMETER} parameter from web.xml.
+ * If {@link DeploymentConfiguration#getClassLoader()} for the request
+ * returns a {@link ClassLoader}, it is used for loading the Root class.
+ * Otherwise the {@link ClassLoader} used to load this class is used.
+ *
+ *
+ * @param request
+ * the wrapped request for which a root is needed
+ * @return a root instance to use for the request
+ * @throws RootRequiresMoreInformationException
+ * may be thrown by an implementation to indicate that
+ * {@link BrowserDetails} are required to create a root
+ *
+ * @see #getRootClassName(WrappedRequest)
+ * @see Root
+ * @see RootRequiresMoreInformationException
+ * @see WrappedRequest#getBrowserDetails()
+ *
+ * @since 7.0
+ */
+ protected Root getRoot(WrappedRequest request)
+ throws RootRequiresMoreInformationException {
+ String rootClassName = getRootClassName(request);
+ try {
+ ClassLoader classLoader = request.getDeploymentConfiguration()
+ .getClassLoader();
+ if (classLoader == null) {
+ classLoader = getClass().getClassLoader();
+ }
+ Class 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 Root class that should be used for
+ * a request. The class must have an accessible no-args constructor.
+ *
+ * The default implementation uses the {@value #ROOT_PARAMETER} parameter
+ * from web.xml.
+ *
+ *
+ * This method is mainly used by the default implementation of
+ * {@link #getRoot(WrappedRequest)}. If you override that method with your
+ * own functionality, the results of this method might not be used.
+ *
+ *
+ * @param request
+ * the request for which a new root is required
+ * @return the name of the root class to use
+ *
+ * @since 7.0
+ */
+ protected String getRootClassName(WrappedRequest request) {
+ Object rootClassNameObj = properties.get(ROOT_PARAMETER);
+ if (rootClassNameObj instanceof String) {
+ return (String) rootClassNameObj;
+ } else {
+ throw new RuntimeException("No " + ROOT_PARAMETER
+ + " defined in web.xml");
+ }
+ }
+
+ /**
+ * Finds the theme to use for a specific root. If no specific theme is
+ * required, null is returned.
+ *
+ * TODO Tell what the default implementation does once it does something.
+ *
+ * @param root
+ * the root to get a theme for
+ * @return the name of the theme, or null if the default theme
+ * should be used
+ *
+ * @since 7.0
+ */
+ public String getThemeForRoot(Root root) {
+ Theme rootTheme = getAnnotationFor(root.getClass(), Theme.class);
+ if (rootTheme != null) {
+ return rootTheme.value();
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Finds the widgetset to use for a specific root. If no specific widgetset
+ * is required, null is returned.
+ *
+ * TODO Tell what the default implementation does once it does something.
+ *
+ * @param root
+ * the root to get a widgetset for
+ * @return the name of the widgetset, or null if the default
+ * widgetset should be used
+ *
+ * @since 7.0
+ */
+ public String getWidgetsetForRoot(Root root) {
+ Widgetset rootWidgetset = getAnnotationFor(root.getClass(),
+ Widgetset.class);
+ if (rootWidgetset != null) {
+ return rootWidgetset.value();
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Helper to get an annotation for a class. If the annotation is not present
+ * on the target class, it's superclasses and implemented interfaces are
+ * also searched for the annotation.
+ *
+ * @param type
+ * the target class from which the annotation should be found
+ * @param annotationType
+ * the annotation type to look for
+ * @return an annotation of the given type, or null if the
+ * annotation is not present on the class
+ */
+ private static T getAnnotationFor(Class> type,
+ Class annotationType) {
+ // Find from the class hierarchy
+ Class> currentType = type;
+ while (currentType != Object.class) {
+ T annotation = currentType.getAnnotation(annotationType);
+ if (annotation != null) {
+ return annotation;
+ } else {
+ currentType = currentType.getSuperclass();
+ }
+ }
+
+ // Find from an implemented interface
+ for (Class> iface : type.getInterfaces()) {
+ T annotation = iface.getAnnotation(annotationType);
+ if (annotation != null) {
+ return annotation;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Handles a request by passing it to each registered {@link RequestHandler}
+ * in turn until one produces a response. This method is used for requests
+ * that have not been handled by any specific functionality in the terminal
+ * implementation (e.g. {@link AbstractApplicationServlet}).
+ *
+ * The request handlers are invoked in the revere order in which they were
+ * added to the application until a response has been produced. This means
+ * that the most recently added handler is used first and the first request
+ * handler that was added to the application is invoked towards the end
+ * unless any previous handler has already produced a response.
+ *
+ *
+ * @param request
+ * the wrapped request to get information from
+ * @param response
+ * the response to which data can be written
+ * @return returns true if a {@link RequestHandler} has
+ * produced a response and false if no response has
+ * been written.
+ * @throws IOException
+ *
+ * @see #addRequestHandler(RequestHandler)
+ * @see RequestHandler
+ *
+ * @since 7.0
+ */
+ public boolean handleRequest(WrappedRequest request,
+ WrappedResponse response) throws IOException {
+ // Use a copy to avoid ConcurrentModificationException
+ for (RequestHandler handler : new ArrayList(
+ requestHandlers)) {
+ if (handler.handleRequest(this, request, response)) {
+ return true;
+ }
+ }
+ // If not handled
+ return false;
+ }
+
+ /**
+ * Adds a request handler to this application. Request handlers can be added
+ * to provide responses to requests that are not handled by the default
+ * functionality of the framework.
+ *
+ * Handlers are called in reverse order of addition, so the most recently
+ * added handler will be called first.
+ *
+ *
+ * @param handler
+ * the request handler to add
+ *
+ * @see #handleRequest(WrappedRequest, WrappedResponse)
+ * @see #removeRequestHandler(RequestHandler)
+ *
+ * @since 7.0
+ */
+ public void addRequestHandler(RequestHandler handler) {
+ requestHandlers.addFirst(handler);
+ }
+
+ /**
+ * Removes a request handler from the application.
+ *
+ * @param handler
+ * the request handler to remove
+ *
+ * @since 7.0
+ */
+ public void removeRequestHandler(RequestHandler handler) {
+ requestHandlers.remove(handler);
+ }
+
+ /**
+ * Gets the request handlers that are registered to the application. The
+ * iteration order of the returned collection is the same as the order in
+ * which the request handlers will be invoked when a request is handled.
+ *
+ * @return a collection of request handlers, with the iteration order
+ * according to the order they would be invoked
+ *
+ * @see #handleRequest(WrappedRequest, WrappedResponse)
+ * @see #addRequestHandler(RequestHandler)
+ * @see #removeRequestHandler(RequestHandler)
+ *
+ * @since 7.0
+ */
+ public Collection getRequestHandlers() {
+ return Collections.unmodifiableCollection(requestHandlers);
+ }
+
+ /**
+ * Find an application resource with a given key.
+ *
+ * @param key
+ * The key of the resource
+ * @return The application resource corresponding to the provided key, or
+ * null if no resource is registered for the key
+ *
+ * @since 7.0
+ */
+ public ApplicationResource getResource(String key) {
+ return keyResourceMap.get(key);
+ }
+
+ /**
+ * Thread local for keeping track of currently used application instance
+ *
+ * @since 7.0
+ */
+ private static final ThreadLocal currentApplication = new ThreadLocal();
+
+ private boolean rootPreserved = false;
+
+ /**
+ * Gets the currently used application. The current application is
+ * automatically defined when processing requests to the server. In other
+ * cases, (e.g. from background threads), the current application is not
+ * automatically defined.
+ *
+ * @return the current application instance if available, otherwise
+ * null
+ *
+ * @see #setCurrent(Application)
+ *
+ * @since 7.0
+ */
+ public static Application getCurrent() {
+ return currentApplication.get();
+ }
+
+ /**
+ * Sets the thread local for the current application. This method is used by
+ * the framework to set the current application whenever a new request is
+ * processed and it is cleared when the request has been processed.
+ *
+ * The application developer can also use this method to define the current
+ * application outside the normal request handling, e.g. when initiating
+ * custom background threads.
+ *
+ *
+ * @param application
+ *
+ * @see #getCurrent()
+ * @see ThreadLocal
+ *
+ * @since 7.0
+ */
+ public static void setCurrent(Application application) {
+ currentApplication.set(application);
+ }
+
+ /**
+ * Check whether this application is in production mode. If an application
+ * is in production mode, certain debugging facilities are not available.
+ *
+ * @return the status of the production mode flag
+ *
+ * @since 7.0
+ */
+ public boolean isProductionMode() {
+ return productionMode;
+ }
+
+ /**
+ * Finds the {@link Root} to which a particular request belongs. If the
+ * request originates from an existing Root, that root is returned. In other
+ * cases, the method attempts to create and initialize a new root and might
+ * throw a {@link RootRequiresMoreInformationException} if all required
+ * information is not available.
+ *
+ * Please note that this method can also return a newly created
+ * Root which has not yet been initialized. You can use
+ * {@link #isRootInitPending(int)} with the root's id (
+ * {@link Root#getRootId()} to check whether the initialization is still
+ * pending.
+ *
+ *
+ * @param request
+ * the request for which a root is desired
+ * @return a root belonging to the request
+ * @throws RootRequiresMoreInformationException
+ * if no existing root could be found and creating a new root
+ * requires additional information from the browser
+ *
+ * @see #getRoot(WrappedRequest)
+ * @see RootRequiresMoreInformationException
+ *
+ * @since 7.0
+ */
+ public Root getRootForRequest(WrappedRequest request)
+ throws RootRequiresMoreInformationException {
+ Root root = Root.getCurrent();
+ if (root != null) {
+ return root;
+ }
+ Integer rootId = getRootId(request);
+
+ synchronized (this) {
+ BrowserDetails browserDetails = request.getBrowserDetails();
+ boolean hasBrowserDetails = browserDetails != null
+ && browserDetails.getUriFragment() != null;
+
+ root = roots.get(rootId);
+
+ if (root == null && isRootPreserved()) {
+ // Check for a known root
+ if (!retainOnRefreshRoots.isEmpty()) {
+
+ Integer retainedRootId;
+ if (!hasBrowserDetails) {
+ throw new RootRequiresMoreInformationException();
+ } else {
+ String windowName = browserDetails.getWindowName();
+ retainedRootId = retainOnRefreshRoots.get(windowName);
+ }
+
+ if (retainedRootId != null) {
+ rootId = retainedRootId;
+ root = roots.get(rootId);
+ }
+ }
+ }
+
+ if (root == null) {
+ // Throws exception if root can not yet be created
+ root = getRoot(request);
+
+ // Initialize some fields for a newly created root
+ if (root.getApplication() == null) {
+ root.setApplication(this);
+ }
+ if (root.getRootId() < 0) {
+
+ if (rootId == null) {
+ // Get the next id if none defined
+ rootId = Integer.valueOf(nextRootId++);
+ }
+ root.setRootId(rootId.intValue());
+ roots.put(rootId, root);
+ }
+ }
+
+ // Set thread local here so it is available in init
+ Root.setCurrent(root);
+
+ if (!initedRoots.contains(rootId)) {
+ boolean initRequiresBrowserDetails = isRootPreserved()
+ || !root.getClass()
+ .isAnnotationPresent(EagerInit.class);
+ if (!initRequiresBrowserDetails || hasBrowserDetails) {
+ root.doInit(request);
+
+ // Remember that this root has been initialized
+ initedRoots.add(rootId);
+
+ // init() might turn on preserve so do this afterwards
+ if (isRootPreserved()) {
+ // Remember this root
+ String windowName = request.getBrowserDetails()
+ .getWindowName();
+ retainOnRefreshRoots.put(windowName, rootId);
+ }
+ }
+ }
+ } // end synchronized block
+
+ return root;
+ }
+
+ /**
+ * Internal helper to finds the root id for a request.
+ *
+ * @param request
+ * the request to get the root id for
+ * @return a root id, or null if no root id is defined
+ *
+ * @since 7.0
+ */
+ private static Integer getRootId(WrappedRequest request) {
+ if (request instanceof CombinedRequest) {
+ // Combined requests has the rootid parameter in the second request
+ CombinedRequest combinedRequest = (CombinedRequest) request;
+ request = combinedRequest.getSecondRequest();
+ }
+ String rootIdString = request
+ .getParameter(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.
+ *
+ * NOTE that you should avoid turning this feature on/off on-the-fly when
+ * the UI is already shown, as it might not be retained as intended.
+ *
+ *
+ * @param rootPreserved
+ * trueif the same Root instance should be reused
+ * e.g. when the browser window is refreshed.
+ */
+ public void setRootPreserved(boolean rootPreserved) {
+ this.rootPreserved = rootPreserved;
+ if (!rootPreserved) {
+ retainOnRefreshRoots.clear();
+ }
+ }
+
+ /**
+ * Checks whether the same Root state should be reused if the framework can
+ * detect that the application is opened in a browser window where it has
+ * previously been open. The framework attempts to discover this by checking
+ * the value of window.name in the browser.
+ *
+ * @return trueif the same Root instance should be reused e.g.
+ * when the browser window is refreshed.
+ */
+ public boolean isRootPreserved() {
+ return rootPreserved;
+ }
+
+ /**
+ * Checks whether there's a pending initialization for the root with the
+ * given id.
+ *
+ * @param rootId
+ * root id to check for
+ * @return true of the initialization is pending,
+ * false if the root id is not registered or if the
+ * root has already been initialized
+ *
+ * @see #getRootForRequest(WrappedRequest)
+ */
+ public boolean isRootInitPending(int rootId) {
+ return !initedRoots.contains(Integer.valueOf(rootId));
+ }
+
+ /**
+ * Gets all the roots of this application. This includes roots that have
+ * been requested but not yet initialized. Please note, that roots are not
+ * automatically removed e.g. if the browser window is closed and that there
+ * is no way to manually remove a root. Inactive roots will thus not be
+ * released for GC until the entire application is released when the session
+ * has timed out (unless there are dangling references). Improved support
+ * for releasing unused roots is planned for an upcoming alpha release of
+ * Vaadin 7.
+ *
+ * @return a collection of roots belonging to this application
+ *
+ * @since 7.0
+ */
+ public Collection getRoots() {
+ return Collections.unmodifiableCollection(roots.values());
+ }
+
+ private int connectorIdSequence = 0;
+
+ /**
+ * Generate an id for the given Connector. Connectors must not call this
+ * method more than once, the first time they need an id.
+ *
+ * @param connector
+ * A connector that has not yet been assigned an id.
+ * @return A new id for the connector
+ */
+ public String createConnectorId(ClientConnector connector) {
+ return String.valueOf(connectorIdSequence++);
+ }
+
+ private static final Logger getLogger() {
+ return Logger.getLogger(Application.class.getName());
+ }
+
+ /**
+ * Returns a Root with the given id.
+ *
+ * This is meant for framework internal use.
+ *
+ *
+ * @param rootId
+ * The root id
+ * @return The root with the given id or null if not found
+ */
+ public Root getRootById(int rootId) {
+ return roots.get(rootId);
+ }
+
+ 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);
+ }
+}
diff --git a/server/src/com/vaadin/RootRequiresMoreInformationException.java b/server/src/com/vaadin/RootRequiresMoreInformationException.java
new file mode 100644
index 0000000000..ed0fa41437
--- /dev/null
+++ b/server/src/com/vaadin/RootRequiresMoreInformationException.java
@@ -0,0 +1,25 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin;
+
+import com.vaadin.terminal.WrappedRequest;
+import com.vaadin.terminal.WrappedRequest.BrowserDetails;
+
+/**
+ * Exception that is thrown to indicate that creating or initializing the root
+ * requires information detailed from the web browser ({@link BrowserDetails})
+ * to be present.
+ *
+ * This exception may not be thrown if that information is already present in
+ * the current WrappedRequest.
+ *
+ * @see Application#getRoot(WrappedRequest)
+ * @see WrappedRequest#getBrowserDetails()
+ *
+ * @since 7.0
+ */
+public class RootRequiresMoreInformationException extends Exception {
+ // Nothing of interest here
+}
diff --git a/server/src/com/vaadin/Vaadin.gwt.xml b/server/src/com/vaadin/Vaadin.gwt.xml
new file mode 100644
index 0000000000..07d7c941e6
--- /dev/null
+++ b/server/src/com/vaadin/Vaadin.gwt.xml
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/server/src/com/vaadin/Version.java b/server/src/com/vaadin/Version.java
new file mode 100644
index 0000000000..eb6d73e7e0
--- /dev/null
+++ b/server/src/com/vaadin/Version.java
@@ -0,0 +1,74 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+package com.vaadin;
+
+import java.io.Serializable;
+
+public class Version implements Serializable {
+ /**
+ * The version number of this release. For example "6.2.0". Always in the
+ * format "major.minor.revision[.build]". The build part is optional. All of
+ * major, minor, revision must be integers.
+ */
+ private static final String VERSION;
+ /**
+ * Major version number. For example 6 in 6.2.0.
+ */
+ private static final int VERSION_MAJOR;
+
+ /**
+ * Minor version number. For example 2 in 6.2.0.
+ */
+ private static final int VERSION_MINOR;
+
+ /**
+ * Version revision number. For example 0 in 6.2.0.
+ */
+ private static final int VERSION_REVISION;
+
+ /**
+ * Build identifier. For example "nightly-20091123-c9963" in
+ * 6.2.0.nightly-20091123-c9963.
+ */
+ private static final String VERSION_BUILD;
+
+ /* Initialize version numbers from string replaced by build-script. */
+ static {
+ if ("@VERSION@".equals("@" + "VERSION" + "@")) {
+ VERSION = "9.9.9.INTERNAL-DEBUG-BUILD";
+ } else {
+ VERSION = "@VERSION@";
+ }
+ final String[] digits = VERSION.split("\\.", 4);
+ VERSION_MAJOR = Integer.parseInt(digits[0]);
+ VERSION_MINOR = Integer.parseInt(digits[1]);
+ VERSION_REVISION = Integer.parseInt(digits[2]);
+ if (digits.length == 4) {
+ VERSION_BUILD = digits[3];
+ } else {
+ VERSION_BUILD = "";
+ }
+ }
+
+ public static String getFullVersion() {
+ return VERSION;
+ }
+
+ public static int getMajorVersion() {
+ return VERSION_MAJOR;
+ }
+
+ public static int getMinorVersion() {
+ return VERSION_MINOR;
+ }
+
+ public static int getRevision() {
+ return VERSION_REVISION;
+ }
+
+ public static String getBuildIdentifier() {
+ return VERSION_BUILD;
+ }
+
+}
diff --git a/server/src/com/vaadin/annotations/AutoGenerated.java b/server/src/com/vaadin/annotations/AutoGenerated.java
new file mode 100644
index 0000000000..72c9b62a91
--- /dev/null
+++ b/server/src/com/vaadin/annotations/AutoGenerated.java
@@ -0,0 +1,18 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+package com.vaadin.annotations;
+
+/**
+ * Marker annotation for automatically generated code elements.
+ *
+ * These elements may be modified or removed by code generation.
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 6.0
+ */
+public @interface AutoGenerated {
+
+}
diff --git a/server/src/com/vaadin/annotations/EagerInit.java b/server/src/com/vaadin/annotations/EagerInit.java
new file mode 100644
index 0000000000..c7c2702d2a
--- /dev/null
+++ b/server/src/com/vaadin/annotations/EagerInit.java
@@ -0,0 +1,30 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+package com.vaadin.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import com.vaadin.terminal.WrappedRequest;
+import com.vaadin.ui.Root;
+
+/**
+ * Indicates that the init method in a Root class can be called before full
+ * browser details ({@link WrappedRequest#getBrowserDetails()}) are available.
+ * This will make the UI appear more quickly, as ensuring the availability of
+ * this information typically requires an additional round trip to the client.
+ *
+ * @see Root#init(com.vaadin.terminal.WrappedRequest)
+ * @see WrappedRequest#getBrowserDetails()
+ *
+ * @since 7.0
+ *
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface EagerInit {
+ // No values
+}
diff --git a/server/src/com/vaadin/annotations/JavaScript.java b/server/src/com/vaadin/annotations/JavaScript.java
new file mode 100644
index 0000000000..357bcc3649
--- /dev/null
+++ b/server/src/com/vaadin/annotations/JavaScript.java
@@ -0,0 +1,41 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import com.vaadin.terminal.gwt.server.ClientConnector;
+
+/**
+ * If this annotation is present on a {@link ClientConnector} class, the
+ * framework ensures the referenced JavaScript files are loaded before the init
+ * method for the corresponding client-side connector is invoked.
+ *
+ * Absolute URLs including protocol and host are used as is on the client-side.
+ * Relative urls are mapped to APP/CONNECTOR/[url] which are by default served
+ * from the classpath relative to the class where the annotation is defined.
+ *
+ * Example: {@code @JavaScript( "http://host.com/file1.js", "file2.js"})} on the
+ * class com.example.MyConnector would load the file http://host.com/file1.js as
+ * is and file2.js from /com/example/file2.js on the server's classpath using
+ * the ClassLoader that was used to load com.example.MyConnector.
+ *
+ * @author Vaadin Ltd
+ * @version @VERSION@
+ * @since 7.0.0
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface JavaScript {
+ /**
+ * JavaScript files to load before initializing the client-side connector.
+ *
+ * @return an array of JavaScript file urls
+ */
+ public String[] value();
+}
diff --git a/server/src/com/vaadin/annotations/StyleSheet.java b/server/src/com/vaadin/annotations/StyleSheet.java
new file mode 100644
index 0000000000..d082cb8d30
--- /dev/null
+++ b/server/src/com/vaadin/annotations/StyleSheet.java
@@ -0,0 +1,38 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import com.vaadin.terminal.gwt.server.ClientConnector;
+
+/**
+ * If this annotation is present on a {@link ClientConnector} class, the
+ * framework ensures the referenced style sheets are loaded before the init
+ * method for the corresponding client-side connector is invoked.
+ *
+ * Example: {@code @StyleSheet( "http://host.com/file1.css", "file2.css"})} on
+ * the class com.example.MyConnector would load the file
+ * http://host.com/file1.css as is and file2.css from /com/example/file2.css on
+ * the server's classpath using the ClassLoader that was used to load
+ * com.example.MyConnector.
+ *
+ * @author Vaadin Ltd
+ * @version @VERSION@
+ * @since 7.0.0
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface StyleSheet {
+ /**
+ * Style sheets to load before initializing the client-side connector.
+ *
+ * @return an array of style sheet urls
+ */
+ public String[] value();
+}
diff --git a/server/src/com/vaadin/annotations/Theme.java b/server/src/com/vaadin/annotations/Theme.java
new file mode 100644
index 0000000000..7c62b07741
--- /dev/null
+++ b/server/src/com/vaadin/annotations/Theme.java
@@ -0,0 +1,24 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import com.vaadin.ui.Root;
+
+/**
+ * Defines a specific theme for a {@link Root}.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface Theme {
+ /**
+ * @return simple name of the theme
+ */
+ public String value();
+}
diff --git a/server/src/com/vaadin/annotations/Widgetset.java b/server/src/com/vaadin/annotations/Widgetset.java
new file mode 100644
index 0000000000..99113f73f9
--- /dev/null
+++ b/server/src/com/vaadin/annotations/Widgetset.java
@@ -0,0 +1,25 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import com.vaadin.ui.Root;
+
+/**
+ * Defines a specific theme for a {@link Root}.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface Widgetset {
+ /**
+ * @return name of the widgetset
+ */
+ public String value();
+
+}
diff --git a/server/src/com/vaadin/annotations/package.html b/server/src/com/vaadin/annotations/package.html
new file mode 100644
index 0000000000..d789e9b5df
--- /dev/null
+++ b/server/src/com/vaadin/annotations/package.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
Contains annotations used in Vaadin. Note that some annotations
+are also found in other packages e.g., {@link com.vaadin.ui.ClientWidget}.
+ * Defines the interface to commit and discard changes to an object, supporting
+ * read-through and write-through modes.
+ *
+ *
+ *
+ * Read-through mode means that the value read from the buffered object
+ * is constantly up to date with the data source. Write-through mode
+ * means that all changes to the object are immediately updated to the data
+ * source.
+ *
+ *
+ *
+ * Since these modes are independent, their combinations may result in some
+ * behaviour that may sound surprising.
+ *
+ *
+ *
+ * For example, if a Buffered object is in read-through mode but
+ * not in write-through mode, the result is an object whose value is updated
+ * directly from the data source only if it's not locally modified. If the value
+ * is locally modified, retrieving the value from the object would result in a
+ * value that is different than the one stored in the data source, even though
+ * the object is in read-through mode.
+ *
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+public interface Buffered extends Serializable {
+
+ /**
+ * Updates all changes since the previous commit to the data source. The
+ * value stored in the object will always be updated into the data source
+ * when commit is called.
+ *
+ * @throws SourceException
+ * if the operation fails because of an exception is thrown by
+ * the data source. The cause is included in the exception.
+ * @throws InvalidValueException
+ * if the operation fails because validation is enabled and the
+ * values do not validate
+ */
+ public void commit() throws SourceException, InvalidValueException;
+
+ /**
+ * Discards all changes since last commit. The object updates its value from
+ * the data source.
+ *
+ * @throws SourceException
+ * if the operation fails because of an exception is thrown by
+ * the data source. The cause is included in the exception.
+ */
+ public void discard() throws SourceException;
+
+ /**
+ * Tests if the object is in write-through mode. If the object is in
+ * write-through mode, all modifications to it will result in
+ * commit being called after the modification.
+ *
+ * @return true if the object is in write-through mode,
+ * false if it's not.
+ * @deprecated Use {@link #setBuffered(boolean)} instead. Note that
+ * setReadThrough(true), setWriteThrough(true) equals
+ * setBuffered(false)
+ */
+ @Deprecated
+ public boolean isWriteThrough();
+
+ /**
+ * Sets the object's write-through mode to the specified status. When
+ * switching the write-through mode on, the commit operation
+ * will be performed.
+ *
+ * @param writeThrough
+ * Boolean value to indicate if the object should be in
+ * write-through mode after the call.
+ * @throws SourceException
+ * If the operation fails because of an exception is thrown by
+ * the data source.
+ * @throws InvalidValueException
+ * If the implicit commit operation fails because of a
+ * validation error.
+ *
+ * @deprecated Use {@link #setBuffered(boolean)} instead. Note that
+ * setReadThrough(true), setWriteThrough(true) equals
+ * setBuffered(false)
+ */
+ @Deprecated
+ public void setWriteThrough(boolean writeThrough) throws SourceException,
+ InvalidValueException;
+
+ /**
+ * Tests if the object is in read-through mode. If the object is in
+ * read-through mode, retrieving its value will result in the value being
+ * first updated from the data source to the object.
+ *
+ * The only exception to this rule is that when the object is not in
+ * write-through mode and it's buffer contains a modified value, the value
+ * retrieved from the object will be the locally modified value in the
+ * buffer which may differ from the value in the data source.
+ *
+ *
+ * @return true if the object is in read-through mode,
+ * false if it's not.
+ * @deprecated Use {@link #isBuffered(boolean)} instead. Note that
+ * setReadThrough(true), setWriteThrough(true) equals
+ * setBuffered(false)
+ */
+ @Deprecated
+ public boolean isReadThrough();
+
+ /**
+ * Sets the object's read-through mode to the specified status. When
+ * switching read-through mode on, the object's value is updated from the
+ * data source.
+ *
+ * @param readThrough
+ * Boolean value to indicate if the object should be in
+ * read-through mode after the call.
+ *
+ * @throws SourceException
+ * If the operation fails because of an exception is thrown by
+ * the data source. The cause is included in the exception.
+ * @deprecated Use {@link #setBuffered(boolean)} instead. Note that
+ * setReadThrough(true), setWriteThrough(true) equals
+ * setBuffered(false)
+ */
+ @Deprecated
+ public void setReadThrough(boolean readThrough) throws SourceException;
+
+ /**
+ * Sets the object's buffered mode to the specified status.
+ *
+ * When the object is in buffered mode, an internal buffer will be used to
+ * store changes until {@link #commit()} is called. Calling
+ * {@link #discard()} will revert the internal buffer to the value of the
+ * data source.
+ *
+ *
+ * This is an easier way to use {@link #setReadThrough(boolean)} and
+ * {@link #setWriteThrough(boolean)} and not as error prone. Changing
+ * buffered mode will change both the read through and write through state
+ * of the object.
+ *
+ *
+ * Mixing calls to {@link #setBuffered(boolean)}/{@link #isBuffered()} and
+ * {@link #setReadThrough(boolean)}/{@link #isReadThrough()} or
+ * {@link #setWriteThrough(boolean)}/{@link #isWriteThrough()} is generally
+ * a bad idea.
+ *
+ *
+ * @param buffered
+ * true if buffered mode should be turned on, false otherwise
+ * @since 7.0
+ */
+ public void setBuffered(boolean buffered);
+
+ /**
+ * Checks the buffered mode of this Object.
+ *
+ * This method only returns true if both read and write buffering is used.
+ *
+ *
+ * @return true if buffered mode is on, false otherwise
+ * @since 7.0
+ */
+ public boolean isBuffered();
+
+ /**
+ * Tests if the value stored in the object has been modified since it was
+ * last updated from the data source.
+ *
+ * @return true if the value in the object has been modified
+ * since the last data source update, false if not.
+ */
+ public boolean isModified();
+
+ /**
+ * An exception that signals that one or more exceptions occurred while a
+ * buffered object tried to access its data source or if there is a problem
+ * in processing a data source.
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ @SuppressWarnings("serial")
+ public class SourceException extends RuntimeException implements
+ Serializable {
+
+ /** Source class implementing the buffered interface */
+ private final Buffered source;
+
+ /** Original cause of the source exception */
+ private Throwable[] causes = {};
+
+ /**
+ * Creates a source exception that does not include a cause.
+ *
+ * @param source
+ * the source object implementing the Buffered interface.
+ */
+ public SourceException(Buffered source) {
+ this.source = source;
+ }
+
+ /**
+ * Creates a source exception from a cause exception.
+ *
+ * @param source
+ * the source object implementing the Buffered interface.
+ * @param cause
+ * the original cause for this exception.
+ */
+ public SourceException(Buffered source, Throwable cause) {
+ this.source = source;
+ causes = new Throwable[] { cause };
+ }
+
+ /**
+ * Creates a source exception from multiple causes.
+ *
+ * @param source
+ * the source object implementing the Buffered interface.
+ * @param causes
+ * the original causes for this exception.
+ */
+ public SourceException(Buffered source, Throwable[] causes) {
+ this.source = source;
+ this.causes = causes;
+ }
+
+ /**
+ * Gets the cause of the exception.
+ *
+ * @return The (first) cause for the exception, null if no cause.
+ */
+ @Override
+ public final Throwable getCause() {
+ if (causes.length == 0) {
+ return null;
+ }
+ return causes[0];
+ }
+
+ /**
+ * Gets all the causes for this exception.
+ *
+ * @return throwables that caused this exception
+ */
+ public final Throwable[] getCauses() {
+ return causes;
+ }
+
+ /**
+ * Gets a source of the exception.
+ *
+ * @return the Buffered object which generated this exception.
+ */
+ public Buffered getSource() {
+ return source;
+ }
+
+ }
+}
diff --git a/server/src/com/vaadin/data/BufferedValidatable.java b/server/src/com/vaadin/data/BufferedValidatable.java
new file mode 100644
index 0000000000..ce1d44fce6
--- /dev/null
+++ b/server/src/com/vaadin/data/BufferedValidatable.java
@@ -0,0 +1,35 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.data;
+
+import java.io.Serializable;
+
+/**
+ *
+ * This interface defines the combination of Validatable and
+ * Buffered interfaces. The combination of the interfaces defines
+ * if the invalid data is committed to datasource.
+ *
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+public interface BufferedValidatable extends Buffered, Validatable,
+ Serializable {
+
+ /**
+ * Tests if the invalid data is committed to datasource. The default is
+ * false.
+ */
+ public boolean isInvalidCommitted();
+
+ /**
+ * Sets if the invalid data should be committed to datasource. The default
+ * is false.
+ */
+ public void setInvalidCommitted(boolean isCommitted);
+}
diff --git a/server/src/com/vaadin/data/Collapsible.java b/server/src/com/vaadin/data/Collapsible.java
new file mode 100644
index 0000000000..06c96b7ea7
--- /dev/null
+++ b/server/src/com/vaadin/data/Collapsible.java
@@ -0,0 +1,68 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+package com.vaadin.data;
+
+import com.vaadin.data.Container.Hierarchical;
+import com.vaadin.data.Container.Ordered;
+
+/**
+ * Container needed by large lazy loading hierarchies displayed e.g. in
+ * TreeTable.
+ *
+ * Container of this type gets notified when a subtree is opened/closed in a
+ * component displaying its content. This allows container to lazy load subtrees
+ * and release memory when a sub-tree is no longer displayed.
+ *
+ * Methods from {@link Container.Ordered} (and from {@linkContainer.Indexed} if
+ * implemented) are expected to work as in "preorder" of the currently visible
+ * hierarchy. This means for example that the return value of size method
+ * changes when subtree is collapsed/expanded. In other words items in collapsed
+ * sub trees should be "ignored" by container when the container is accessed
+ * with methods introduced in {@link Container.Ordered} or
+ * {@linkContainer.Indexed}. From the accessors point of view, items in
+ * collapsed subtrees don't exist.
+ *
+ * Collapsing the {@link Item} indicated by itemId hides all
+ * children, and their respective children, from the {@link Container}.
+ *
+ *
+ *
+ * If called on a leaf {@link Item}, this method does nothing.
+ *
+ *
+ * @param itemId
+ * the identifier of the collapsed {@link Item}
+ * @param collapsed
+ * true if you want to collapse the children below
+ * this {@link Item}. false if you want to
+ * uncollapse the children.
+ */
+ public void setCollapsed(Object itemId, boolean collapsed);
+
+ /**
+ *
+ * Checks whether the {@link Item}, identified by itemId is
+ * collapsed or not.
+ *
+ *
+ *
+ * If an {@link Item} is "collapsed" its children are not included in
+ * methods used to list Items in this container.
+ *
+ *
+ * @param itemId
+ * The {@link Item}'s identifier that is to be checked.
+ * @return true iff the {@link Item} identified by
+ * itemId is currently collapsed, otherwise
+ * false.
+ */
+ public boolean isCollapsed(Object itemId);
+
+}
diff --git a/server/src/com/vaadin/data/Container.java b/server/src/com/vaadin/data/Container.java
new file mode 100644
index 0000000000..f4c0ed9794
--- /dev/null
+++ b/server/src/com/vaadin/data/Container.java
@@ -0,0 +1,1105 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.data;
+
+import java.io.Serializable;
+import java.util.Collection;
+
+import com.vaadin.data.util.filter.SimpleStringFilter;
+import com.vaadin.data.util.filter.UnsupportedFilterException;
+
+/**
+ *
+ * A specialized set of identified Items. Basically the Container is a set of
+ * {@link Item}s, but it imposes certain constraints on its contents. These
+ * constraints state the following:
+ *
+ *
+ *
+ *
All Items in the Container must have the same number of Properties.
+ *
All Items in the Container must have the same Property ID's (see
+ * {@link Item#getItemPropertyIds()}).
+ *
All Properties in the Items corresponding to the same Property ID must
+ * have the same data type.
+ *
All Items within a container are uniquely identified by their non-null
+ * IDs.
+ *
+ *
+ *
+ * The Container can be visualized as a representation of a relational database
+ * table. Each Item in the Container represents a row in the table, and all
+ * cells in a column (identified by a Property ID) have the same data type. Note
+ * that as with the cells in a database table, no Property in a Container may be
+ * empty, though they may contain null values.
+ *
+ *
+ *
+ * Note that though uniquely identified, the Items in a Container are not
+ * necessarily {@link Container.Ordered ordered} or {@link Container.Indexed
+ * indexed}.
+ *
+ *
+ *
+ * Containers can derive Item ID's from the item properties or use other,
+ * container specific or user specified identifiers.
+ *
+ *
+ *
+ * If a container is {@link Filterable filtered} or {@link Sortable sorted},
+ * most of the the methods of the container interface and its subinterfaces
+ * (container size, {@link #containsId(Object)}, iteration and indices etc.)
+ * relate to the filtered and sorted view, not to the full container contents.
+ * See individual method javadoc for exceptions to this (adding and removing
+ * items).
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * The Container interface is split to several subinterfaces so that a class can
+ * implement only the ones it needs.
+ *
+ *
+ * @author Vaadin Ltd
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+public interface Container extends Serializable {
+
+ /**
+ * Gets the {@link Item} with the given Item ID from the Container. If the
+ * Container does not contain the requested Item, null is
+ * returned.
+ *
+ * Containers should not return Items that are filtered out.
+ *
+ * @param itemId
+ * ID of the {@link Item} to retrieve
+ * @return the {@link Item} with the given ID or null if the
+ * Item is not found in the Container
+ */
+ public Item getItem(Object itemId);
+
+ /**
+ * Gets the ID's of all Properties stored in the Container. The ID's cannot
+ * be modified through the returned collection.
+ *
+ * @return unmodifiable collection of Property IDs
+ */
+ public Collection> getContainerPropertyIds();
+
+ /**
+ * Gets the ID's of all visible (after filtering and sorting) Items stored
+ * in the Container. The ID's cannot be modified through the returned
+ * collection.
+ *
+ * If the container is {@link Ordered}, the collection returned by this
+ * method should follow that order. If the container is {@link Sortable},
+ * the items should be in the sorted order.
+ *
+ * Calling this method for large lazy containers can be an expensive
+ * operation and should be avoided when practical.
+ *
+ * @return unmodifiable collection of Item IDs
+ */
+ public Collection> getItemIds();
+
+ /**
+ * Gets the Property identified by the given itemId and propertyId from the
+ * Container. If the Container does not contain the item or it is filtered
+ * out, or the Container does not have the Property, null is
+ * returned.
+ *
+ * @param itemId
+ * ID of the visible Item which contains the Property
+ * @param propertyId
+ * ID of the Property to retrieve
+ * @return Property with the given ID or null
+ */
+ public Property> getContainerProperty(Object itemId, Object propertyId);
+
+ /**
+ * Gets the data type of all Properties identified by the given Property ID.
+ *
+ * @param propertyId
+ * ID identifying the Properties
+ * @return data type of the Properties
+ */
+ public Class> getType(Object propertyId);
+
+ /**
+ * Gets the number of visible Items in the Container.
+ *
+ * Filtering can hide items so that they will not be visible through the
+ * container API.
+ *
+ * @return number of Items in the Container
+ */
+ public int size();
+
+ /**
+ * Tests if the Container contains the specified Item.
+ *
+ * Filtering can hide items so that they will not be visible through the
+ * container API, and this method should respect visibility of items (i.e.
+ * only indicate visible items as being in the container) if feasible for
+ * the container.
+ *
+ * @param itemId
+ * ID the of Item to be tested
+ * @return boolean indicating if the Container holds the specified Item
+ */
+ public boolean containsId(Object itemId);
+
+ /**
+ * Creates a new Item with the given ID in the Container.
+ *
+ *
+ * The new Item is returned, and it is ready to have its Properties
+ * modified. Returns null if the operation fails or the
+ * Container already contains a Item with the given ID.
+ *
+ *
+ *
+ * This functionality is optional.
+ *
+ *
+ * @param itemId
+ * ID of the Item to be created
+ * @return Created new Item, or null in case of a failure
+ * @throws UnsupportedOperationException
+ * if adding an item with an explicit item ID is not supported
+ * by the container
+ */
+ public Item addItem(Object itemId) throws UnsupportedOperationException;
+
+ /**
+ * Creates a new Item into the Container, and assign it an automatic ID.
+ *
+ *
+ * The new ID is returned, or null if the operation fails.
+ * After a successful call you can use the {@link #getItem(Object ItemId)
+ * getItem}method to fetch the Item.
+ *
+ *
+ *
+ * This functionality is optional.
+ *
+ *
+ * @return ID of the newly created Item, or null in case of a
+ * failure
+ * @throws UnsupportedOperationException
+ * if adding an item without an explicit item ID is not
+ * supported by the container
+ */
+ public Object addItem() throws UnsupportedOperationException;
+
+ /**
+ * Removes the Item identified by ItemId from the Container.
+ *
+ *
+ * Containers that support filtering should also allow removing an item that
+ * is currently filtered out.
+ *
+ *
+ *
+ * This functionality is optional.
+ *
+ *
+ * @param itemId
+ * ID of the Item to remove
+ * @return true if the operation succeeded, false
+ * if not
+ * @throws UnsupportedOperationException
+ * if the container does not support removing individual items
+ */
+ public boolean removeItem(Object itemId)
+ throws UnsupportedOperationException;
+
+ /**
+ * Adds a new Property to all Items in the Container. The Property ID, data
+ * type and default value of the new Property are given as parameters.
+ *
+ * This functionality is optional.
+ *
+ * @param propertyId
+ * ID of the Property
+ * @param type
+ * Data type of the new Property
+ * @param defaultValue
+ * The value all created Properties are initialized to
+ * @return true if the operation succeeded, false
+ * if not
+ * @throws UnsupportedOperationException
+ * if the container does not support explicitly adding container
+ * properties
+ */
+ public boolean addContainerProperty(Object propertyId, Class> type,
+ Object defaultValue) throws UnsupportedOperationException;
+
+ /**
+ * Removes a Property specified by the given Property ID from the Container.
+ * Note that the Property will be removed from all Items in the Container.
+ *
+ * This functionality is optional.
+ *
+ * @param propertyId
+ * ID of the Property to remove
+ * @return true if the operation succeeded, false
+ * if not
+ * @throws UnsupportedOperationException
+ * if the container does not support removing container
+ * properties
+ */
+ public boolean removeContainerProperty(Object propertyId)
+ throws UnsupportedOperationException;
+
+ /**
+ * Removes all Items from the Container.
+ *
+ *
+ * Note that Property ID and type information is preserved. This
+ * functionality is optional.
+ *
+ *
+ * @return true if the operation succeeded, false
+ * if not
+ * @throws UnsupportedOperationException
+ * if the container does not support removing all items
+ */
+ public boolean removeAllItems() throws UnsupportedOperationException;
+
+ /**
+ * Interface for Container classes whose {@link Item}s can be traversed in
+ * order.
+ *
+ *
+ * If the container is filtered or sorted, the traversal applies to the
+ * filtered and sorted view.
+ *
+ *
+ * The addItemAfter() methods should apply filters to the added
+ * item after inserting it, possibly hiding it immediately. If the container
+ * is being sorted, they may add items at the correct sorted position
+ * instead of the given position. See also {@link Filterable} and
+ * {@link Sortable} for more information.
+ *
+ */
+ public interface Ordered extends Container {
+
+ /**
+ * Gets the ID of the Item following the Item that corresponds to
+ * itemId. If the given Item is the last or not found in
+ * the Container, null is returned.
+ *
+ * @param itemId
+ * ID of a visible Item in the Container
+ * @return ID of the next visible Item or null
+ */
+ public Object nextItemId(Object itemId);
+
+ /**
+ * Gets the ID of the Item preceding the Item that corresponds to
+ * itemId. If the given Item is the first or not found in
+ * the Container, null is returned.
+ *
+ * @param itemId
+ * ID of a visible Item in the Container
+ * @return ID of the previous visible Item or null
+ */
+ public Object prevItemId(Object itemId);
+
+ /**
+ * Gets the ID of the first Item in the Container.
+ *
+ * @return ID of the first visible Item in the Container
+ */
+ public Object firstItemId();
+
+ /**
+ * Gets the ID of the last Item in the Container..
+ *
+ * @return ID of the last visible Item in the Container
+ */
+ public Object lastItemId();
+
+ /**
+ * Tests if the Item corresponding to the given Item ID is the first
+ * Item in the Container.
+ *
+ * @param itemId
+ * ID of an Item in the Container
+ * @return true if the Item is first visible item in the
+ * Container, false if not
+ */
+ public boolean isFirstId(Object itemId);
+
+ /**
+ * Tests if the Item corresponding to the given Item ID is the last Item
+ * in the Container.
+ *
+ * @return true if the Item is last visible item in the
+ * Container, false if not
+ */
+ public boolean isLastId(Object itemId);
+
+ /**
+ * Adds a new item after the given item.
+ *
+ * Adding an item after null item adds the item as first item of the
+ * ordered container.
+ *
+ *
+ * @see Ordered Ordered: adding items in filtered or sorted containers
+ *
+ * @param previousItemId
+ * Id of the visible item in ordered container after which to
+ * insert the new item.
+ * @return item id the the created new item or null if the operation
+ * fails.
+ * @throws UnsupportedOperationException
+ * if the operation is not supported by the container
+ */
+ public Object addItemAfter(Object previousItemId)
+ throws UnsupportedOperationException;
+
+ /**
+ * Adds a new item after the given item.
+ *
+ * Adding an item after null item adds the item as first item of the
+ * ordered container.
+ *
+ *
+ * @see Ordered Ordered: adding items in filtered or sorted containers
+ *
+ * @param previousItemId
+ * Id of the visible item in ordered container after which to
+ * insert the new item.
+ * @param newItemId
+ * Id of the new item to be added.
+ * @return new item or null if the operation fails.
+ * @throws UnsupportedOperationException
+ * if the operation is not supported by the container
+ */
+ public Item addItemAfter(Object previousItemId, Object newItemId)
+ throws UnsupportedOperationException;
+
+ }
+
+ /**
+ * Interface for Container classes whose {@link Item}s can be sorted.
+ *
+ * When an {@link Ordered} or {@link Indexed} container is sorted, all
+ * relevant operations of these interfaces should only use the filtered and
+ * sorted contents and the filtered indices to the container. Indices or
+ * item identifiers in the public API refer to the visible view unless
+ * otherwise stated. However, the addItem*() methods may add
+ * items that will be filtered out after addition or moved to another
+ * position based on sorting.
+ *
+ *
+ * How sorting is performed when a {@link Hierarchical} container implements
+ * {@link Sortable} is implementation specific and should be documented in
+ * the implementing class. However, the recommended approach is sorting the
+ * roots and the sets of children of each item separately.
+ *
+ *
+ * Depending on the container type, sorting a container may permanently
+ * change the internal order of items in the container.
+ *
+ */
+ public interface Sortable extends Ordered {
+
+ /**
+ * Sort method.
+ *
+ * Sorts the container items.
+ *
+ * Sorting a container can irreversibly change the order of its items or
+ * only change the order temporarily, depending on the container.
+ *
+ * @param propertyId
+ * Array of container property IDs, whose values are used to
+ * sort the items in container as primary, secondary, ...
+ * sorting criterion. All of the item IDs must be in the
+ * collection returned by
+ * {@link #getSortableContainerPropertyIds()}
+ * @param ascending
+ * Array of sorting order flags corresponding to each
+ * property ID used in sorting. If this array is shorter than
+ * propertyId array, ascending order is assumed for items
+ * where the order is not specified. Use true to
+ * sort in ascending order, false to use
+ * descending order.
+ */
+ void sort(Object[] propertyId, boolean[] ascending);
+
+ /**
+ * Gets the container property IDs which can be used to sort the items.
+ *
+ * @return the IDs of the properties that can be used for sorting the
+ * container
+ */
+ Collection> getSortableContainerPropertyIds();
+
+ }
+
+ /**
+ * Interface for Container classes whose {@link Item}s can be accessed by
+ * their position in the container.
+ *
+ * If the container is filtered or sorted, all indices refer to the filtered
+ * and sorted view. However, the addItemAt() methods may add
+ * items that will be filtered out after addition or moved to another
+ * position based on sorting.
+ *
+ */
+ public interface Indexed extends Ordered {
+
+ /**
+ * Gets the index of the Item corresponding to the itemId. The following
+ * is true for the returned index: 0 <= index < size(), or
+ * index = -1 if there is no visible item with that id in the container.
+ *
+ * @param itemId
+ * ID of an Item in the Container
+ * @return index of the Item, or -1 if (the filtered and sorted view of)
+ * the Container does not include the Item
+ */
+ public int indexOfId(Object itemId);
+
+ /**
+ * Gets the ID of an Item by an index number.
+ *
+ * @param index
+ * Index of the requested id in (the filtered and sorted view
+ * of) the Container
+ * @return ID of the Item in the given index
+ */
+ public Object getIdByIndex(int index);
+
+ /**
+ * Adds a new item at given index (in the filtered view).
+ *
+ * The indices of the item currently in the given position and all the
+ * following items are incremented.
+ *
+ *
+ * This method should apply filters to the added item after inserting
+ * it, possibly hiding it immediately. If the container is being sorted,
+ * the item may be added at the correct sorted position instead of the
+ * given position. See {@link Indexed}, {@link Ordered},
+ * {@link Filterable} and {@link Sortable} for more information.
+ *
+ *
+ * @param index
+ * Index (in the filtered and sorted view) to add the new
+ * item.
+ * @return item id of the created item or null if the operation fails.
+ * @throws UnsupportedOperationException
+ * if the operation is not supported by the container
+ */
+ public Object addItemAt(int index) throws UnsupportedOperationException;
+
+ /**
+ * Adds a new item at given index (in the filtered view).
+ *
+ * The indexes of the item currently in the given position and all the
+ * following items are incremented.
+ *
+ *
+ * This method should apply filters to the added item after inserting
+ * it, possibly hiding it immediately. If the container is being sorted,
+ * the item may be added at the correct sorted position instead of the
+ * given position. See {@link Indexed}, {@link Filterable} and
+ * {@link Sortable} for more information.
+ *
+ *
+ * @param index
+ * Index (in the filtered and sorted view) at which to add
+ * the new item.
+ * @param newItemId
+ * Id of the new item to be added.
+ * @return new {@link Item} or null if the operation fails.
+ * @throws UnsupportedOperationException
+ * if the operation is not supported by the container
+ */
+ public Item addItemAt(int index, Object newItemId)
+ throws UnsupportedOperationException;
+
+ }
+
+ /**
+ *
+ * Interface for Container classes whose Items can be arranged
+ * hierarchically. This means that the Items in the container belong in a
+ * tree-like structure, with the following quirks:
+ *
+ *
+ *
+ *
The Item structure may have more than one root elements
+ *
The Items in the hierarchy can be declared explicitly to be able or
+ * unable to have children.
+ *
+ */
+ public interface Hierarchical extends Container {
+
+ /**
+ * Gets the IDs of all Items that are children of the specified Item.
+ * The returned collection is unmodifiable.
+ *
+ * @param itemId
+ * ID of the Item whose children the caller is interested in
+ * @return An unmodifiable {@link java.util.Collection collection}
+ * containing the IDs of all other Items that are children in
+ * the container hierarchy
+ */
+ public Collection> getChildren(Object itemId);
+
+ /**
+ * Gets the ID of the parent Item of the specified Item.
+ *
+ * @param itemId
+ * ID of the Item whose parent the caller wishes to find out.
+ * @return the ID of the parent Item. Will be null if the
+ * specified Item is a root element.
+ */
+ public Object getParent(Object itemId);
+
+ /**
+ * Gets the IDs of all Items in the container that don't have a parent.
+ * Such items are called root Items. The returned
+ * collection is unmodifiable.
+ *
+ * @return An unmodifiable {@link java.util.Collection collection}
+ * containing IDs of all root elements of the container
+ */
+ public Collection> rootItemIds();
+
+ /**
+ *
+ * Sets the parent of an Item. The new parent item must exist and be
+ * able to have children. (
+ * {@link #areChildrenAllowed(Object)} == true ). It is
+ * also possible to detach a node from the hierarchy (and thus make it
+ * root) by setting the parent null.
+ *
+ *
+ *
+ * This operation is optional.
+ *
+ *
+ * @param itemId
+ * ID of the item to be set as the child of the Item
+ * identified with newParentId
+ * @param newParentId
+ * ID of the Item that's to be the new parent of the Item
+ * identified with itemId
+ * @return true if the operation succeeded,
+ * false if not
+ */
+ public boolean setParent(Object itemId, Object newParentId)
+ throws UnsupportedOperationException;
+
+ /**
+ * Tests if the Item with given ID can have children.
+ *
+ * @param itemId
+ * ID of the Item in the container whose child capability is
+ * to be tested
+ * @return true if the specified Item exists in the
+ * Container and it can have children, false if
+ * it's not found from the container or it can't have children.
+ */
+ public boolean areChildrenAllowed(Object itemId);
+
+ /**
+ *
+ * Sets the given Item's capability to have children. If the Item
+ * identified with itemId already has children and
+ * {@link #areChildrenAllowed(Object)} is false this method
+ * fails and false is returned.
+ *
+ *
+ * The children must be first explicitly removed with
+ * {@link #setParent(Object itemId, Object newParentId)}or
+ * {@link com.vaadin.data.Container#removeItem(Object itemId)}.
+ *
+ *
+ *
+ * This operation is optional. If it is not implemented, the method
+ * always returns false.
+ *
+ *
+ * @param itemId
+ * ID of the Item in the container whose child capability is
+ * to be set
+ * @param areChildrenAllowed
+ * boolean value specifying if the Item can have children or
+ * not
+ * @return true if the operation succeeded,
+ * false if not
+ */
+ public boolean setChildrenAllowed(Object itemId,
+ boolean areChildrenAllowed)
+ throws UnsupportedOperationException;
+
+ /**
+ * Tests if the Item specified with itemId is a root Item.
+ * The hierarchical container can have more than one root and must have
+ * at least one unless it is empty. The {@link #getParent(Object itemId)}
+ * method always returns null for root Items.
+ *
+ * @param itemId
+ * ID of the Item whose root status is to be tested
+ * @return true if the specified Item is a root,
+ * false if not
+ */
+ public boolean isRoot(Object itemId);
+
+ /**
+ *
+ * Tests if the Item specified with itemId has child Items
+ * or if it is a leaf. The {@link #getChildren(Object itemId)} method
+ * always returns null for leaf Items.
+ *
+ *
+ *
+ * Note that being a leaf does not imply whether or not an Item is
+ * allowed to have children.
+ *
+ * .
+ *
+ * @param itemId
+ * ID of the Item to be tested
+ * @return true if the specified Item has children,
+ * false if not (is a leaf)
+ */
+ public boolean hasChildren(Object itemId);
+
+ /**
+ *
+ * Removes the Item identified by ItemId from the
+ * Container.
+ *
+ *
+ *
+ * Note that this does not remove any children the item might have.
+ *
+ *
+ * @param itemId
+ * ID of the Item to remove
+ * @return true if the operation succeeded,
+ * false if not
+ */
+ @Override
+ public boolean removeItem(Object itemId)
+ throws UnsupportedOperationException;
+ }
+
+ /**
+ * Interface that is implemented by containers which allow reducing their
+ * visible contents based on a set of filters. This interface has been
+ * renamed from {@link Filterable}, and implementing the new
+ * {@link Filterable} instead of or in addition to {@link SimpleFilterable}
+ * is recommended. This interface might be removed in future Vaadin
+ * versions.
+ *
+ * When a set of filters are set, only items that match all the filters are
+ * included in the visible contents of the container. Still new items that
+ * do not match filters can be added to the container. Multiple filters can
+ * be added and the container remembers the state of the filters. When
+ * multiple filters are added, all filters must match for an item to be
+ * visible in the container.
+ *
+ *
+ * When an {@link Ordered} or {@link Indexed} container is filtered, all
+ * operations of these interfaces should only use the filtered contents and
+ * the filtered indices to the container.
+ *
+ *
+ * How filtering is performed when a {@link Hierarchical} container
+ * implements {@link SimpleFilterable} is implementation specific and should
+ * be documented in the implementing class.
+ *
+ *
+ * Adding items (if supported) to a filtered {@link Ordered} or
+ * {@link Indexed} container should insert them immediately after the
+ * indicated visible item. The unfiltered position of items added at index
+ * 0, at index {@link com.vaadin.data.Container#size()} or at an undefined
+ * position is up to the implementation.
+ *
+ *
+ * The functionality of SimpleFilterable can be implemented using the
+ * {@link Filterable} API and {@link SimpleStringFilter}.
+ *
+ *
+ * @since 5.0 (renamed from Filterable to SimpleFilterable in 6.6)
+ */
+ public interface SimpleFilterable extends Container, Serializable {
+
+ /**
+ * Add a filter for given property.
+ *
+ * The API {@link Filterable#addContainerFilter(Filter)} is recommended
+ * instead of this method. A {@link SimpleStringFilter} can be used with
+ * the new API to implement the old string filtering functionality.
+ *
+ * The filter accepts items for which toString() of the value of the
+ * given property contains or starts with given filterString. Other
+ * items are not visible in the container when filtered.
+ *
+ * If a container has multiple filters, only items accepted by all
+ * filters are visible.
+ *
+ * @param propertyId
+ * Property for which the filter is applied to.
+ * @param filterString
+ * String that must match the value of the property
+ * @param ignoreCase
+ * Determine if the casing can be ignored when comparing
+ * strings.
+ * @param onlyMatchPrefix
+ * Only match prefixes; no other matches are included.
+ */
+ public void addContainerFilter(Object propertyId, String filterString,
+ boolean ignoreCase, boolean onlyMatchPrefix);
+
+ /**
+ * Remove all filters from all properties.
+ */
+ public void removeAllContainerFilters();
+
+ /**
+ * Remove all filters from the given property.
+ *
+ * @param propertyId
+ * for which to remove filters
+ */
+ public void removeContainerFilters(Object propertyId);
+ }
+
+ /**
+ * Filter interface for container filtering.
+ *
+ * If a filter does not support in-memory filtering,
+ * {@link #passesFilter(Item)} should throw
+ * {@link UnsupportedOperationException}.
+ *
+ * Lazy containers must be able to map filters to their internal
+ * representation (e.g. SQL or JPA 2.0 Criteria).
+ *
+ * An {@link UnsupportedFilterException} can be thrown by the container if a
+ * particular filter is not supported by the container.
+ *
+ * An {@link Filter} should implement {@link #equals(Object)} and
+ * {@link #hashCode()} correctly to avoid duplicate filter registrations
+ * etc.
+ *
+ * @see Filterable
+ *
+ * @since 6.6
+ */
+ public interface Filter extends Serializable {
+
+ /**
+ * Check if an item passes the filter (in-memory filtering).
+ *
+ * @param itemId
+ * identifier of the item being filtered; may be null when
+ * the item is being added to the container
+ * @param item
+ * the item being filtered
+ * @return true if the item is accepted by this filter
+ * @throws UnsupportedOperationException
+ * if the filter cannot be used for in-memory filtering
+ */
+ public boolean passesFilter(Object itemId, Item item)
+ throws UnsupportedOperationException;
+
+ /**
+ * Check if a change in the value of a property can affect the filtering
+ * result. May always return true, at the cost of performance.
+ *
+ * If the filter cannot determine whether it may depend on the property
+ * or not, should return true.
+ *
+ * @param propertyId
+ * @return true if the filtering result may/does change based on changes
+ * to the property identified by propertyId
+ */
+ public boolean appliesToProperty(Object propertyId);
+
+ }
+
+ /**
+ * Interface that is implemented by containers which allow reducing their
+ * visible contents based on a set of filters.
+ *
+ * When a set of filters are set, only items that match all the filters are
+ * included in the visible contents of the container. Still new items that
+ * do not match filters can be added to the container. Multiple filters can
+ * be added and the container remembers the state of the filters. When
+ * multiple filters are added, all filters must match for an item to be
+ * visible in the container.
+ *
+ *
+ * When an {@link Ordered} or {@link Indexed} container is filtered, all
+ * operations of these interfaces should only use the filtered and sorted
+ * contents and the filtered indices to the container. Indices or item
+ * identifiers in the public API refer to the visible view unless otherwise
+ * stated. However, the addItem*() methods may add items that
+ * will be filtered out after addition or moved to another position based on
+ * sorting.
+ *
+ *
+ * How filtering is performed when a {@link Hierarchical} container
+ * implements {@link Filterable} is implementation specific and should be
+ * documented in the implementing class.
+ *
+ *
+ * Adding items (if supported) to a filtered {@link Ordered} or
+ * {@link Indexed} container should insert them immediately after the
+ * indicated visible item. However, the unfiltered position of items added
+ * at index 0, at index {@link com.vaadin.data.Container#size()} or at an
+ * undefined position is up to the implementation.
+ *
+ *
+ *
+ * This API replaces the old Filterable interface, renamed to
+ * {@link SimpleFilterable} in Vaadin 6.6.
+ *
+ *
+ * @since 6.6
+ */
+ public interface Filterable extends Container, Serializable {
+ /**
+ * Adds a filter for the container.
+ *
+ * If a container has multiple filters, only items accepted by all
+ * filters are visible.
+ *
+ * @throws UnsupportedFilterException
+ * if the filter is not supported by the container
+ */
+ public void addContainerFilter(Filter filter)
+ throws UnsupportedFilterException;
+
+ /**
+ * Removes a filter from the container.
+ *
+ * This requires that the equals() method considers the filters as
+ * equivalent (same instance or properly implemented equals() method).
+ */
+ public void removeContainerFilter(Filter filter);
+
+ /**
+ * Remove all active filters from the container.
+ */
+ public void removeAllContainerFilters();
+
+ }
+
+ /**
+ * Interface implemented by viewer classes capable of using a Container as a
+ * data source.
+ */
+ public interface Viewer extends Serializable {
+
+ /**
+ * Sets the Container that serves as the data source of the viewer.
+ *
+ * @param newDataSource
+ * The new data source Item
+ */
+ public void setContainerDataSource(Container newDataSource);
+
+ /**
+ * Gets the Container serving as the data source of the viewer.
+ *
+ * @return data source Container
+ */
+ public Container getContainerDataSource();
+
+ }
+
+ /**
+ *
+ * Interface implemented by the editor classes supporting editing the
+ * Container. Implementing this interface means that the Container serving
+ * as the data source of the editor can be modified through it.
+ *
+ *
+ * Note that not implementing the Container.Editor interface
+ * does not restrict the class from editing the Container contents
+ * internally.
+ *
+ */
+ public interface Editor extends Container.Viewer, Serializable {
+
+ }
+
+ /* Contents change event */
+
+ /**
+ * An Event object specifying the Container whose Item set has
+ * changed (items added, removed or reordered).
+ *
+ * A simple property value change is not an item set change.
+ */
+ public interface ItemSetChangeEvent extends Serializable {
+
+ /**
+ * Gets the Property where the event occurred.
+ *
+ * @return source of the event
+ */
+ public Container getContainer();
+ }
+
+ /**
+ * Container Item set change listener interface.
+ *
+ * An item set change refers to addition, removal or reordering of items in
+ * the container. A simple property value change is not an item set change.
+ */
+ public interface ItemSetChangeListener extends Serializable {
+
+ /**
+ * Lets the listener know a Containers visible (filtered and/or sorted,
+ * if applicable) Item set has changed.
+ *
+ * @param event
+ * change event text
+ */
+ public void containerItemSetChange(Container.ItemSetChangeEvent event);
+ }
+
+ /**
+ * The interface for adding and removing ItemSetChangeEvent
+ * listeners. By implementing this interface a class explicitly announces
+ * that it will generate a ItemSetChangeEvent when its contents
+ * are modified.
+ *
+ * An item set change refers to addition, removal or reordering of items in
+ * the container. A simple property value change is not an item set change.
+ *
+ *
+ * Note: The general Java convention is not to explicitly declare that a
+ * class generates events, but to directly define the
+ * addListener and removeListener methods. That
+ * way the caller of these methods has no real way of finding out if the
+ * class really will send the events, or if it just defines the methods to
+ * be able to implement an interface.
+ *
+ */
+ public interface ItemSetChangeNotifier extends Serializable {
+
+ /**
+ * Adds an Item set change listener for the object.
+ *
+ * @param listener
+ * listener to be added
+ */
+ public void addListener(Container.ItemSetChangeListener listener);
+
+ /**
+ * Removes the Item set change listener from the object.
+ *
+ * @param listener
+ * listener to be removed
+ */
+ public void removeListener(Container.ItemSetChangeListener listener);
+ }
+
+ /* Property set change event */
+
+ /**
+ * An Event object specifying the Container whose Property set
+ * has changed.
+ *
+ * A property set change means the addition, removal or other structural
+ * changes to the properties of a container. Changes concerning the set of
+ * items in the container and their property values are not property set
+ * changes.
+ */
+ public interface PropertySetChangeEvent extends Serializable {
+
+ /**
+ * Retrieves the Container whose contents have been modified.
+ *
+ * @return Source Container of the event.
+ */
+ public Container getContainer();
+ }
+
+ /**
+ * The listener interface for receiving PropertySetChangeEvent
+ * objects.
+ *
+ * A property set change means the addition, removal or other structural
+ * change of the properties (supported property IDs) of a container. Changes
+ * concerning the set of items in the container and their property values
+ * are not property set changes.
+ */
+ public interface PropertySetChangeListener extends Serializable {
+
+ /**
+ * Notifies this listener that the set of property IDs supported by the
+ * Container has changed.
+ *
+ * @param event
+ * Change event.
+ */
+ public void containerPropertySetChange(
+ Container.PropertySetChangeEvent event);
+ }
+
+ /**
+ *
+ * The interface for adding and removing PropertySetChangeEvent
+ * listeners. By implementing this interface a class explicitly announces
+ * that it will generate a PropertySetChangeEvent when the set
+ * of property IDs supported by the container is modified.
+ *
+ *
+ *
+ * A property set change means the addition, removal or other structural
+ * changes to the properties of a container. Changes concerning the set of
+ * items in the container and their property values are not property set
+ * changes.
+ *
+ *
+ *
+ * Note that the general Java convention is not to explicitly declare that a
+ * class generates events, but to directly define the
+ * addListener and removeListener methods. That
+ * way the caller of these methods has no real way of finding out if the
+ * class really will send the events, or if it just defines the methods to
+ * be able to implement an interface.
+ *
+ */
+ public interface PropertySetChangeNotifier extends Serializable {
+
+ /**
+ * Registers a new Property set change listener for this Container.
+ *
+ * @param listener
+ * The new Listener to be registered
+ */
+ public void addListener(Container.PropertySetChangeListener listener);
+
+ /**
+ * Removes a previously registered Property set change listener.
+ *
+ * @param listener
+ * Listener to be removed
+ */
+ public void removeListener(Container.PropertySetChangeListener listener);
+ }
+}
diff --git a/server/src/com/vaadin/data/Item.java b/server/src/com/vaadin/data/Item.java
new file mode 100644
index 0000000000..98b95aecff
--- /dev/null
+++ b/server/src/com/vaadin/data/Item.java
@@ -0,0 +1,180 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.data;
+
+import java.io.Serializable;
+import java.util.Collection;
+
+/**
+ *
+ * Provides a mechanism for handling a set of Properties, each associated to a
+ * locally unique non-null identifier. The interface is split into subinterfaces
+ * to enable a class to implement only the functionalities it needs.
+ *
+ *
+ * @author Vaadin Ltd
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+public interface Item extends Serializable {
+
+ /**
+ * Gets the Property corresponding to the given Property ID stored in the
+ * Item. If the Item does not contain the Property, null is
+ * returned.
+ *
+ * @param id
+ * identifier of the Property to get
+ * @return the Property with the given ID or null
+ */
+ public Property> getItemProperty(Object id);
+
+ /**
+ * Gets the collection of IDs of all Properties stored in the Item.
+ *
+ * @return unmodifiable collection containing IDs of the Properties stored
+ * the Item
+ */
+ public Collection> getItemPropertyIds();
+
+ /**
+ * Tries to add a new Property into the Item.
+ *
+ *
+ * This functionality is optional.
+ *
+ *
+ * @param id
+ * ID of the new Property
+ * @param property
+ * the Property to be added and associated with the id
+ * @return true if the operation succeeded, false
+ * if not
+ * @throws UnsupportedOperationException
+ * if the operation is not supported.
+ */
+ public boolean addItemProperty(Object id, Property property)
+ throws UnsupportedOperationException;
+
+ /**
+ * Removes the Property identified by ID from the Item.
+ *
+ *
+ * This functionality is optional.
+ *
+ *
+ * @param id
+ * ID of the Property to be removed
+ * @return true if the operation succeeded
+ * @throws UnsupportedOperationException
+ * if the operation is not supported. false if not
+ */
+ public boolean removeItemProperty(Object id)
+ throws UnsupportedOperationException;
+
+ /**
+ * Interface implemented by viewer classes capable of using an Item as a
+ * data source.
+ */
+ public interface Viewer extends Serializable {
+
+ /**
+ * Sets the Item that serves as the data source of the viewer.
+ *
+ * @param newDataSource
+ * The new data source Item
+ */
+ public void setItemDataSource(Item newDataSource);
+
+ /**
+ * Gets the Item serving as the data source of the viewer.
+ *
+ * @return data source Item
+ */
+ public Item getItemDataSource();
+ }
+
+ /**
+ * Interface implemented by the Editor classes capable of
+ * editing the Item. Implementing this interface means that the Item serving
+ * as the data source of the editor can be modified through it.
+ *
+ * Note : Not implementing the Item.Editor interface does not
+ * restrict the class from editing the contents of an internally.
+ *
+ */
+ public interface Editor extends Item.Viewer, Serializable {
+
+ }
+
+ /* Property set change event */
+
+ /**
+ * An Event object specifying the Item whose contents has been
+ * changed through the Property interface.
+ *
+ * Note: The values stored in the Properties may change without triggering
+ * this event.
+ *
+ */
+ public interface PropertySetChangeEvent extends Serializable {
+
+ /**
+ * Retrieves the Item whose contents has been modified.
+ *
+ * @return source Item of the event
+ */
+ public Item getItem();
+ }
+
+ /**
+ * The listener interface for receiving PropertySetChangeEvent
+ * objects.
+ */
+ public interface PropertySetChangeListener extends Serializable {
+
+ /**
+ * Notifies this listener that the Item's property set has changed.
+ *
+ * @param event
+ * Property set change event object
+ */
+ public void itemPropertySetChange(Item.PropertySetChangeEvent event);
+ }
+
+ /**
+ * The interface for adding and removing PropertySetChangeEvent
+ * listeners. By implementing this interface a class explicitly announces
+ * that it will generate a PropertySetChangeEvent when its
+ * Property set is modified.
+ *
+ * Note : The general Java convention is not to explicitly declare that a
+ * class generates events, but to directly define the
+ * addListener and removeListener methods. That
+ * way the caller of these methods has no real way of finding out if the
+ * class really will send the events, or if it just defines the methods to
+ * be able to implement an interface.
+ *
+ */
+ public interface PropertySetChangeNotifier extends Serializable {
+
+ /**
+ * Registers a new property set change listener for this Item.
+ *
+ * @param listener
+ * The new Listener to be registered.
+ */
+ public void addListener(Item.PropertySetChangeListener listener);
+
+ /**
+ * Removes a previously registered property set change listener.
+ *
+ * @param listener
+ * Listener to be removed.
+ */
+ public void removeListener(Item.PropertySetChangeListener listener);
+ }
+}
diff --git a/server/src/com/vaadin/data/Property.java b/server/src/com/vaadin/data/Property.java
new file mode 100644
index 0000000000..9fab642381
--- /dev/null
+++ b/server/src/com/vaadin/data/Property.java
@@ -0,0 +1,402 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.data;
+
+import java.io.Serializable;
+
+/**
+ *
+ * The Property is a simple data object that contains one typed
+ * value. This interface contains methods to inspect and modify the stored value
+ * and its type, and the object's read-only state.
+ *
+ *
+ *
+ * The Property also defines the events
+ * ReadOnlyStatusChangeEvent and ValueChangeEvent, and
+ * the associated listener and notifier interfaces.
+ *
+ *
+ *
+ * The Property.Viewer interface should be used to attach the
+ * Property to an external data source. This way the value in the data source
+ * can be inspected using the Property interface.
+ *
+ *
+ *
+ * The Property.editor interface should be implemented if the value
+ * needs to be changed through the implementing class.
+ *
+ *
+ * @param T
+ * type of values of the property
+ *
+ * @author Vaadin Ltd
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+public interface Property extends Serializable {
+
+ /**
+ * Gets the value stored in the Property. The returned object is compatible
+ * with the class returned by getType().
+ *
+ * @return the value stored in the Property
+ */
+ public T getValue();
+
+ /**
+ * Sets the value of the Property.
+ *
+ * Implementing this functionality is optional. If the functionality is
+ * missing, one should declare the Property to be in read-only mode and
+ * throw Property.ReadOnlyException in this function.
+ *
+ *
+ * Note : Since Vaadin 7.0, setting the value of a non-String property as a
+ * String is no longer supported.
+ *
+ * @param newValue
+ * New value of the Property. This should be assignable to the
+ * type returned by getType
+ *
+ * @throws Property.ReadOnlyException
+ * if the object is in read-only mode
+ */
+ public void setValue(Object newValue) throws Property.ReadOnlyException;
+
+ /**
+ * Returns the type of the Property. The methods getValue and
+ * setValue must be compatible with this type: one must be able
+ * to safely cast the value returned from getValue to the given
+ * type and pass any variable assignable to this type as an argument to
+ * setValue.
+ *
+ * @return type of the Property
+ */
+ public Class extends T> getType();
+
+ /**
+ * Tests if the Property is in read-only mode. In read-only mode calls to
+ * the method setValue will throw
+ * ReadOnlyException and will not modify the value of the
+ * Property.
+ *
+ * @return true if the Property is in read-only mode,
+ * false if it's not
+ */
+ public boolean isReadOnly();
+
+ /**
+ * Sets the Property's read-only mode to the specified status.
+ *
+ * This functionality is optional, but all properties must implement the
+ * isReadOnly mode query correctly.
+ *
+ * @param newStatus
+ * new read-only status of the Property
+ */
+ public void setReadOnly(boolean newStatus);
+
+ /**
+ * A Property that is capable of handle a transaction that can end in commit
+ * or rollback.
+ *
+ * Note that this does not refer to e.g. database transactions but rather
+ * two-phase commit that allows resetting old field values on a form etc. if
+ * the commit of one of the properties fails after others have already been
+ * committed. If
+ *
+ * @param
+ * The type of the property
+ * @author Vaadin Ltd
+ * @version @version@
+ * @since 7.0
+ */
+ public interface Transactional extends Property {
+
+ /**
+ * Starts a transaction.
+ *
+ *
+ * If the value is set during a transaction the value must not replace
+ * the original value until {@link #commit()} is called. Still,
+ * {@link #getValue()} must return the current value set in the
+ * transaction. Calling {@link #rollback()} while in a transaction must
+ * rollback the value to what it was before the transaction started.
+ *
+ *
+ * {@link ValueChangeEvent}s must not be emitted for internal value
+ * changes during a transaction. If the value changes as a result of
+ * {@link #commit()}, a {@link ValueChangeEvent} should be emitted.
+ *
+ */
+ public void startTransaction();
+
+ /**
+ * Commits and ends the transaction that is in progress.
+ *
+ * If the value is changed as a result of this operation, a
+ * {@link ValueChangeEvent} is emitted if such are supported.
+ *
+ * This method has no effect if there is no transaction is in progress.
+ *
+ * This method must never throw an exception.
+ */
+ public void commit();
+
+ /**
+ * Aborts and rolls back the transaction that is in progress.
+ *
+ * The value is reset to the value before the transaction started. No
+ * {@link ValueChangeEvent} is emitted as a result of this.
+ *
+ * This method has no effect if there is no transaction is in progress.
+ *
+ * This method must never throw an exception.
+ */
+ public void rollback();
+ }
+
+ /**
+ * Exception object that signals that a requested Property
+ * modification failed because it's in read-only mode.
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ @SuppressWarnings("serial")
+ public class ReadOnlyException extends RuntimeException {
+
+ /**
+ * Constructs a new ReadOnlyException without a detail
+ * message.
+ */
+ public ReadOnlyException() {
+ }
+
+ /**
+ * Constructs a new ReadOnlyException with the specified
+ * detail message.
+ *
+ * @param msg
+ * the detail message
+ */
+ public ReadOnlyException(String msg) {
+ super(msg);
+ }
+ }
+
+ /**
+ * Interface implemented by the viewer classes capable of using a Property
+ * as a data source.
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ public interface Viewer extends Serializable {
+
+ /**
+ * Sets the Property that serves as the data source of the viewer.
+ *
+ * @param newDataSource
+ * the new data source Property
+ */
+ public void setPropertyDataSource(Property newDataSource);
+
+ /**
+ * Gets the Property serving as the data source of the viewer.
+ *
+ * @return the Property serving as the viewers data source
+ */
+ public Property getPropertyDataSource();
+ }
+
+ /**
+ * Interface implemented by the editor classes capable of editing the
+ * Property.
+ *
+ * Implementing this interface means that the Property serving as the data
+ * source of the editor can be modified through the editor. It does not
+ * restrict the editor from editing the Property internally, though if the
+ * Property is in a read-only mode, attempts to modify it will result in the
+ * ReadOnlyException being thrown.
+ *
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ public interface Editor extends Property.Viewer, Serializable {
+
+ }
+
+ /* Value change event */
+
+ /**
+ * An Event object specifying the Property whose value has been
+ * changed.
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ public interface ValueChangeEvent extends Serializable {
+
+ /**
+ * Retrieves the Property that has been modified.
+ *
+ * @return source Property of the event
+ */
+ public Property getProperty();
+ }
+
+ /**
+ * The listener interface for receiving
+ * ValueChangeEvent objects.
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ public interface ValueChangeListener extends Serializable {
+
+ /**
+ * Notifies this listener that the Property's value has changed.
+ *
+ * @param event
+ * value change event object
+ */
+ public void valueChange(Property.ValueChangeEvent event);
+ }
+
+ /**
+ * The interface for adding and removing ValueChangeEvent
+ * listeners. If a Property wishes to allow other objects to receive
+ * ValueChangeEvent generated by it, it must implement this
+ * interface.
+ *
+ * Note : The general Java convention is not to explicitly declare that a
+ * class generates events, but to directly define the
+ * addListener and removeListener methods. That
+ * way the caller of these methods has no real way of finding out if the
+ * class really will send the events, or if it just defines the methods to
+ * be able to implement an interface.
+ *
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ public interface ValueChangeNotifier extends Serializable {
+
+ /**
+ * Registers a new value change listener for this Property.
+ *
+ * @param listener
+ * the new Listener to be registered
+ */
+ public void addListener(Property.ValueChangeListener listener);
+
+ /**
+ * Removes a previously registered value change listener.
+ *
+ * @param listener
+ * listener to be removed
+ */
+ public void removeListener(Property.ValueChangeListener listener);
+ }
+
+ /* ReadOnly Status change event */
+
+ /**
+ * An Event object specifying the Property whose read-only
+ * status has been changed.
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ public interface ReadOnlyStatusChangeEvent extends Serializable {
+
+ /**
+ * Property whose read-only state has changed.
+ *
+ * @return source Property of the event.
+ */
+ public Property getProperty();
+ }
+
+ /**
+ * The listener interface for receiving
+ * ReadOnlyStatusChangeEvent objects.
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ public interface ReadOnlyStatusChangeListener extends Serializable {
+
+ /**
+ * Notifies this listener that a Property's read-only status has
+ * changed.
+ *
+ * @param event
+ * Read-only status change event object
+ */
+ public void readOnlyStatusChange(
+ Property.ReadOnlyStatusChangeEvent event);
+ }
+
+ /**
+ * The interface for adding and removing
+ * ReadOnlyStatusChangeEvent listeners. If a Property wishes to
+ * allow other objects to receive ReadOnlyStatusChangeEvent
+ * generated by it, it must implement this interface.
+ *
+ * Note : The general Java convention is not to explicitly declare that a
+ * class generates events, but to directly define the
+ * addListener and removeListener methods. That
+ * way the caller of these methods has no real way of finding out if the
+ * class really will send the events, or if it just defines the methods to
+ * be able to implement an interface.
+ *
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ public interface ReadOnlyStatusChangeNotifier extends Serializable {
+
+ /**
+ * Registers a new read-only status change listener for this Property.
+ *
+ * @param listener
+ * the new Listener to be registered
+ */
+ public void addListener(Property.ReadOnlyStatusChangeListener listener);
+
+ /**
+ * Removes a previously registered read-only status change listener.
+ *
+ * @param listener
+ * listener to be removed
+ */
+ public void removeListener(
+ Property.ReadOnlyStatusChangeListener listener);
+ }
+}
diff --git a/server/src/com/vaadin/data/Validatable.java b/server/src/com/vaadin/data/Validatable.java
new file mode 100644
index 0000000000..4a7a0fda10
--- /dev/null
+++ b/server/src/com/vaadin/data/Validatable.java
@@ -0,0 +1,110 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.data;
+
+import java.io.Serializable;
+import java.util.Collection;
+
+/**
+ *
+ * Interface for validatable objects. Defines methods to verify if the object's
+ * value is valid or not, and to add, remove and list registered validators of
+ * the object.
+ *
+ * Adds a new validator for this object. The validator's
+ * {@link Validator#validate(Object)} method is activated every time the
+ * object's value needs to be verified, that is, when the {@link #isValid()}
+ * method is called. This usually happens when the object's value changes.
+ *
+ * Removes a previously registered validator from the object. The specified
+ * validator is removed from the object and its validate method
+ * is no longer called in {@link #isValid()}.
+ *
+ * Lists all validators currently registered for the object. If no
+ * validators are registered, returns null.
+ *
+ *
+ * @return collection of validators or null
+ */
+ public Collection getValidators();
+
+ /**
+ *
+ * Tests the current value of the object against all registered validators.
+ * The registered validators are iterated and for each the
+ * {@link Validator#validate(Object)} method is called. If any validator
+ * throws the {@link Validator.InvalidValueException} this method returns
+ * false.
+ *
+ *
+ * @return true if the registered validators concur that the
+ * value is valid, false otherwise
+ */
+ public boolean isValid();
+
+ /**
+ *
+ * Checks the validity of the validatable. If the validatable is valid this
+ * method should do nothing, and if it's not valid, it should throw
+ * Validator.InvalidValueException
+ *
+ *
+ * @throws Validator.InvalidValueException
+ * if the value is not valid
+ */
+ public void validate() throws Validator.InvalidValueException;
+
+ /**
+ *
+ * Checks the validabtable object accept invalid values.The default value is
+ * true.
+ *
+ * Should the validabtable object accept invalid values. Supporting this
+ * configuration possibility is optional. By default invalid values are
+ * allowed.
+ *
+ *
+ * @param invalidValueAllowed
+ *
+ * @throws UnsupportedOperationException
+ * if the setInvalidAllowed is not supported.
+ */
+ public void setInvalidAllowed(boolean invalidValueAllowed)
+ throws UnsupportedOperationException;
+
+}
diff --git a/server/src/com/vaadin/data/Validator.java b/server/src/com/vaadin/data/Validator.java
new file mode 100644
index 0000000000..768a02babe
--- /dev/null
+++ b/server/src/com/vaadin/data/Validator.java
@@ -0,0 +1,175 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.data;
+
+import java.io.Serializable;
+
+import com.vaadin.terminal.gwt.server.AbstractApplicationServlet;
+
+/**
+ * Interface that implements a method for validating if an {@link Object} is
+ * valid or not.
+ *
+ * Implementors of this class can be added to any
+ * {@link com.vaadin.data.Validatable Validatable} implementor to verify its
+ * value.
+ *
+ *
+ * {@link #validate(Object)} can be used to check if a value is valid. An
+ * {@link InvalidValueException} with an appropriate validation error message is
+ * thrown if the value is not valid.
+ *
+ *
+ * Validators must not have any side effects.
+ *
+ *
+ * Since Vaadin 7, the method isValid(Object) does not exist in the interface -
+ * {@link #validate(Object)} should be used instead, and the exception caught
+ * where applicable. Concrete classes implementing {@link Validator} can still
+ * internally implement and use isValid(Object) for convenience or to ease
+ * migration from earlier Vaadin versions.
+ *
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+public interface Validator extends Serializable {
+
+ /**
+ * Checks the given value against this validator. If the value is valid the
+ * method does nothing. If the value is invalid, an
+ * {@link InvalidValueException} is thrown.
+ *
+ * @param value
+ * the value to check
+ * @throws Validator.InvalidValueException
+ * if the value is invalid
+ */
+ public void validate(Object value) throws Validator.InvalidValueException;
+
+ /**
+ * Exception that is thrown by a {@link Validator} when a value is invalid.
+ *
+ *
+ * The default implementation of InvalidValueException does not support HTML
+ * in error messages. To enable HTML support, override
+ * {@link #getHtmlMessage()} and use the subclass in validators.
+ *
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ @SuppressWarnings("serial")
+ public class InvalidValueException extends RuntimeException {
+
+ /**
+ * Array of one or more validation errors that are causing this
+ * validation error.
+ */
+ private InvalidValueException[] causes = null;
+
+ /**
+ * Constructs a new {@code InvalidValueException} with the specified
+ * message.
+ *
+ * @param message
+ * The detail message of the problem.
+ */
+ public InvalidValueException(String message) {
+ this(message, new InvalidValueException[] {});
+ }
+
+ /**
+ * Constructs a new {@code InvalidValueException} with a set of causing
+ * validation exceptions. The causing validation exceptions are included
+ * when the exception is painted to the client.
+ *
+ * @param message
+ * The detail message of the problem.
+ * @param causes
+ * One or more {@code InvalidValueException}s that caused
+ * this exception.
+ */
+ public InvalidValueException(String message,
+ InvalidValueException[] causes) {
+ super(message);
+ if (causes == null) {
+ throw new NullPointerException(
+ "Possible causes array must not be null");
+ }
+
+ this.causes = causes;
+ }
+
+ /**
+ * Check if the error message should be hidden.
+ *
+ * An empty (null or "") message is invisible unless it contains nested
+ * exceptions that are visible.
+ *
+ * @return true if the error message should be hidden, false otherwise
+ */
+ public boolean isInvisible() {
+ String msg = getMessage();
+ if (msg != null && msg.length() > 0) {
+ return false;
+ }
+ if (causes != null) {
+ for (int i = 0; i < causes.length; i++) {
+ if (!causes[i].isInvisible()) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns the message of the error in HTML.
+ *
+ * Note that this API may change in future versions.
+ */
+ public String getHtmlMessage() {
+ return AbstractApplicationServlet
+ .safeEscapeForHtml(getLocalizedMessage());
+ }
+
+ /**
+ * Returns the {@code InvalidValueExceptions} that caused this
+ * exception.
+ *
+ * @return An array containing the {@code InvalidValueExceptions} that
+ * caused this exception. Returns an empty array if this
+ * exception was not caused by other exceptions.
+ */
+ public InvalidValueException[] getCauses() {
+ return causes;
+ }
+
+ }
+
+ /**
+ * A specific type of {@link InvalidValueException} that indicates that
+ * validation failed because the value was empty. What empty means is up to
+ * the thrower.
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 5.3.0
+ */
+ @SuppressWarnings("serial")
+ public class EmptyValueException extends Validator.InvalidValueException {
+
+ public EmptyValueException(String message) {
+ super(message);
+ }
+
+ }
+}
diff --git a/server/src/com/vaadin/data/fieldgroup/BeanFieldGroup.java b/server/src/com/vaadin/data/fieldgroup/BeanFieldGroup.java
new file mode 100644
index 0000000000..b8efa5b1e4
--- /dev/null
+++ b/server/src/com/vaadin/data/fieldgroup/BeanFieldGroup.java
@@ -0,0 +1,157 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+package com.vaadin.data.fieldgroup;
+
+import java.lang.reflect.Method;
+
+import com.vaadin.data.Item;
+import com.vaadin.data.util.BeanItem;
+import com.vaadin.data.validator.BeanValidator;
+import com.vaadin.ui.Field;
+
+public class BeanFieldGroup extends FieldGroup {
+
+ private Class beanType;
+
+ private static Boolean beanValidationImplementationAvailable = null;
+
+ public BeanFieldGroup(Class beanType) {
+ this.beanType = beanType;
+ }
+
+ @Override
+ protected Class> getPropertyType(Object propertyId) {
+ if (getItemDataSource() != null) {
+ return super.getPropertyType(propertyId);
+ } else {
+ // Data source not set so we need to figure out the type manually
+ /*
+ * toString should never really be needed as propertyId should be of
+ * form "fieldName" or "fieldName.subField[.subField2]" but the
+ * method declaration comes from parent.
+ */
+ java.lang.reflect.Field f;
+ try {
+ f = getField(beanType, propertyId.toString());
+ return f.getType();
+ } catch (SecurityException e) {
+ throw new BindException("Cannot determine type of propertyId '"
+ + propertyId + "'.", e);
+ } catch (NoSuchFieldException e) {
+ throw new BindException("Cannot determine type of propertyId '"
+ + propertyId + "'. The propertyId was not found in "
+ + beanType.getName(), e);
+ }
+ }
+ }
+
+ private static java.lang.reflect.Field getField(Class> cls,
+ String propertyId) throws SecurityException, NoSuchFieldException {
+ if (propertyId.contains(".")) {
+ String[] parts = propertyId.split("\\.", 2);
+ // Get the type of the field in the "cls" class
+ java.lang.reflect.Field field1 = getField(cls, parts[0]);
+ // Find the rest from the sub type
+ return getField(field1.getType(), parts[1]);
+ } else {
+ try {
+ // Try to find the field directly in the given class
+ java.lang.reflect.Field field1 = cls
+ .getDeclaredField(propertyId);
+ return field1;
+ } catch (NoSuchFieldError e) {
+ // Try super classes until we reach Object
+ Class> superClass = cls.getSuperclass();
+ if (superClass != Object.class) {
+ return getField(superClass, propertyId);
+ } else {
+ throw e;
+ }
+ }
+ }
+ }
+
+ /**
+ * Helper method for setting the data source directly using a bean. This
+ * method wraps the bean in a {@link BeanItem} and calls
+ * {@link #setItemDataSource(Item)}.
+ *
+ * @param bean
+ * The bean to use as data source.
+ */
+ public void setItemDataSource(T bean) {
+ setItemDataSource(new BeanItem(bean));
+ }
+
+ @Override
+ public void setItemDataSource(Item item) {
+ if (!(item instanceof BeanItem)) {
+ throw new RuntimeException(getClass().getSimpleName()
+ + " only supports BeanItems as item data source");
+ }
+ super.setItemDataSource(item);
+ }
+
+ @Override
+ public BeanItem getItemDataSource() {
+ return (BeanItem) super.getItemDataSource();
+ }
+
+ @Override
+ public void bind(Field field, Object propertyId) {
+ if (getItemDataSource() != null) {
+ // The data source is set so the property must be found in the item.
+ // If it is not we try to add it.
+ try {
+ getItemProperty(propertyId);
+ } catch (BindException e) {
+ // Not found, try to add a nested property;
+ // BeanItem property ids are always strings so this is safe
+ getItemDataSource().addNestedProperty((String) propertyId);
+ }
+ }
+
+ super.bind(field, propertyId);
+ }
+
+ @Override
+ protected void configureField(Field> field) {
+ super.configureField(field);
+ // Add Bean validators if there are annotations
+ if (isBeanValidationImplementationAvailable()) {
+ BeanValidator validator = new BeanValidator(beanType,
+ getPropertyId(field).toString());
+ field.addValidator(validator);
+ if (field.getLocale() != null) {
+ validator.setLocale(field.getLocale());
+ }
+ }
+ }
+
+ /**
+ * Checks whether a bean validation implementation (e.g. Hibernate Validator
+ * or Apache Bean Validation) is available.
+ *
+ * TODO move this method to some more generic location
+ *
+ * @return true if a JSR-303 bean validation implementation is available
+ */
+ protected static boolean isBeanValidationImplementationAvailable() {
+ if (beanValidationImplementationAvailable != null) {
+ return beanValidationImplementationAvailable;
+ }
+ try {
+ Class> validationClass = Class
+ .forName("javax.validation.Validation");
+ Method buildFactoryMethod = validationClass
+ .getMethod("buildDefaultValidatorFactory");
+ Object factory = buildFactoryMethod.invoke(null);
+ beanValidationImplementationAvailable = (factory != null);
+ } catch (Exception e) {
+ // no bean validation implementation available
+ beanValidationImplementationAvailable = false;
+ }
+ return beanValidationImplementationAvailable;
+ }
+}
\ No newline at end of file
diff --git a/server/src/com/vaadin/data/fieldgroup/Caption.java b/server/src/com/vaadin/data/fieldgroup/Caption.java
new file mode 100644
index 0000000000..b990b720cd
--- /dev/null
+++ b/server/src/com/vaadin/data/fieldgroup/Caption.java
@@ -0,0 +1,15 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+package com.vaadin.data.fieldgroup;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ ElementType.FIELD })
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Caption {
+ String value();
+}
diff --git a/server/src/com/vaadin/data/fieldgroup/DefaultFieldGroupFieldFactory.java b/server/src/com/vaadin/data/fieldgroup/DefaultFieldGroupFieldFactory.java
new file mode 100644
index 0000000000..be0db328f2
--- /dev/null
+++ b/server/src/com/vaadin/data/fieldgroup/DefaultFieldGroupFieldFactory.java
@@ -0,0 +1,157 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+package com.vaadin.data.fieldgroup;
+
+import java.util.EnumSet;
+
+import com.vaadin.data.Item;
+import com.vaadin.data.fieldgroup.FieldGroup.BindException;
+import com.vaadin.ui.AbstractSelect;
+import com.vaadin.ui.AbstractTextField;
+import com.vaadin.ui.CheckBox;
+import com.vaadin.ui.ComboBox;
+import com.vaadin.ui.Field;
+import com.vaadin.ui.ListSelect;
+import com.vaadin.ui.NativeSelect;
+import com.vaadin.ui.OptionGroup;
+import com.vaadin.ui.RichTextArea;
+import com.vaadin.ui.Table;
+import com.vaadin.ui.TextField;
+
+public class DefaultFieldGroupFieldFactory implements FieldGroupFieldFactory {
+
+ public static final Object CAPTION_PROPERTY_ID = "Caption";
+
+ @Override
+ public T createField(Class> type, Class fieldType) {
+ if (Enum.class.isAssignableFrom(type)) {
+ return createEnumField(type, fieldType);
+ } else if (Boolean.class.isAssignableFrom(type)
+ || boolean.class.isAssignableFrom(type)) {
+ return createBooleanField(fieldType);
+ }
+ if (AbstractTextField.class.isAssignableFrom(fieldType)) {
+ return fieldType.cast(createAbstractTextField(fieldType
+ .asSubclass(AbstractTextField.class)));
+ } else if (fieldType == RichTextArea.class) {
+ return fieldType.cast(createRichTextArea());
+ }
+ return createDefaultField(type, fieldType);
+ }
+
+ protected RichTextArea createRichTextArea() {
+ RichTextArea rta = new RichTextArea();
+ rta.setImmediate(true);
+
+ return rta;
+ }
+
+ private T createEnumField(Class> type,
+ Class fieldType) {
+ if (AbstractSelect.class.isAssignableFrom(fieldType)) {
+ AbstractSelect s = createCompatibleSelect((Class extends AbstractSelect>) fieldType);
+ populateWithEnumData(s, (Class extends Enum>) type);
+ return (T) s;
+ }
+
+ return null;
+ }
+
+ protected AbstractSelect createCompatibleSelect(
+ Class extends AbstractSelect> fieldType) {
+ AbstractSelect select;
+ if (fieldType.isAssignableFrom(ListSelect.class)) {
+ select = new ListSelect();
+ select.setMultiSelect(false);
+ } else if (fieldType.isAssignableFrom(NativeSelect.class)) {
+ select = new NativeSelect();
+ } else if (fieldType.isAssignableFrom(OptionGroup.class)) {
+ select = new OptionGroup();
+ select.setMultiSelect(false);
+ } else if (fieldType.isAssignableFrom(Table.class)) {
+ Table t = new Table();
+ t.setSelectable(true);
+ select = t;
+ } else {
+ select = new ComboBox(null);
+ }
+ select.setImmediate(true);
+ select.setNullSelectionAllowed(false);
+
+ return select;
+ }
+
+ protected T createBooleanField(Class fieldType) {
+ if (fieldType.isAssignableFrom(CheckBox.class)) {
+ CheckBox cb = new CheckBox(null);
+ cb.setImmediate(true);
+ return (T) cb;
+ } else if (AbstractTextField.class.isAssignableFrom(fieldType)) {
+ return (T) createAbstractTextField((Class extends AbstractTextField>) fieldType);
+ }
+
+ return null;
+ }
+
+ protected T createAbstractTextField(
+ Class fieldType) {
+ if (fieldType == AbstractTextField.class) {
+ fieldType = (Class) TextField.class;
+ }
+ try {
+ T field = fieldType.newInstance();
+ field.setImmediate(true);
+ return field;
+ } catch (Exception e) {
+ throw new BindException("Could not create a field of type "
+ + fieldType, e);
+ }
+ }
+
+ /**
+ * Fallback when no specific field has been created. Typically returns a
+ * TextField.
+ *
+ * @param
+ * The type of field to create
+ * @param type
+ * The type of data that should be edited
+ * @param fieldType
+ * The type of field to create
+ * @return A field capable of editing the data or null if no field could be
+ * created
+ */
+ protected T createDefaultField(Class> type,
+ Class fieldType) {
+ if (fieldType.isAssignableFrom(TextField.class)) {
+ return fieldType.cast(createAbstractTextField(TextField.class));
+ }
+ return null;
+ }
+
+ /**
+ * Populates the given select with all the enums in the given {@link Enum}
+ * class. Uses {@link Enum}.toString() for caption.
+ *
+ * @param select
+ * The select to populate
+ * @param enumClass
+ * The Enum class to use
+ */
+ protected void populateWithEnumData(AbstractSelect select,
+ Class extends Enum> enumClass) {
+ select.removeAllItems();
+ for (Object p : select.getContainerPropertyIds()) {
+ select.removeContainerProperty(p);
+ }
+ select.addContainerProperty(CAPTION_PROPERTY_ID, String.class, "");
+ select.setItemCaptionPropertyId(CAPTION_PROPERTY_ID);
+ @SuppressWarnings("unchecked")
+ EnumSet> enumSet = EnumSet.allOf(enumClass);
+ for (Object r : enumSet) {
+ Item newItem = select.addItem(r);
+ newItem.getItemProperty(CAPTION_PROPERTY_ID).setValue(r.toString());
+ }
+ }
+}
diff --git a/server/src/com/vaadin/data/fieldgroup/FieldGroup.java b/server/src/com/vaadin/data/fieldgroup/FieldGroup.java
new file mode 100644
index 0000000000..3df19f5bc9
--- /dev/null
+++ b/server/src/com/vaadin/data/fieldgroup/FieldGroup.java
@@ -0,0 +1,978 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+package com.vaadin.data.fieldgroup;
+
+import java.io.Serializable;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.logging.Logger;
+
+import com.vaadin.data.Item;
+import com.vaadin.data.Property;
+import com.vaadin.data.Validator.InvalidValueException;
+import com.vaadin.data.util.TransactionalPropertyWrapper;
+import com.vaadin.tools.ReflectTools;
+import com.vaadin.ui.DefaultFieldFactory;
+import com.vaadin.ui.Field;
+import com.vaadin.ui.Form;
+
+/**
+ * FieldGroup provides an easy way of binding fields to data and handling
+ * commits of these fields.
+ *
+ * The functionality of FieldGroup is similar to {@link Form} but
+ * {@link FieldGroup} does not handle layouts in any way. The typical use case
+ * is to create a layout outside the FieldGroup and then use FieldGroup to bind
+ * the fields to a data source.
+ *
+ *
+ * {@link FieldGroup} is not a UI component so it cannot be added to a layout.
+ * Using the buildAndBind methods {@link FieldGroup} can create fields for you
+ * using a FieldGroupFieldFactory but you still have to add them to the correct
+ * position in your layout.
+ *