diff options
Diffstat (limited to 'src/com/itmill/toolkit/Application.java')
-rw-r--r-- | src/com/itmill/toolkit/Application.java | 767 |
1 files changed, 767 insertions, 0 deletions
diff --git a/src/com/itmill/toolkit/Application.java b/src/com/itmill/toolkit/Application.java new file mode 100644 index 0000000000..dd12f5d65a --- /dev/null +++ b/src/com/itmill/toolkit/Application.java @@ -0,0 +1,767 @@ +/* ************************************************************************* + + IT Mill Toolkit + + Development of Browser User Intarfaces Made Easy + + Copyright (C) 2000-2006 IT Mill Ltd + + ************************************************************************* + + This product is distributed under commercial license that can be found + from the product package on license/license.txt. Use of this product might + require purchasing a commercial license from IT Mill Ltd. For guidelines + on usage, see license/licensing-guidelines.html + + ************************************************************************* + + For more information, contact: + + IT Mill Ltd phone: +358 2 4802 7180 + Ruukinkatu 2-4 fax: +358 2 4802 7181 + 20540, Turku email: info@itmill.com + Finland company www: www.itmill.com + + Primary source for information and releases: www.itmill.com + + ********************************************************************** */ + +package com.itmill.toolkit; + +import com.itmill.toolkit.service.ApplicationContext; +import com.itmill.toolkit.terminal.*; +import com.itmill.toolkit.ui.AbstractComponent; +import com.itmill.toolkit.ui.Window; + +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; +import java.util.EventListener; +import java.util.EventObject; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.Locale; +import java.util.Properties; +import java.util.Random; +import java.net.MalformedURLException; +import java.net.URL; + +/** <p>Abstract base class required for all MillStone applications. This + * class provides all the basic services required by the MillStone + * framework. These services allow external discovery and manipulation of + * the user, {@link com.itmill.toolkit.ui.Window windows} and + * themes, and starting and stopping the application.</p> + * + * <p>As mentioned, all MillStone applications must inherit this class. + * However, this is almost all of what one needs to do to create a fully + * functional MillStone application. The only thing a class inheriting the + * <code>Application</code> needs to do is implement the <code>init()</code> + * where it creates the windows it needs to perform its function. Note that + * all MillStone applications must have at least one window: the main + * window. The first unnamed window constructed by an application + * automatically becomes the main window which behaves just like other + * windows with one exception: when accessing windows using URLs the main + * window corresponds to the application URL whereas other windows + * correspond to a URL gotten by catenating the window's name to the + * application URL.</p> + * + * <p>See the class <code>com.itmill.toolkit.demo.HelloWorld</code> for + * a simple example of a fully working MillStone Application.</p> + * + * <p><strong>Window access.</strong> <code>Application</code> provides + * methods to list, add and remove the windows it contains.</p> + * + * <p><strong>Execution control.</strong> This class includes method to + * start and finish the execution of the application. Being finished means + * basically that no windows will be available from the application + * anymore.</p> + * + * <p><strong>Theme selection.</strong> The MillStone 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.itmill.toolkit.terminal.Terminal terminal} is used. The + * terminal always defines a default theme.</p> + * + * @author IT Mill Ltd. + * @version @VERSION@ + * @since 3.0 + */ +public abstract class Application + implements URIHandler, Terminal.ErrorListener { + + /** Random window name generator */ + private static Random nameGenerator = new Random(); + + /** Application context the application is running in */ + private ApplicationContext context; + + /** The current user or <code>null</code> if no user has logged in. */ + private Object user; + + /** Mapping from window name to window instance */ + private Hashtable windows = new Hashtable(); + + /** Main window of the application. */ + private Window mainWindow = null; + + /** The application's URL. */ + private URL applicationUrl; + + /** Name of the theme currently used by the application. */ + private String theme = null; + + /** Application status */ + private 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; + + /** Window attach listeners */ + private LinkedList windowAttachListeners = null; + + /** Window detach listeners */ + private LinkedList windowDetachListeners = null; + + /** Application error listeners */ + private LinkedList errorListeners = null; + + /** Application resource mapping: key <-> resource */ + private Hashtable resourceKeyMap = new Hashtable(); + private Hashtable keyResourceMap = new Hashtable(); + private long lastResourceKeyNumber = 0; + + /** URL the user is redirected to on application close or + * null if application is just closed */ + private String logoutURL = null; + + /** Gets a window by name. Returns <code>null</code> + * if the application is not running or it does not contain a window + * corresponding to <code>name</code>. + * + * @param name The name of the window. + * @return The window associated with the given URI or <code>null</code> + */ + public Window getWindow(String name) { + + // For closed app, do not give any windows + if (!isRunning()) + return null; + + // Get the window by name + Window window = (Window) windows.get(name); + + return window; + } + + /** Adds a new window to the application. + * + * <p>This implicitly invokes the + * {@link com.itmill.toolkit.ui.Window#setApplication(Application)} + * method. </p> + * + * @param window the new <code>Window</code> to add + * @throws IllegalArgumentException if a window with the same name + * as the new window already exists in the application + * @throws NullPointerException if the given <code>Window</code> or + * its name is <code>null</code> + */ + public void addWindow(Window window) + throws IllegalArgumentException, NullPointerException { + + // Nulls can not be added to application + if (window == null) + return; + + // Get the naming proposal from window + String name = window.getName(); + + // Check that the application does not already contain + // window having the same name + if (name != null && windows.containsKey(name)) { + + // If the window is already added + if (window == windows.get(name)) + return; + + // Otherwise complain + throw new IllegalArgumentException( + "Window with name '" + + window.getName() + + "' is already present in the application"); + } + + // If the name of the window is null, the window is automatically named + if (name == null) { + boolean accepted = false; + while (!accepted) { + + // Try another name + name = String.valueOf(Math.abs(nameGenerator.nextInt())); + if (!windows.containsKey(name)) + accepted = true; + } + window.setName(name); + } + + // Add the window to application + windows.put(name, window); + window.setApplication(this); + + // Fire window attach event + if (windowAttachListeners != null) { + Object[] listeners = windowAttachListeners.toArray(); + WindowAttachEvent event = new WindowAttachEvent(window); + for (int i = 0; i < listeners.length; i++) { + ((WindowAttachListener) listeners[i]).windowAttached(event); + } + } + + // If no main window is set, declare the window to be main window + if (getMainWindow() == null) + setMainWindow(window); + } + + /** Removes the specified window from the application. + * + * @param window The window to be removed + */ + public void removeWindow(Window window) { + if (window != null && windows.contains(window)) { + + // Remove window from application + windows.remove(window.getName()); + + // If the window was main window, clear it + if (getMainWindow() == window) + setMainWindow(null); + + // Remove application from window + if (window.getApplication() == this) + window.setApplication(null); + + // Fire window detach event + if (windowDetachListeners != null) { + Object[] listeners = windowDetachListeners.toArray(); + WindowDetachEvent event = new WindowDetachEvent(window); + for (int i = 0; i < listeners.length; i++) { + ((WindowDetachListener) listeners[i]).windowDetached(event); + } + } + } + } + + /** Gets the user of the application. + * + * @return 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. + * + * @param user the new user. + */ + public void setUser(Object user) { + Object prevUser = this.user; + if (user != prevUser && (user == null || !user.equals(prevUser))) { + this.user = user; + if (userChangeListeners != null) { + Object[] listeners = userChangeListeners.toArray(); + 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. + * + * @return the application's URL. + */ + public URL getURL() { + return applicationUrl; + } + + /** Ends the Application. In effect this will cause the application stop + * returning any windows when asked. + */ + public void close() { + applicationIsRunning = false; + } + + /** Starts the application on the given URL. After this call the + * application corresponds to the given URL and it will return windows + * when asked for them. + * + * @param applicationUrl The URL the application should respond to + * @param applicationProperties Application properties as specified by the adapter. + * @param context The context application will be running in + * + */ + public void start(URL applicationUrl, Properties applicationProperties, ApplicationContext context) { + this.applicationUrl = applicationUrl; + this.properties = applicationProperties; + this.context = context; + init(); + applicationIsRunning = true; + } + + /** Tests if the application is running or if it has it been finished. + * + * @return <code>true</code> if the application is running, + * <code>false</code> if not + */ + public boolean isRunning() { + return applicationIsRunning; + } + + /** Gets the set of windows contained by the application. + * + * @return Unmodifiable collection of windows + */ + public Collection getWindows() { + return Collections.unmodifiableCollection(windows.values()); + } + + /** Main initializer of the application. This method is called by the + * framework when the application is started, and it should perform + * whatever initialization operations the application needs, such + * as creating windows and adding components to them. + */ + public abstract void init(); + + /** Gets the application's theme. The application's theme is the default + * theme used by all the windows in it that do not explicitly specify a + * theme. If the application theme is not explicitly set, the + * <code>null</code> is returned. + * + * @return the name of the application's theme + */ + public String getTheme() { + return theme; + } + + /** Sets the application's theme. Note that this theme can be overridden + * by the windows. <code>null</code> implies the default terminal theme. + * + * @param theme The new theme for this application + */ + public void setTheme(String theme) { + + // Collect list of windows not having the current or future theme + LinkedList toBeUpdated = new LinkedList(); + String myTheme = this.getTheme(); + for (Iterator i = getWindows().iterator(); i.hasNext();) { + Window w = (Window) i.next(); + String windowTheme = w.getTheme(); + if ((windowTheme == null) + || (!theme.equals(windowTheme) && windowTheme.equals(myTheme))) { + toBeUpdated.add(w); + } + } + + // Update theme + this.theme = theme; + + // Ask windows to update themselves + for (Iterator i = getWindows().iterator(); i.hasNext();) + ((Window) i.next()).requestRepaint(); + } + + /** Returns the mainWindow of the application. + * @return Window + */ + public Window getMainWindow() { + return mainWindow; + } + + /** Sets the mainWindow. If the main window is not explicitly set, the + * main window defaults to first created window. Setting window as + * a main window of this application also adds the window to this application. + * @param mainWindow The mainWindow to set + */ + public void setMainWindow(Window mainWindow) { + + addWindow(mainWindow); + this.mainWindow = mainWindow; + } + + /** Returns an enumeration of all the names in this application. + * @return an enumeration of all the keys in this property list, including the keys in + * the default property list. + * + */ + public Enumeration getPropertyNames() { + return this.properties.propertyNames(); + } + + /** Searches for the property with the specified name in this application. + * The method returns null if the property is not found. + * + * @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 this.properties.getProperty(name); + } + + /** Add new resource to the application. The resource can be + * accessed by the user of the application. */ + public void addResource(ApplicationResource resource) { + + // Check if the resource is already mapped + if (resourceKeyMap.containsKey(resource)) + return; + + // Generate key + String key = String.valueOf(++lastResourceKeyNumber); + + // Add the resource to mappings + resourceKeyMap.put(resource, key); + keyResourceMap.put(key, resource); + } + + /** Remove resource from the application. */ + public void removeResource(ApplicationResource resource) { + Object key = resourceKeyMap.get(resource); + if (key != null) { + resourceKeyMap.remove(resource); + keyResourceMap.remove(key); + } + } + + /** Get relative uri of the resource */ + public String getRelativeLocation(ApplicationResource resource) { + + // Get the key + String key = (String) resourceKeyMap.get(resource); + + // If the resource is not registered, return null + if (key == null) + return null; + + String filename = resource.getFilename(); + if (filename == null) + return "APP/" + key + "/"; + else + return "APP/" + key + "/" + filename; + } + + /* @see com.itmill.toolkit.terminal.URIHandler#handleURI(URL, String) + */ + public DownloadStream handleURI( + URL context, + String relativeUri) { // If the relative uri is null, we are ready + if (relativeUri == null) + return null; + + // Resolve prefix + String prefix = relativeUri; + int index = relativeUri.indexOf('/'); + if (index >= 0) + prefix = relativeUri.substring(0, index); + + // Handle resource requests + if (prefix.equals("APP")) { + + // Handle resource request + int next = relativeUri.indexOf('/', index + 1); + if (next < 0) + return null; + String key = relativeUri.substring(index + 1, next); + ApplicationResource resource = + (ApplicationResource) keyResourceMap.get(key); + if (resource != null) + return resource.getStream(); + + // Resource requests override uri handling + return null; + } + + // If the uri is in some window, handle the window uri + Window window = getWindow(prefix); + if (window != null) { + URL windowContext; + try { + windowContext = new URL(context, prefix + "/"); + String windowUri = + relativeUri.length() > prefix.length() + 1 + ? relativeUri.substring(prefix.length() + 1) + : ""; + return window.handleURI(windowContext, windowUri); + } catch (MalformedURLException e) { + return null; + } + } + + // If the uri was not pointing to a window, handle the + // uri in main window + window = getMainWindow(); + if (window != null) + return window.handleURI(context, relativeUri); + + return null; + } + + /** Get thed default locale for this application */ + public Locale getLocale() { + if (this.locale != null) + return this.locale; + return Locale.getDefault(); + } + + /** Set the default locale for this application */ + public void setLocale(Locale locale) { + this.locale = locale; + } + + /** 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 { + + /** + * Serial generated by eclipse. + */ + private static final long serialVersionUID = 3544951069307188281L; + + /** New user of the application */ + private Object newUser; + + /** Previous user of the application */ + private Object prevUser; + + /** Contructor for user change event */ + public UserChangeEvent( + Application source, + Object newUser, + Object prevUser) { + super(source); + this.newUser = newUser; + this.prevUser = prevUser; + } + + /** Get the new user of the application */ + public Object getNewUser() { + return newUser; + } + + /** Get the previous user of the application */ + public Object getPreviousUser() { + return prevUser; + } + + /** Get the application where the user change occurred */ + public Application getApplication() { + return (Application) getSource(); + } + } + + /** Public interface for listening application user changes + * @version @VERSION@ + * @since 3.0 + */ + public interface UserChangeListener extends EventListener { + + /** Invoked when the application user has changed */ + public void applicationUserChanged(Application.UserChangeEvent event); + } + + /** Add user change listener */ + public void addListener(UserChangeListener listener) { + if (userChangeListeners == null) + userChangeListeners = new LinkedList(); + userChangeListeners.add(listener); + } + + /** Remove user change listener */ + public void removeListener(UserChangeListener listener) { + if (userChangeListeners == null) + return; + userChangeListeners.remove(listener); + if (userChangeListeners.isEmpty()) + userChangeListeners = null; + } + + /** Window detach event */ + public class WindowDetachEvent extends EventObject { + + /** + * Serial generated by eclipse. + */ + private static final long serialVersionUID = 3544669568644691769L; + private Window window; + + /** Create event. + * @param window Detached window. + */ + public WindowDetachEvent(Window window) { + super(Application.this); + this.window = window; + } + + /** Get the detached window */ + public Window getWindow() { + return window; + } + + /** Get the application from which the window was detached */ + public Application getApplication() { + return (Application) getSource(); + } + } + + /** Window attach event */ + public class WindowAttachEvent extends EventObject { + + /** + * Serial generated by eclipse. + */ + private static final long serialVersionUID = 3977578104367822392L; + private Window window; + + /** Create event. + * @param window Attached window. + */ + public WindowAttachEvent(Window window) { + super(Application.this); + this.window = window; + } + + /** Get the attached window */ + public Window getWindow() { + return window; + } + + /** Get the application to which the window was attached */ + public Application getApplication() { + return (Application) getSource(); + } + } + + /** Window attach listener interface */ + public interface WindowAttachListener { + + /** Window attached */ + public void windowAttached(WindowAttachEvent event); + } + + /** Window detach listener interface */ + public interface WindowDetachListener { + + /** Window attached */ + public void windowDetached(WindowDetachEvent event); + } + + /** Add window attach listener */ + public void addListener(WindowAttachListener listener) { + if (windowAttachListeners == null) + windowAttachListeners = new LinkedList(); + windowAttachListeners.add(listener); + } + + /** Add window detach listener */ + public void addListener(WindowDetachListener listener) { + if (windowDetachListeners == null) + windowDetachListeners = new LinkedList(); + windowDetachListeners.add(listener); + } + + /** Remove window attach listener */ + public void removeListener(WindowAttachListener listener) { + if (windowAttachListeners != null) { + windowAttachListeners.remove(listener); + if (windowAttachListeners.isEmpty()) + windowAttachListeners = null; + } + } + + /** Remove window detach listener */ + public void removeListener(WindowDetachListener listener) { + if (windowDetachListeners != null) { + windowDetachListeners.remove(listener); + if (windowDetachListeners.isEmpty()) + windowDetachListeners = null; + } + } + + /** 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: Desctop application + * just closes the application window and web-application redirects + * the browser to application main URL. + * + * @return 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: Desctop 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; + } + + /** + * @see com.itmill.toolkit.terminal.Terminal.ErrorListener#terminalError(com.itmill.toolkit.terminal.Terminal.ErrorEvent) + */ + public void terminalError(Terminal.ErrorEvent event) { + + // Find the original source of the error/exception + Object owner = null; + if (event instanceof VariableOwner.ErrorEvent) { + owner = ((VariableOwner.ErrorEvent) event).getVariableOwner(); + } else if (event instanceof URIHandler.ErrorEvent) { + owner = ((URIHandler.ErrorEvent) event).getURIHandler(); + } else if (event instanceof ParameterHandler.ErrorEvent) { + owner = ((ParameterHandler.ErrorEvent) event).getParameterHandler(); + } + + // Show the error in AbstractComponent + if (owner instanceof AbstractComponent) { + Throwable e = event.getThrowable(); + if (e instanceof ErrorMessage) + ((AbstractComponent) owner).setComponentError((ErrorMessage) e); + else + ((AbstractComponent) owner).setComponentError( + new SystemError(e)); + } + } + + /** Get application context. + * + * The application context is the environment where the application + * is running in. + */ + public ApplicationContext getContext() { + return context; + } + +}
\ No newline at end of file |