All Downloads are FREE. Search and download functionalities are using the official Maven repository.

nextapp.echo.app.ApplicationInstance Maven / Gradle / Ivy

There is a newer version: 3.1.0
Show newest version
/* 
 * This file is part of the Echo Web Application Framework (hereinafter "Echo").
 * Copyright (C) 2002-2009 NextApp, Inc.
 *
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 */

package nextapp.echo.app;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.Serializable;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import nextapp.echo.app.update.ServerUpdateManager;
import nextapp.echo.app.update.UpdateManager;
import nextapp.echo.app.util.Uid;

/**
 * A single user-instance of an Echo application.
 */
public abstract class ApplicationInstance 
implements Serializable {
    
    /** Serial Version UID. */
    private static final long serialVersionUID = 20070101L;

    /** The name and version of the Echo API in use. */
    public static final String ID_STRING = "NextApp Echo v3.0.rc1";

    public static final String FOCUSED_COMPONENT_CHANGED_PROPERTY = "focusedComponent";
    public static final String LOCALE_CHANGED_PROPERTY = "locale";
    public static final String MODAL_COMPONENTS_CHANGED_PROPERTY = "modalComponents";
    public static final String STYLE_SHEET_CHANGED_PROPERTY = "styleSheet";
    public static final String WINDOWS_CHANGED_PROPERTY = "windows";
    
    /** 
     * A ThreadLocal reference to the 
     * ApplicationInstance relevant to the current thread.
     */ 
    private static final ThreadLocal activeInstance = new ThreadLocal();
    
    /**
     * Determines the current modal component by searching the entire hierarchy for modal components.
     * This operation is only performed when multiple visibly rendered components are registered as modal.
     * 
     * @param searchComponent the root Component at which to start the search.
     * @param visibleModalComponents the set of visible modal components
     * @return the current modal component
     */
    private static Component findCurrentModalComponent(Component searchComponent, Set visibleModalComponents) {
        int count = searchComponent.getComponentCount();
        for (int i = count - 1; i >= 0; --i) {
            Component foundComponent = findCurrentModalComponent(searchComponent.getComponent(i), visibleModalComponents);
            if (foundComponent != null) {
                return foundComponent;
            }
        }

        if (searchComponent instanceof ModalSupport && ((ModalSupport) searchComponent).isModal()
                 && visibleModalComponents.contains(searchComponent)) {
            return searchComponent;
        }
        
        return null;
    }
    
    /**
     * Generates a system-level identifier (an identifier which is unique to all
     * ApplicationInstances).
     * 
     * @return the generated identifier
     * @see #generateId()
     */
    public static final String generateSystemId() {
        return Uid.generateUidString();
    }
    
    /**
     * Returns a reference to the ApplicationInstance that is 
     * relevant to the current thread, or null if no instance is relevant.
     * 
     * @return the relevant ApplicationInstance
     */
    public static final ApplicationInstance getActive() {
        return (ApplicationInstance) activeInstance.get();
    }

    /**
     * Sets the ApplicationInstance that is relevant to the 
     * current thread.  This method should be invoked with a null
     * argument when the previously set ApplicationInstance is 
     * no longer relevant.
     * 

* This method should only be invoked by the application container. * * @param applicationInstance the relevant ApplicationInstance */ public static final void setActive(ApplicationInstance applicationInstance) { activeInstance.set(applicationInstance); } /** * The presently focused component. */ private transient WeakReference focusedComponent; /** * The default Locale of the application. * This Locale will be inherited by Components. */ private Locale locale; /** * The default LayoutDirection of the application, derived * from the application's Locale. * This LayoutDirection will be inherited by * Components. */ private LayoutDirection layoutDirection; /** * Contextual data. * @see #getContextProperty(java.lang.String) */ private Map context; /** * Mapping from the render ids of all registered components to the * Component instances themselves. */ private Map renderIdToComponentMap; /** * Mapping between TaskQueueHandles and Lists * of Runnable tasks. Values may be null if a particular * TaskQueue does not contain any tasks. */ private HashMap taskQueueMap; /** * Fires property change events for the instance object. */ private PropertyChangeSupport propertyChangeSupport; /** * The UpdateManager handling updates to/from this application. */ private UpdateManager updateManager; /** * The top-level Window. * Currently only one top-level is supported per * ApplicationInstance. */ private Window defaultWindow; /** * The StyleSheet used by the application. */ private StyleSheet styleSheet; /** * Collection of modal components, the last index representing the current * modal context. */ private List modalComponents; /** * The next available sequentially generated * ApplicationInstance-unique identifier value. * @see #generateId() */ private long nextId; /** * Creates an ApplicationInstance. */ public ApplicationInstance() { super(); locale = Locale.getDefault(); layoutDirection = LayoutDirection.forLocale(locale); propertyChangeSupport = new PropertyChangeSupport(this); updateManager = new UpdateManager(this); renderIdToComponentMap = new HashMap(); taskQueueMap = new HashMap(); } /** * Invoked after the application has been passivated (such that its state may * be persisted or moved amongst VMs) and is about to be reactivated. * Implementations must invoke super.activate(). */ public void activate() { } /** * Adds a PropertyChangeListener to receive notification of * application-level property changes. * * @param l the listener to add */ public void addPropertyChangeListener(PropertyChangeListener l) { propertyChangeSupport.addPropertyChangeListener(l); } /** * Creates a new task queue. A handle object representing the created task * queue is returned. The created task queue will remain active until it is * provided to the removeTaskQueue() method. Developers must * take care to invoke removeTaskQueue() on any created * task queues. * * @return a TaskQueueHandler representing the created task * queue * @see #removeTaskQueue(TaskQueueHandle) */ public TaskQueueHandle createTaskQueue() { TaskQueueHandle taskQueue = new TaskQueueHandle() { /** Serial Version UID. */ private static final long serialVersionUID = 20070101L; }; synchronized (taskQueueMap) { taskQueueMap.put(taskQueue, null); } return taskQueue; } /** * Invoked when the application is disposed and will not be used again. * Implementations must invoke super.dispose(). */ public void dispose() { if (defaultWindow != null) { defaultWindow.doDispose(); defaultWindow.register(null); } synchronized (taskQueueMap) { taskQueueMap.clear(); } } /** * Initializes the ApplicationInstance. This method is * invoked by the application container. * * @return the default Window of the application * @throws IllegalStateException in the event that the current thread is not * permitted to update the state of the user interface */ public final Window doInit() { if (this != activeInstance.get()) { throw new IllegalStateException( "Attempt to update state of application user interface outside of user interface thread."); } Window window = init(); setDefaultWindow(window); doValidation(); return window; } /** * Validates all components registered with the application. */ public final void doValidation() { doValidation(defaultWindow); } /** * Validates a single component and then recursively validates its * children. This is the recursive support method for * the parameterless doValidation() method. * * @param c The component to be validated. * @see #doValidation() */ private void doValidation(Component c) { c.validate(); int size = c.getComponentCount(); for (int index = 0; index < size; ++index) { doValidation(c.getComponent(index)); } } /** * Queues the given stateless Command for execution on the * current client/server synchronization. * * @param command the Command to execute */ public void enqueueCommand(Command command) { updateManager.getServerUpdateManager().enqueueCommand(command); } /** * Enqueues a task to be run during the next client/server * synchronization. The task will be run * synchronously in the user interface update thread. * Enqueuing a task in response to an external event will result * in changes being pushed to the client. * * @param taskQueue the TaskQueueHandle representing the * queue into which this task should be placed * @param task the task to run on client/server synchronization */ public void enqueueTask(TaskQueueHandle taskQueue, Runnable task) { synchronized (taskQueueMap) { List taskList = (List) taskQueueMap.get(taskQueue); if (taskList == null) { taskList = new ArrayList(); taskQueueMap.put(taskQueue, taskList); } taskList.add(task); } } /** * Reports a bound property change. * * @param propertyName the name of the changed property * @param oldValue the previous value of the property * @param newValue the present value of the property */ protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) { propertyChangeSupport.firePropertyChange(propertyName, oldValue, newValue); } /** * Generates an identifier which is unique within this * ApplicationInstance. This identifier should not be * used outside of the context of this ApplicationInstance. * * @return the unique identifier * @see #generateSystemId() */ public String generateId() { return Long.toString(nextId++); } /** * Returns the value of a contextual property. * Contextual properties are typically set by an application * container, e.g., the Web Container, in order to provide * container-specific information. The property names of contextual * properties are provided within the application container * documentation when their use is required. * * @param propertyName the name of the object * @return the object */ public Object getContextProperty(String propertyName) { return context == null ? null : context.get(propertyName); } /** * Retrieves the component currently registered with the application * with the specified render id. * * @param renderId the render id of the component * @return the component (or null if no component with the specified * render id is registered) */ public Component getComponentByRenderId(String renderId) { return (Component) renderIdToComponentMap.get(renderId); } /** * Returns the default window of the application. * * @return the default Window */ public Window getDefaultWindow() { return defaultWindow; } /** * Returns the presently focused component, if known. * * @return the focused component */ public Component getFocusedComponent() { if (focusedComponent == null) { return null; } else { return (Component) focusedComponent.get(); } } /** * Returns the application instance's default * LayoutDirection. * * @return the Locale */ public LayoutDirection getLayoutDirection() { return layoutDirection; } /** * Returns the application instance's default Locale. * * @return the Locale */ public Locale getLocale() { return locale; } /** * Retrieves the root component of the current modal context, or null * if no modal context exists. Components which are not within the * descendant hierarchy of the modal context are barred from receiving * user input. * * @return the root component of the modal context */ public Component getModalContextRoot() { if (modalComponents == null || modalComponents.size() == 0) { // No components marked as modal. return null; } else if (modalComponents.size() == 1) { // One component marked as modal, return it if visible, null otherwise. Component component = (Component) modalComponents.get(0); return component.isRenderVisible() ? component : null; } // Multiple modal components. Set visibleModalComponents = new HashSet(); for (int i = modalComponents.size() - 1; i >= 0; --i) { Component component = (Component) modalComponents.get(i); if (component.isRenderVisible()) { visibleModalComponents.add(component); } } return findCurrentModalComponent(getDefaultWindow(), visibleModalComponents); } /** * Retrieves the style for the specified specified class of * component / style name. * * @param componentClass the component Class * @param styleName the component's specified style name * @return the appropriate application-wide style, or null * if none exists */ public Style getStyle(Class componentClass, String styleName) { if (styleSheet == null) { return null; } else { return styleSheet.getStyle(styleName, componentClass, true); } } /** * Returns the application-wide StyleSheet, if present. * * @return the StyleSheet */ public StyleSheet getStyleSheet() { return styleSheet; } /** * Retrieves the UpdateManager being used to manage the * client/server synchronization of this ApplicationInstance * * @return the UpdateManager */ public UpdateManager getUpdateManager() { return updateManager; } /** * Determines if this ApplicationInstance currently has any * active tasks queues, which might be monitoring external events. * * @return true if the instance has any task queues */ public final boolean hasTaskQueues() { return taskQueueMap.size() > 0; } /** * Determines if there are any queued tasks in any of the task * queues associated with this ApplicationInstance. *

* This method may be overridden by an application in order to check * on the status of long-running operations and enqueue tasks * just-in-time. In such cases tasks should be enqueued * and the value of super.hasQueuedTasks() should be * returned. This method is not invoked by a user-interface thread and * thus the component hierarchy may not be modified in * overriding implementations. * * @return true if any tasks are queued */ public boolean hasQueuedTasks() { if (taskQueueMap.size() == 0) { return false; } synchronized (taskQueueMap) { Iterator it = taskQueueMap.values().iterator(); while (it.hasNext()) { List taskList = (List) it.next(); if (taskList != null && taskList.size() > 0) { return true; } } } return false; } /** * Determines if the given component is modal (i.e., that only components * below it in the hierarchy should be enabled). * * @param component the Component * @return true if the Component is modal */ private boolean isModal(Component component) { return modalComponents != null && modalComponents.contains(component); } /** * Invoked to initialize the application, returning the default window. * The returned window must be visible. * * @return the default window of the application */ public abstract Window init(); /** * Notifies the UpdateManager in response to a component * property change or child addition/removal. *

* This method is invoked directly from Components * (rather than using a PropertyChangeListener) in the interest * of memory efficiency. * * @param parent the parent/updated component * @param propertyName the name of the property changed * @param oldValue the previous value of the property * (or the removed component in the case of a * CHILDREN_CHANGED_PROPERTY) * @param newValue the new value of the property * (or the added component in the case of a * CHILDREN_CHANGED_PROPERTY) * @throws IllegalStateException in the event that the current thread is not * permitted to update the state of the user interface */ void notifyComponentPropertyChange(Component parent, String propertyName, Object oldValue, Object newValue) { // Ensure current thread is a user interface thread. if (this != activeInstance.get()) { throw new IllegalStateException( "Attempt to update state of application user interface outside of user interface thread."); } ServerUpdateManager serverUpdateManager = updateManager.getServerUpdateManager(); if (Component.CHILDREN_CHANGED_PROPERTY.equals(propertyName)) { if (newValue == null) { serverUpdateManager.processComponentRemove(parent, (Component) oldValue); } else { serverUpdateManager.processComponentAdd(parent, (Component) newValue); } } else if (Component.PROPERTY_LAYOUT_DATA.equals(propertyName)) { serverUpdateManager.processComponentLayoutDataUpdate(parent); } else if (Component.VISIBLE_CHANGED_PROPERTY.equals(propertyName)) { if (oldValue != null && newValue != null && oldValue.equals(newValue)) { return; } serverUpdateManager.processComponentVisibilityUpdate(parent); } else { if (oldValue != null && newValue != null && oldValue.equals(newValue)) { return; } if (parent instanceof ModalSupport && ModalSupport.MODAL_CHANGED_PROPERTY.equals(propertyName)) { setModal(parent, ((Boolean) newValue).booleanValue()); } serverUpdateManager.processComponentPropertyUpdate(parent, propertyName, oldValue, newValue); } } /** * Invoked before the application is passivated (such that its state may * be persisted or moved amongst VMs). * Implementations must invoke super.passivate(). */ public void passivate() { } /** * Processes client input specific to the ApplicationInstance * received from the UpdateManager. * Derivative implementations should take care to invoke * super.processInput(). */ public void processInput(String propertyName, Object propertyValue) { if (FOCUSED_COMPONENT_CHANGED_PROPERTY.equals(propertyName)) { setFocusedComponent((Component) propertyValue); } } /** * Processes all queued tasks. This method may only be invoked from within a * UI thread by the UpdateManager. Tasks are removed from queues * once they have been processed. */ public void processQueuedTasks() { if (taskQueueMap.size() == 0) { return; } List currentTasks = new ArrayList(); synchronized (taskQueueMap) { Iterator taskListsIt = taskQueueMap.values().iterator(); while (taskListsIt.hasNext()) { List tasks = (List) taskListsIt.next(); if (tasks != null) { currentTasks.addAll(tasks); tasks.clear(); } } } Iterator it = currentTasks.iterator(); while (it.hasNext()) { ((Runnable) it.next()).run(); } } /** * Registers a component with the ApplicationInstance. * The component will be assigned a unique render id in the event that * it does not currently have one. *

* This method is invoked by Component.setApplicationInstance() * * @param component the component to register * @see Component#register(ApplicationInstance) */ void registerComponent(Component component) { String renderId = component.getRenderId(); if (renderId == null || renderIdToComponentMap.containsKey(renderId)) { // Note that the render id is reassigned if it currently exists renderIdToComponentMap. This could be the case // in the event a Component was being used in a pool. component.assignRenderId(generateId()); } renderIdToComponentMap.put(component.getRenderId(), component); if (component instanceof ModalSupport && ((ModalSupport) component).isModal()) { setModal(component, true); } } /** * Removes a PropertyChangeListener from receiving * notification of application-level property changes. * * @param l the listener to remove */ public void removePropertyChangeListener(PropertyChangeListener l) { propertyChangeSupport.removePropertyChangeListener(l); } /** * Removes the task queue described the specified * TaskQueueHandle. * * @param taskQueueHandle the TaskQueueHandle specifying the * task queue to remove * @see #createTaskQueue() */ public void removeTaskQueue(TaskQueueHandle taskQueueHandle) { synchronized(taskQueueMap) { taskQueueMap.remove(taskQueueHandle); } } /** * Sets a contextual property. * * @param propertyName the property name * @param propertyValue the property value * * @see #getContextProperty(java.lang.String) */ public void setContextProperty(String propertyName, Object propertyValue) { if (context == null) { context = new HashMap(); } if (propertyValue == null) { context.remove(propertyName); } else { context.put(propertyName, propertyValue); } } /** * Sets the default top-level window. * * @param window the default top-level window */ private void setDefaultWindow(Window window) { if (defaultWindow != null) { throw new UnsupportedOperationException("Default window already set."); } defaultWindow = window; window.register(this); firePropertyChange(WINDOWS_CHANGED_PROPERTY, null, window); window.doInit(); } /** * Sets the presently focused component. * * @param newValue the component to be focused */ public void setFocusedComponent(Component newValue) { if (newValue instanceof DelegateFocusSupport) { newValue = ((DelegateFocusSupport) newValue).getFocusComponent(); } Component oldValue = getFocusedComponent(); if (newValue == null) { focusedComponent = null; } else { focusedComponent = new WeakReference(newValue); } propertyChangeSupport.firePropertyChange(FOCUSED_COMPONENT_CHANGED_PROPERTY, oldValue, newValue); updateManager.getServerUpdateManager().processApplicationPropertyUpdate(FOCUSED_COMPONENT_CHANGED_PROPERTY, oldValue, newValue); } /** * Sets the default locale of the application. * * @param newValue the new locale */ public void setLocale(Locale newValue) { if (newValue == null) { throw new IllegalArgumentException("ApplicationInstance Locale may not be null."); } Locale oldValue = locale; locale = newValue; layoutDirection = LayoutDirection.forLocale(locale); propertyChangeSupport.firePropertyChange(LOCALE_CHANGED_PROPERTY, oldValue, newValue); // Perform full refresh: container's synchronization peers may need to provide new localization resources to client. updateManager.getServerUpdateManager().processFullRefresh(); } /** * Sets the modal state of a component (i.e, whether only it and * components below it in the hierarchy should be enabled). * * @param component the Component * @param newValue the new modal state */ private void setModal(Component component, boolean newValue) { boolean oldValue = isModal(component); if (newValue) { if (modalComponents == null) { modalComponents = new ArrayList(); } if (!modalComponents.contains(component)) { modalComponents.add(component); } } else { if (modalComponents != null) { modalComponents.remove(component); } } firePropertyChange(MODAL_COMPONENTS_CHANGED_PROPERTY, new Boolean(oldValue), new Boolean(newValue)); } /** * Sets the StyleSheet of this * ApplicationInstance. Components * registered with this instance will retrieve * properties from the StyleSheet * when property values are not specified directly * in a Component or in its specified Style. *

* Note that setting the style sheet should be * done sparingly, given that doing so forces the entire * client state to be updated. Generally style sheets should * only be reconfigured at application initialization and/or when * the user changes the visual theme of a theme-capable application. * * @param newValue the new style sheet */ public void setStyleSheet(StyleSheet newValue) { StyleSheet oldValue = styleSheet; this.styleSheet = newValue; firePropertyChange(STYLE_SHEET_CHANGED_PROPERTY, oldValue, newValue); } /** * Unregisters a component from the ApplicationInstance. *

* This method is invoked by Component.setApplicationInstance(). * * @param component the component to unregister * @see Component#register(ApplicationInstance) */ void unregisterComponent(Component component) { renderIdToComponentMap.remove(component.getRenderId()); if (component instanceof ModalSupport && ((ModalSupport) component).isModal()) { setModal(component, false); } } /** * Verifies that a Component is within the modal context, * i.e., that if a modal Component is present, that it either * is or is a child of that Component. * * @param component the Component to evaluate * @return true if the Component is within the current * modal context * @see Component#verifyInput(java.lang.String, java.lang.Object) */ boolean verifyModalContext(Component component) { Component modalContextRoot = getModalContextRoot(); return modalContextRoot == null || modalContextRoot.isAncestorOf(component); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy