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

nextapp.echo.app.update.ServerUpdateManager 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.update;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import nextapp.echo.app.ApplicationInstance;
import nextapp.echo.app.Command;
import nextapp.echo.app.Component;

/**
 * Monitors updates to component hierarchy and records deltas between 
 * server state of application and client state of application.
 */
public class ServerUpdateManager
implements Serializable {

    /** Serial Version UID. */
    private static final long serialVersionUID = 20070101L;

    /**
     * Comparator to sort components by their depth in the 
     * hierarchy.
     */
    private static final Comparator hierarchyDepthUpdateComparator = new Comparator() {

        /**
         * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
         */
        public int compare(Object a, Object b) {
            return getDepth(((ServerComponentUpdate) a).getParent()) - getDepth(((ServerComponentUpdate) b).getParent());
        }
        
        /**
         * @see java.lang.Object#equals(java.lang.Object)
         */
        public boolean equals(Object o) {
            return false;
        }
        
        /**
         * Returns the depth of the specified component in the hierarchy.
         * 
         * @param component the component
         * @return the depth
         */
        private int getDepth(Component component) {
            int count = 0;
            while (component != null) {
                component = component.getParent();
                ++count;
            }
            return count;
        }
    };
    
    /** Empty array of commands. */
    private static final Command[] EMPTY_COMMAND_ARRAY = new Command[0];
    
    /** Map between application property names and PropertyUpdates to the application. */
    private Map applicationUpdateMap;
    
    /** Enqueued commands to be executed. */
    private List commands;
    
    /**
     * Map between Components and ServerComponentUpdates.  The key is the parent component involved in 
     * the update.
     */
    private Map componentUpdateMap;
    
    /** Special ServerComponentUpdate used to describe a full-refresh of the application state being required. */
    private ServerComponentUpdate fullRefreshUpdate;
    
    /** The ClientUpdateManager that will be used to process input from the client. */
    private ClientUpdateManager clientUpdateManager;
    
    /** The updating ApplicationInstance. */
    private ApplicationInstance applicationInstance;
    
    /** Cache of ServerComponentUpdates (returned by multiple invocations of getComponentUpdates().) */
    private ServerComponentUpdate[] cachedComponentUpdates;
    
    /**
     * Creates a new ServerUpdateManager.
     * 
     * Warning: the init() method must be 
     * invoked before the ServerUpdateManager is used.
     * 
     * @param applicationInstance the relevant ApplicationInstance
     * @see #init(nextapp.echo.app.update.ClientUpdateManager)
     */
    public ServerUpdateManager(ApplicationInstance applicationInstance) {
        super();
        this.applicationInstance = applicationInstance;
        applicationUpdateMap = new HashMap();
        componentUpdateMap = new HashMap();
        fullRefreshUpdate = new ServerComponentUpdate(null);
    }
    
    /**
     * Creates or retrieves a ComponentUpdate for the given
     * parent component.  If a ComponentUpdate is created, it
     * is automatically stored in the update queue.
     * 
     * @param parent the parent component of the update, i.e., the component
     *        which has had children added, removed, or its properties updated
     * @return the created or retrieved update
     */
    private ServerComponentUpdate createComponentUpdate(Component parent) {
        
        ServerComponentUpdate update;
        if (componentUpdateMap.containsKey(parent)) {
            update = (ServerComponentUpdate) componentUpdateMap.get(parent);
        } else {
            update = new ServerComponentUpdate(parent);
            componentUpdateMap.put(parent, update);
        }
        return update;
    }
    
    /**
     * Enqueues a Command for processing.
     * 
     * @param command the command
     */
    public void enqueueCommand(Command command) {
        if (commands == null) {
            commands = new ArrayList();
        }
        commands.add(command);
    }
    
    /**
     * Returns a PropertyUpdate representing the 
     * application-level property update with the specified name.
     * If the specified property has not been updated, null is returned.
     * 
     * @param propertyName the name of the property
     * @return the PropertyUpdate
     */
    public PropertyUpdate getApplicationPropertyUpdate(String propertyName) {
        return (PropertyUpdate) applicationUpdateMap.get(propertyName);
    }
    
    /**
     * Returns the stored Commands.  The commands
     * are NOT removed or modified by this call.  
     * 
     * @return the commands
     */
    public Command[] getCommands() {
        if (commands == null) {
            return EMPTY_COMMAND_ARRAY;
        } else {
            return (Command[])commands.toArray(new Command[commands.size()]);
        }
    }
    
    /**
     * Returns the stored ServerComponentUpdates.  The updates
     * are NOT removed or modified by this call.  The updates will be returned
     * sorted by depth of their parent components within the hierarchy, but in 
     * otherwise random order.
     * 
     * @return the updates
     */
    public ServerComponentUpdate[] getComponentUpdates() {
        if (isFullRefreshRequired()) {
            return new ServerComponentUpdate[]{fullRefreshUpdate};
        } else {
            if (cachedComponentUpdates == null) {
                Collection hierarchyUpdates = componentUpdateMap.values();
                cachedComponentUpdates = (ServerComponentUpdate[])
                         hierarchyUpdates.toArray(new ServerComponentUpdate[hierarchyUpdates.size()]);
                Arrays.sort(cachedComponentUpdates, hierarchyDepthUpdateComparator);
                return cachedComponentUpdates;
            } else {
                return cachedComponentUpdates;
            }
        }
    }
    
    /**
     * Initialization life-cycle method.  Must be invoked before using 
     * the ServerUpdateManager.
     * 
     * @param clientUpdateManager the ClientUpdateManager that
     *        will be used to process input from the client
     */
    public void init(ClientUpdateManager clientUpdateManager) {
        this.clientUpdateManager = clientUpdateManager;
    }
    
    /**
     * Determines if an ancestor of the given component is being added.
     * 
     * @param component the Component to investigate
     * @return true if an ancestor of the component is being added
     */
    private boolean isAncestorBeingAdded(Component component) {
        Component child = component;
        Component parent = component.getParent();
        while (parent != null) {
            ServerComponentUpdate update = (ServerComponentUpdate) componentUpdateMap.get(parent);
            if (update != null) {
                if (update.hasAddedChild(child)) {
                    return true;
                }
            }
            child = parent;
            parent = parent.getParent();
        }
        return false;
    }
    
    /**
     * Determines if the manager has no updates.
     * 
     * @return true if the manager has no updates
     */
    public boolean isEmpty() {
        return componentUpdateMap.size() == 0;
    }
    
    /**
     * Determines if a full refresh of the client state is required.
     * 
     * @return true if a full refresh is required
     */
    public boolean isFullRefreshRequired() {
        return fullRefreshUpdate != null;
    }
    
    /**
     * Processes an update to a property of the ApplicationInstance.
     * 
     * @param propertyName the name of the property
     * @param oldValue the previous value of the property
     * @param newValue the current value of the property
     */
    public void processApplicationPropertyUpdate(String propertyName, Object oldValue, Object newValue) {
        Object clientValue = clientUpdateManager.getApplicationUpdatePropertyValue(propertyName);
        if (clientValue == newValue || (clientValue != null && clientValue.equals(newValue))) {
            // New value is same as client value, thus client is already in sync: cancel the update.
            applicationUpdateMap.remove(propertyName);
        } else {
            applicationUpdateMap.put(propertyName, new PropertyUpdate(oldValue, newValue)); 
        }
    }
    
    /**
     * Processes the addition of a component to the hierarchy.
     * Creates/updates a ServerComponentUpdate if required.
     * 
     * @param parent a component which currently exists in the hierarchy
     * @param child the component which was added to parent
     */
    public void processComponentAdd(Component parent, Component child) {
        if (isFullRefreshRequired()) {
            return;
        }
        if (!child.isRenderVisible()) {
            return;
        }
        if (isAncestorBeingAdded(child)) {
            return;
        }
        
        cachedComponentUpdates = null;

        ServerComponentUpdate update = createComponentUpdate(parent);
        update.addChild(child);
    }
    
    /**
     * Processes an update to the LayoutData of a component.
     * Creates/updates a ServerComponentUpdate if required.
     * 
     * @param updatedComponent a component which currently exists in the 
     *        hierarchy whose LayoutData has changed
     */
    public void processComponentLayoutDataUpdate(Component updatedComponent) {
        if (isFullRefreshRequired()) {
            return;
        }
        if (!updatedComponent.isRenderVisible()) {
            return;
        }

        cachedComponentUpdates = null;
        
        Component parentComponent = updatedComponent.getParent();
        if (parentComponent == null || isAncestorBeingAdded(parentComponent)) {
            // Do nothing.
            return;
        }
        ServerComponentUpdate update = createComponentUpdate(parentComponent);
        update.updateLayoutData(updatedComponent);
    }
    
    /**
     * Processes an update to a property of a component (other than the
     * LayoutData property).
     * Creates/updates a ServerComponentUpdate if required.
     * 
     * @param updatedComponent the component whose property(s) changed.
     * @param propertyName the name of the changed property
     * @param oldValue The previous value of the property
     * @param newValue The new value of the property
     */
    public void processComponentPropertyUpdate(Component updatedComponent, String propertyName, Object oldValue, Object newValue) {
        if (isFullRefreshRequired()) {
            return;
        }
        if (!updatedComponent.isRenderVisible()) {
            return;
        }
        if (isAncestorBeingAdded(updatedComponent)) {
            return;
        }

        cachedComponentUpdates = null;
        
        // Do not add update (and if necessary cancel any update) if the property is being updated
        // as the result of input from the client (and thus client and server state of property are
        // already synchronized).
        ClientComponentUpdate clientComponentUpdate = clientUpdateManager.getComponentUpdate(updatedComponent);
        if (clientComponentUpdate != null) {
            if (clientComponentUpdate.hasInput(propertyName)) {
                Object inputValue = clientComponentUpdate.getInputValue(propertyName);
                if (inputValue == newValue || (inputValue != null && inputValue.equals(newValue))) {
                    ServerComponentUpdate update = (ServerComponentUpdate) componentUpdateMap.get(updatedComponent);
                    if (update != null) {
                        update.cancelUpdateProperty(propertyName);
                    }
                    return;
                }
            }
        }
        
        ServerComponentUpdate update = createComponentUpdate(updatedComponent);
        update.updateProperty(propertyName, oldValue, newValue);
    }

    /**
     * Processes the removal of a component from the hierarchy.
     * Creates/updates a ServerComponentUpdate if required.
     * 
     * @param parent a component which currently exists in the hierarchy
     * @param child the component which was removed from parent
     */
    public void processComponentRemove(Component parent, Component child) {
        if (isFullRefreshRequired()) {
            return;
        }
        if (!parent.isRenderVisible()) {
            return;
        }
        if (isAncestorBeingAdded(parent)) {
            return;
        }
        
        cachedComponentUpdates = null;
        
        ServerComponentUpdate update = createComponentUpdate(parent);
        update.removeChild(child);
        
        // Search updated components for descendants of removed component.
        // Any found descendants will be removed and added to this update's 
        // list of removed descendants.
        Iterator it = componentUpdateMap.keySet().iterator();
        while (it.hasNext()) {
            Component testComponent = (Component) it.next();
            if (child.isAncestorOf(testComponent)) {
                ServerComponentUpdate childUpdate = (ServerComponentUpdate) componentUpdateMap.get(testComponent);
                update.appendRemovedDescendants(childUpdate);
                it.remove();
            }
        }
    }
    
    /**
     * Processes an update to the visible state of a component.
     * Creates/updates a ServerComponentUpdate if required.
     * 
     * @param updatedComponent a component which currently exists in the 
     *        hierarchy whose visible state has changed.
     */
    public void processComponentVisibilityUpdate(Component updatedComponent) {
        cachedComponentUpdates = null;

        Component parentComponent = updatedComponent.getParent();
        if (updatedComponent.isVisible()) {
            processComponentAdd(parentComponent, updatedComponent);
        } else {
            processComponentRemove(parentComponent, updatedComponent);
        }
    }
    
    /**
     * Processes a full refresh of the application state, in response to a 
     * severe change, such as application locale or style sheet.
     */
    public void processFullRefresh() {
        if (fullRefreshUpdate != null) {
            return;
        }
        
        cachedComponentUpdates = null;

        fullRefreshUpdate = new ServerComponentUpdate(null);

        if (applicationInstance.getDefaultWindow() != null) {
            // Default window may be null if an operation is invoked from within the
            // ApplicationInstsance.init() implementation that causes a full refresh.
            fullRefreshUpdate.removeDescendant(applicationInstance.getDefaultWindow());
        }

        Iterator it = componentUpdateMap.keySet().iterator();
        while (it.hasNext()) {
            Component testComponent = (Component) it.next();
            ServerComponentUpdate childUpdate = (ServerComponentUpdate) componentUpdateMap.get(testComponent);
            fullRefreshUpdate.appendRemovedDescendants(childUpdate);
            it.remove();
        }
    }
    
    /**
     * Removes all ServerComponentUpdates from the manager,
     * resetting its state to zero.  This method is invoked by the
     * container once it has retrieved and processed all available updates.
     */
    void purge() {
        applicationUpdateMap.clear();
        componentUpdateMap.clear();
        commands = null;
        fullRefreshUpdate = null;
        cachedComponentUpdates = null;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy