/* ************************************************************************* IT Mill Toolkit Development of Browser User Interfaces 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.pdf. Use of this product might require purchasing a commercial license from IT Mill Ltd. For guidelines on usage, see 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.service.License; 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; /** *
* Base class required for all IT Mill Toolkit applications. This class provides * all the basic services required by the toolkit. 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. *
* *
* As mentioned, all IT Mill Toolkit 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()
* 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.itmill.toolkit.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.itmill.toolkit.terminal.Terminal terminal} is used. The * terminal always defines a default theme. *
* * @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 ornull
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;
/** License for running this application */
private License license = 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 null
if the application is
* not running or it does not contain a window corresponding to
* name
.
*
* @param name
* The name of the window.
* @return The window associated with the given URI or null
*/
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.
*
* * This implicitly invokes the * {@link com.itmill.toolkit.ui.Window#setApplication(Application)} method. *
* * @param window * the newWindow
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 Window
or its name is
* null
*/
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 true
if the application is running,
* false
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
* null
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. null
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 = toBeUpdated.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;
}
/** This method is 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.
* You can safely override this method in your application in order to direct the errors
* to some other destination (for example log).
*
* @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;
}
/**
* Get the license this application is running on.
*
* The license is initialized by the ApplicationServlet before application
* is started. The the license-file can not be found in
* WEB-INF/itmill-toolkit-license.xml, you can set its source in application
* init().
*
* @return License this application is currently using
*/
public License getToolkitLicense() {
return license;
}
/**
* Set the license this application is currently using.
*
* The license is initialized by the ApplicationServlet before application
* is started. Changing the license after application init has no effect.
*
* @param license
* New license for this application.
*/
public void setToolkitLicense(License license) {
this.license = license;
}
}