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

nextapp.echo.webcontainer.UserInstance Maven / Gradle / Ivy

The 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.webcontainer;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.Serializable;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;

import javax.servlet.http.HttpSession;

import nextapp.echo.app.ApplicationInstance;
import nextapp.echo.app.Component;
import nextapp.echo.app.TaskQueueHandle;
import nextapp.echo.app.update.ServerComponentUpdate;
import nextapp.echo.app.update.UpdateManager;
import nextapp.echo.webcontainer.service.AsyncMonitorService;
import nextapp.echo.webcontainer.util.IdTable;

/**
 * Object representing a single user-instance of an application hosted in the 
 * web application container.  This object is stored in the HttpSession.
 */
public class UserInstance implements Serializable {
    
    private abstract class SerializablePropertyChangeListener implements PropertyChangeListener, Serializable { }
    
    /** Serial Version UID. */
    private static final long serialVersionUID = 20070101L;

    /** Default asynchronous monitor callback interval (in milliseconds). */
    private static final int DEFAULT_CALLBACK_INTERVAL = 500;
    
    /** Client configuration data property name. */ 
    public static final String PROPERTY_CLIENT_CONFIGURATION = "clientConfiguration";

    /**
     * The container.
     */
    private UserInstanceContainer container;
    
    /**
     * The unique user instance identifier, generated by the UserInstanceContainer.
     */
    private String id;
    
    /**
     * The client-side generated unique browser window id displaying this UserInstance. 
     */
    private String clientWindowId;
    
    /**
     * The ApplicationInstance.
     */
    private ApplicationInstance applicationInstance;
    
    /**
     * The ApplicationWebSocket.
     */
    private ApplicationWebSocket applicationWebSocket;
    
    /**
     * ClientConfiguration information containing 
     * application-specific client behavior settings.
     */
    private ClientConfiguration clientConfiguration;
    
    /**
     * A ClientProperties object describing the web browser
     * client.
     */
    private ClientProperties clientProperties;
    
    /**
     * Mapping between component instances and RenderState objects.
     */
    private Map componentToRenderStateMap = new HashMap();
    
    /**
     * PropertyChangeListener for supported ApplicationInstance.
     */
    private PropertyChangeListener applicationPropertyChangeListener = new SerializablePropertyChangeListener() {
        /**
         * @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent)
         */
        @Override
        public void propertyChange(PropertyChangeEvent e) {
            String propertyName = e.getPropertyName();
            if (ApplicationInstance.LAST_ENQUEUE_TASK_PROPERTY.equals(propertyName)) {                
                if (applicationWebSocket != null && applicationWebSocket.isOpen()) {
                    UserInstance.this.applicationWebSocket.sendMessage(AsyncMonitorService.REQUEST_SYNC_ATTR);
                }
            } else if (ApplicationInstance.STYLE_SHEET_CHANGED_PROPERTY.equals(propertyName)) {
                updatedPropertyNames.add(ApplicationInstance.STYLE_SHEET_CHANGED_PROPERTY);
            }
        }
    };
    
    /**
     * IdTable used to assign weakly-referenced unique 
     * identifiers to arbitrary objects.
     */
    private transient IdTable idTable;

    /**
     * Flag indicating whether initialization has occurred. 
     */
    private boolean initialized = false;

    /**
     * Flag indicating whether the application has been initialized, i.e., whether ApplicationInstance.doInit()
     * has been invoked.
     */
    private boolean applicationInitialized = false; 
        
    /**
     * Map containing HTTP URL parameters provided on initial HTTP request to application.
     */
    private Map initialRequestParameterMap  = new HashMap();
    
    /**
     * Set of updated property names.
     */
    private Set updatedPropertyNames = new HashSet();

    /**
     * Map of TaskQueueHandles to callback intervals.
     */
    private transient Map taskQueueToCallbackIntervalMap;
    
    /**
     * The current transactionId.  Used to ensure incoming ClientMessages reflect
     * changes made by user against current server-side state of user interface.
     * This is used to eliminate issues that could be encountered with two
     * browser windows pointing at the same application instance.
     */
    private int transactionId = 0;
       
    /**
     * Creates a new UserInstance.
     * 
     * @param container the UserInstanceContainer
     * @param id the unique user instance identifier, generated by the UserInstanceContainer
     * @param clientWindowId the client-side generated unique browser window id displaying this UserInstance 
     * @param initialRequestParameterMap map containing parameters of the initial HTTP request
     */
    public UserInstance(UserInstanceContainer container, String id, String clientWindowId, Map initialRequestParameterMap) {
        super();
        this.container = container;
        this.id = id;
        this.clientWindowId = clientWindowId;
        this.initialRequestParameterMap = initialRequestParameterMap;
    }

    /**
     * Clears all RenderState information.
     */
    public void clearRenderStates() {
        componentToRenderStateMap.clear();
    }
    
    /**
     * Returns the corresponding ApplicationInstance
     * for this user instance.
     * 
     * @return the relevant ApplicationInstance
     */
    public ApplicationInstance getApplicationInstance() {
        return applicationInstance;
    }
    
    /**
     * Returns the corresponding ApplicationWebSocket
     * for this user instance.
     * 
     * @return the relevant ApplicationWebSocket
     */
    public ApplicationWebSocket getApplicationWebSocket() {
        return applicationWebSocket;
    }
    
    /**
     * Determines the application-specified asynchronous monitoring
     * service callback interval.
     * 
     * @return the callback interval, in milliseconds
     */
    public int getCallbackInterval() {
        if (taskQueueToCallbackIntervalMap == null || taskQueueToCallbackIntervalMap.size() == 0) {
            return DEFAULT_CALLBACK_INTERVAL;
        }
        Iterator it = taskQueueToCallbackIntervalMap.values().iterator();
        int returnInterval = Integer.MAX_VALUE;
        while (it.hasNext()) {
            int interval = ((Integer) it.next()).intValue();
            if (interval < returnInterval) {
                returnInterval = interval;
            }
        }
        return returnInterval;
    }

    /**
     * Returns the default character encoding in which responses should be
     * rendered.
     * 
     * @return the default character encoding in which responses should be
     *         rendered
     */
    public String getCharacterEncoding() {
        return container.getCharacterEncoding();
    }
    
    /** 
     * The ServerDelayMessage displayed during client/server-interactions.
     * Retrieves the ClientConfiguration information containing application-specific client behavior settings.
     * 
     * @return the relevant ClientProperties
     */
    public ClientConfiguration getClientConfiguration() {
        return clientConfiguration;
    }
    
    /**
     * Retrieves the ClientProperties object providing
     * information about the client of this instance.
     * 
     * @return the relevant ClientProperties
     */
    public ClientProperties getClientProperties() {
        return clientProperties;
    }
    
    /**
     * Returns the client-side render id that should be used when rendering the
     * specified Component.
     * 
     * @param component the component 
     * @return the client-side render id
     */
    public String getClientRenderId(Component component) {
        return getClientRenderId(component.getRenderId());
    }
    
    /**
     * @see UserInstance#getClientRenderId(nextapp.echo.app.Component)
     * 
     * @param componentRenderId component render id
     * @return the client-side render id
     */
    public String getClientRenderId(final String componentRenderId) {
        return "C."+ componentRenderId;
    }
    
    /**
     * Retrieves the Component with the specified client-side render id.
     * 
     * @param clientRenderId client-side element render id, e.g., "C.42323"
     * @return the component (e.g., the component whose id is "42323")
     */
    public Component getComponentByClientRenderId(String clientRenderId) {
        try {
            return applicationInstance.getComponentByRenderId(clientRenderId.substring(2));
        } catch (IndexOutOfBoundsException ex) {
            throw new IllegalArgumentException("Invalid component element id: " + clientRenderId);
        }
    }

    /**
     * Returns the current transaction id.
     * 
     * @return the current transaction id
     */
    public int getCurrentTransactionId() {
        return transactionId;
    }

    /**
     * Returns the UserInstance unique identifier.
     * 
     * @return the identifier value
     */
    public String getId() {
        return id;
    }
    
    /**
     * Returns the client-side generated unique browser window id displaying this UserInstance.
     * 
     * @return the client-side window id
     */
    public String getClientWindowId() {
        return clientWindowId; 
    }

    /**
     * Retrieves the IdTable used by this 
     * ContainerInstance to assign weakly-referenced unique 
     * identifiers to arbitrary objects.
     * 
     * @return the IdTable
     */
    public IdTable getIdTable() {
        if (idTable == null) {
            idTable = new IdTable();
        }
        return idTable;
    }
    
    /**
     * Returns an immutable Map containing the HTTP form 
     * parameters sent on the initial request to the application.
     * 
     * @return the initial request parameter map
     */
    public Map getInitialRequestParameterMap() {
        return initialRequestParameterMap;
    }
    
    /**
     * Increments the current transaction id and returns it.
     * 
     * @return the current transaction id, after an increment
     */
    public int getNextTransactionId() {
        ++transactionId;
        return transactionId;
    }

    /**
     * Retrieves the RenderState of the specified
     * Component.
     * 
     * @param component the component
     * @return the rendering state
     */
    public RenderState getRenderState(Component component) {
        return (RenderState) componentToRenderStateMap.get(component);
    }

    /**
     * Returns the id of the HTML element that will serve as the Root component.
     * This element must already be present in the DOM when the application is
     * first rendered.
     * 
     * @return the element id
     */
    public String getRootHtmlElementId() {
        return container.getRootHtmlElementId();
    }
    
    /**
     * Determines the URI to invoke the specified Service.
     * 
     * @param service the Service
     * @return the URI
     */
    public String getServiceUri(Service service) {
        return container.getServiceUri(service, id);
    }
    
    /**
     * Determines the URI to invoke the specified Service with
     * additional request parameters. The additional parameters are provided by
     * way of the parameterNames and parameterValues
     * arrays. The value of a parameter at a specific index in the
     * parameterNames array is provided in the
     * parameterValues array at the same index. The arrays must
     * thus be of equal length. Null values are allowed in the
     * parameterValues array, and in such cases only the parameter
     * name will be rendered in the returned URI.
     * 
     * @param service the Service
     * @param parameterNames the names of the additional URI parameters
     * @param parameterValues the values of the additional URI parameters
     * @return the URI
     */
    public String getServiceUri(Service service, String[] parameterNames, String[] parameterValues) {
        return container.getServiceUri(service, id, parameterNames, parameterValues);
    }

    /**
     * Returns the URI of the servlet managing this UserInstance.
     * 
     * @return the URI
     */
    public String getServletUri() {
        return container.getServletUri();
    }

    /**
     * Returns the HttpSession containing this
     * UserInstance.
     * 
     * @return the HttpSession
     */
    public HttpSession getSession() {
        return container.getSession();
    }
    
    /**
     * Returns an iterator over updated property names.
     * Invoked by OutputProcessor.
     */
    Iterator getUpdatedPropertyNames() {
        if (updatedPropertyNames.size() == 0) {
            return Collections.EMPTY_SET.iterator();
        } else {
            Set updatedPropertyNames = this.updatedPropertyNames;
            this.updatedPropertyNames = new HashSet();
            return updatedPropertyNames.iterator();
        }
    }
    
   /**
     * Convenience method to retrieve the application's 
     * UpdateManager, which is used to synchronize
     * client and server states.
     * This method is equivalent to invoking
     * getApplicationInstance().getUpdateManager().
     * 
     * @return the UpdateManager
     */
    public UpdateManager getUpdateManager() {
        return applicationInstance.getUpdateManager();
    }
    
    /**
     * Disposes of the UserInstance.
     */
    public void dispose() {
        if (applicationInstance != null) {
            try {
                ApplicationInstance.setActive(applicationInstance);
                applicationInstance.dispose();
                if (applicationWebSocket != null) {
                    applicationWebSocket.dispose();
                }
                applicationInstance.removePropertyChangeListener(applicationPropertyChangeListener);
            } finally {
                ApplicationInstance.setActive(null);
            }
        }
    }

    /**
     * Initializes the UserInstance, creating an instance
     * of the target ApplicationInstance.
     * The ApplicationInstance will not be initialized until
     * getApplicationInstance() is invoked for the first time.
     *
     * @param conn the relevant Connection
     */
    public void initHTTP(Connection conn) {
        if (initialized) {
            throw new IllegalStateException("Attempt to invoke UserInstance.init() on initialized instance.");
        }
        WebContainerServlet servlet = conn.getServlet();
        
        applicationInstance = servlet.newApplicationInstance();
        applicationInstance.addPropertyChangeListener(applicationPropertyChangeListener);
        
        ContainerContext containerContext = new ContainerContextImpl(this);
        applicationInstance.setContextProperty(ContainerContext.CONTEXT_PROPERTY_NAME, containerContext);
        
        initialized = true;
    }

    /**
     * Determines if the UserInstance has been initialized, 
     * i.e., whether its init() method has been invoked.
     * 
     * @return true if the UserInstance is initialized
     */
    public boolean isInitialized() {
        return initialized;
    }

    /**
     * Associates a WebSocketConnection to the UserInstance.
     *
     * @param conn the relevant WebSocketConnection
     */
    public void initWebSocket(WebSocketConnection conn) {
        this.applicationWebSocket = conn.getApplicationWebSocket();
    }
    
    /**
     * Prepares the ApplicationInstance for use, initializing the application if it has not been initialized 
     * previously.
     */
    void prepareApplicationInstance() {
        if (!applicationInitialized) {
            try {
                applicationInstance.doInit();
            } finally {
                applicationInitialized = true;
            }
        }
    }
    
    /**
     * Removes all RenderStates whose components are not
     * registered.
     */
    public void purgeRenderStates() {
        ServerComponentUpdate[] updates = getUpdateManager().getServerUpdateManager().getComponentUpdates();

        Iterator it = componentToRenderStateMap.keySet().iterator();
        while (it.hasNext()) {
            Component component = (Component) it.next();
            if (!component.isRegistered() || !component.isRenderVisible()) {
                it.remove();
                continue;
            }

            for (int i = 0; i < updates.length; ++i) {
                if (updates[i].hasRemovedDescendant(component)) {
                    it.remove();
                    continue;
                }
            }
        }
    }

    /**
     * Removes the RenderState of the specified
     * Component.
     * 
     * @param component the component
     */
    public void removeRenderState(Component component) {
        componentToRenderStateMap.remove(component);
    }

    /**
     * Sets the contained ApplicationInstance active or inactive.
     * 
     * @param active the new active state
     */
    void setActive(boolean active) {
        if (active) {
            ApplicationInstance.setActive(applicationInstance);
        } else {
            ApplicationInstance.setActive(null);
        }
    }
    
    /**
     * Sets the ClientConfiguration information containing
     * application-specific client behavior settings.
     * 
     * @param clientConfiguration the new ClientConfiguration
     */
    public void setClientConfiguration(ClientConfiguration clientConfiguration) {
        this.clientConfiguration = clientConfiguration;
        this.updatedPropertyNames.add(PROPERTY_CLIENT_CONFIGURATION);
    }

    /**
     * Stores the ClientProperties object that provides
     * information about the client of this instance.
     * 
     * @param clientProperties the relevant ClientProperties
     */
    void setClientProperties(ClientProperties clientProperties) {
        this.clientProperties = clientProperties;
    }

    /**
     * Sets the RenderState of the specified 
     * Component.
     * 
     * @param component the component
     * @param renderState the render state
     */
    public void setRenderState(Component component, RenderState renderState) {
        componentToRenderStateMap.put(component, renderState);
    }

    /**
     * Sets the interval between asynchronous callbacks from the client to check
     * for queued tasks for a given TaskQueue.  If multiple 
     * TaskQueues are active, the smallest specified interval should
     * be used.  The default interval is 500ms.
     * Application access to this method should be accessed via the 
     * ContainerContext.
     * 
     * @param taskQueue the TaskQueue
     * @param ms the number of milliseconds between asynchronous client 
     *        callbacks
     * @see nextapp.echo.webcontainer.ContainerContext#setTaskQueueCallbackInterval(nextapp.echo.app.TaskQueueHandle, int)
     */
    public void setTaskQueueCallbackInterval(TaskQueueHandle taskQueue, int ms) {
        if (taskQueueToCallbackIntervalMap == null) {
            taskQueueToCallbackIntervalMap = new WeakHashMap();
        }
        taskQueueToCallbackIntervalMap.put(taskQueue, new Integer(ms));
    }
    
    /**
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        return "UserInstance id=" + id + ", Application=" 
                + (applicationInstance == null ? null : applicationInstance.getClass().getName());
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy