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 TaskQueueHandle
s 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 RenderState
s 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
* TaskQueue
s 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());
}
}