http://git-wip-us.apache.org/repos/asf/flex-blazeds/blob/bf2e1dc9/core/src/flex/messaging/FlexSession.java ---------------------------------------------------------------------- diff --git a/core/src/flex/messaging/FlexSession.java b/core/src/flex/messaging/FlexSession.java new file mode 100644 index 0000000..4f34f8a --- /dev/null +++ b/core/src/flex/messaging/FlexSession.java @@ -0,0 +1,1064 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (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.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package flex.messaging; + +import flex.messaging.client.FlexClient; +import flex.messaging.client.FlexClientListener; +import flex.messaging.log.LogCategories; +import flex.messaging.messages.Message; +import flex.messaging.util.TimeoutAbstractObject; + +import java.security.Principal; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * The base for FlexSession implementations. + */ +public abstract class FlexSession extends TimeoutAbstractObject implements FlexClientListener, MessageClientListener +{ + //-------------------------------------------------------------------------- + // + // Public Static Variables + // + //-------------------------------------------------------------------------- + + /** + * Log category for FlexSession related messages. + */ + public static final String FLEX_SESSION_LOG_CATEGORY = LogCategories.ENDPOINT_FLEXSESSION; + + /** + * + */ + public static final int MAX_CONNECTIONS_PER_SESSION_UNLIMITED = -1; + + //-------------------------------------------------------------------------- + // + // Private Static Variables + // + //-------------------------------------------------------------------------- + + /** + * The set of session created listeners to notify upon a new session creation. + */ + private static final CopyOnWriteArrayList<FlexSessionListener> createdListeners = new CopyOnWriteArrayList<FlexSessionListener>(); + + /** + * Error string constants. + */ + private static final int FLEX_SESSION_INVALIDATED = 10019; + + //-------------------------------------------------------------------------- + // + // Constructor + // + //-------------------------------------------------------------------------- + + /** + * + * @deprecated Post 2.6.1 releases require use of the constructor that takes an <tt>AbstractFlexSessionProvider</tt> argument. + */ + public FlexSession() + { + this(null); + } + + /** + * + * Constructs a new FlexSession instance. + * + * @param sessionProvider The provider that instantiated this instance. + */ + public FlexSession(AbstractFlexSessionProvider sessionProvider) + { + this.sessionProvider = sessionProvider; + } + + //-------------------------------------------------------------------------- + // + // Static Methods + // + //-------------------------------------------------------------------------- + + /** + * Adds a session created listener that will be notified when new sessions + * are created. + * + * @see flex.messaging.FlexSessionListener + * + * @param listener The listener to add. + */ + public static void addSessionCreatedListener(FlexSessionListener listener) + { + if (listener != null) + createdListeners.addIfAbsent(listener); + } + + /** + * Removes a session created listener. + * + * @see flex.messaging.FlexSessionListener + * + * @param listener The listener to remove. + */ + public static void removeSessionCreatedListener(FlexSessionListener listener) + { + if (listener != null) + createdListeners.remove(listener); + } + + //-------------------------------------------------------------------------- + // + // Variables + // + //-------------------------------------------------------------------------- + + /** + * Flag used to break cycles during invalidation. + */ + protected boolean invalidating; + + /** + * Instance level lock to sync for state changes. + */ + protected final Object lock = new Object(); + + /** + * Flag indicated whether the session has been invalidated/destroyed. + */ + protected boolean valid = true; + + /** + * The attributes associated with this session. + */ + private Map<String, Object> attributes; + + /** + * Registered attribute listeners for the session. + */ + private volatile CopyOnWriteArrayList<FlexSessionAttributeListener> attributeListeners; + + /** + * Flag indicating whether creation notification has been completed. + */ + private boolean creationNotified; + + /** + * The set of session destroy listeners to notify when the session is destroyed. + */ + private volatile CopyOnWriteArrayList<FlexSessionListener> destroyedListeners; + + /** + * The associated FlexClients. + */ + private final CopyOnWriteArrayList<FlexClient> flexClients = new CopyOnWriteArrayList<FlexClient>(); + + /** + * List of associated MessageClients created while this session was active (thread local). + */ + private volatile CopyOnWriteArrayList<MessageClient> messageClients; + + /** + * Storage for remote credentials associated with the session; used by the HTTPProxyService + * when requests are made to a secured remote endpoint. + */ + private volatile Map remoteCredentials; + + //-------------------------------------------------------------------------- + // + // Properties + // + //-------------------------------------------------------------------------- + + //---------------------------------- + // asyncPollMap + //---------------------------------- + + /** + * + * Used internally to manage async long-polls; not for public use. + * + * A map of endpoint to async poll objects that keeps track of what + * client is parked on a long-poll with what endpoint. + * We only want an endpoint to have a single connection to a client. + * Normally, the server leaves an async long-poll in place until + * data arrives to push to the client or a timeout is reached. + * However, if two or more browser tabs/windows are sharing the same server session and are both attempting + * async long-polling, we alternate their parked requests to avoid locking up the browser process by holding too many + * Http connections open at once. + * This also aids with closing out 'orphaned' long polls following a browser page reload. + * Generically typed as <code>Object</code>; using this reference is left up to async poll + * implementation code. + */ + public volatile HashMap<String, FlexClient.AsyncPollWithTimeout> asyncPollMap; + + //---------------------------------- + // flexSessionProvider + //---------------------------------- + + private final AbstractFlexSessionProvider sessionProvider; + + /** + * + * Returns the session provider that created this instance. + * + * @return The session provider that created this instance. + */ + public AbstractFlexSessionProvider getFlexSessionProvider() + { + return sessionProvider; + } + + //---------------------------------- + // principal + //---------------------------------- + + /** + * The principal associated with the session. + */ + private Principal userPrincipal; + + /** + * This method should be called on FlexContext and not on this class. Keeping + * this method for backwards compatibility. This method will produce + * correct results when perClientAuthentication is false. However, it will + * not return correct results when perClientAuthentication is true. + * + * Returns the principal associated with the session. If the client has not + * authenticated the principal will be null. + * + * @return The principal associated with the session. + */ + public Principal getUserPrincipal() + { + synchronized (lock) + { + checkValid(); + return userPrincipal; + } + } + + /** + * This method should be called on FlexContext and not on this class. Keeping + * this method for backwards compatibility. Calling this when perClientAuthentication + * is true will not correctly set the UserPrincipal. + * + * @param userPrincipal The principal to associate with the session. + */ + public void setUserPrincipal(Principal userPrincipal) + { + synchronized (lock) + { + checkValid(); + this.userPrincipal = userPrincipal; + } + } + + //---------------------------------- + // canStream + //---------------------------------- + + /** + * + * Used internally by streaming endpoints to enforce session level streaming + * connection limits; not for public use. + * This flag is volatile to allow for consistent reads across thread without + * needing to pay the cost for a synchronized lock for each read. + */ + public volatile boolean canStream = true; + + //---------------------------------- + // maxConnectionsPerSession + //---------------------------------- + + /** + * + * Used internally by streaming and long polling endpoints to enforce session + * level streaming connection limits; not for public use. Default value is -1 + * (limitless) + */ + public int maxConnectionsPerSession = MAX_CONNECTIONS_PER_SESSION_UNLIMITED; + + //---------------------------------- + // streamingClientsCount + //---------------------------------- + + /** + * + * Used internally by streaming and long polling endpoints to enforce + * session level streaming connection limits; not for public use. + * + * Some browsers put limits on the number of connections per session. For + * example, Firefox has network.http.max-connections-per-server=8 limit which + * limits the number of streaming connections per session to 7. Similarly, + * IE has a limit of 2 per session. + * + * This variable is used by streaming and long polling endpoint to keep + * track of open connections per session and disallow them when needed. + * + */ + public int streamingConnectionsCount; + + //---------------------------------- + // useSmallMessages + //---------------------------------- + + /** + * + */ + private boolean useSmallMessages; + + /** + * + * Determines whether the server can attempt to send small messages + * for those messages that have a small form. This setting can be overridden + * by an endpoint's enableSmallMessages switch which controls whether + * small messages should be sent, even if they are supported. + * + * The default is false. + * + * @return true if the server can attempt to send small messages. + */ + public boolean useSmallMessages() + { + return useSmallMessages; + } + + /** + * @param value true if the server can attempt to send small messages. + */ + public void setUseSmallMessages(boolean value) + { + useSmallMessages = value; + } + + //---------------------------------- + // waitMonitor + //---------------------------------- + + /** + * + * Used internally to manage wait()-based long-polls; not for public use. + * + * This is the monitor that a request handling thread associated with this + * FlexSession is waiting on. Normally, the waiting request handling thread will wait until + * a new message arrives that can be returned in a poll response or its wait interval times out. + * This also aids with closing out 'orphaned' long polls following a browser page reload. + */ + public volatile HashMap<String, FlexClient.EndpointQueue> waitMonitor; + + //-------------------------------------------------------------------------- + // + // Methods + // + //-------------------------------------------------------------------------- + + /** + * Adds a session attribute listener that will be notified when an + * attribute is added, removed or changed. + * + * @param listener The listener to add. + */ + public void addSessionAttributeListener(FlexSessionAttributeListener listener) + { + if (listener != null) + { + checkValid(); + + if (attributeListeners == null) + { + synchronized (lock) + { + if (attributeListeners == null) + attributeListeners = new CopyOnWriteArrayList<FlexSessionAttributeListener>(); + } + } + + attributeListeners.addIfAbsent(listener); + } + } + + /** + * Adds a session destroy listener that will be notified when the session + * is destroyed. Session destroy listeners are notified after all attributes + * have been unbound from the session and any FlexSessionBindingListeners + * and FlexSessionAttributeListeners have been notified. + * + * @see flex.messaging.FlexSessionListener + * + * @param listener The listener to add. + */ + public void addSessionDestroyedListener(FlexSessionListener listener) + { + if (listener != null) + { + checkValid(); + + if (destroyedListeners == null) + { + synchronized (lock) + { + if (destroyedListeners == null) + destroyedListeners = new CopyOnWriteArrayList<FlexSessionListener>(); + } + } + + destroyedListeners.addIfAbsent(listener); + } + } + + /** + * Returns the attribute bound to the specified name in the session, or null + * if no attribute is bound under the name. + * + * @param name The name the target attribute is bound to. + * @return The attribute bound to the specified name. + */ + public Object getAttribute(String name) + { + synchronized (lock) + { + checkValid(); + + return (attributes == null) ? null : attributes.get(name); + } + } + + /** + * Returns a snapshot of the names of all attributes bound to the session. + * + * @return A snapshot of the names of all attributes bound to the session. + */ + public Enumeration<String> getAttributeNames() + { + synchronized (lock) + { + checkValid(); + + if (attributes == null) + return Collections.enumeration(Collections.<String>emptyList()); + + // Return a copy so we do not run into concurrent modification problems if + // someone adds to the attributes while iterating through the returned enumeration. + return Collections.enumeration(new ArrayList<String>(attributes.keySet())); + } + } + + /** + * + * Implements MessageClientListener. + * Handling created events is a no-op. + * + * @param messageClient The new MessageClient. + */ + public void messageClientCreated(MessageClient messageClient) {} + + /** + * + * Implements MessageClientListener. + * Notification that an associated MessageClient was destroyed. + * + * @param messageClient The MessageClient that was destroyed. + */ + public void messageClientDestroyed(MessageClient messageClient) + { + unregisterMessageClient(messageClient); + } + + /** + * + * FlexClient invokes this to determine whether the session can be used to push messages + * to the client. + * + * @return true if the FlexSession supports direct push; otherwise false (polling is assumed). + */ + public abstract boolean isPushSupported(); + + /** + * + * FlexClient invokes this to push a message to a remote client. + * + * @param message The message to push. + */ + public void push(Message message) + { + throw new UnsupportedOperationException("Push not supported."); + } + + /** + * Removes the attribute bound to the specified name in the session. + * + * @param name The name of the attribute to remove. + */ + public void removeAttribute(String name) + { + Object value; // Used for event dispatch after the attribute is removed. + + synchronized (lock) + { + checkValid(); // Re-enters lock but should be fast because we're already holding it. + + value = (attributes != null) ? attributes.remove(name) : null; + } + + // If no value was bound under this name it's a no-op. + if (value == null) + return; + + notifyAttributeUnbound(name, value); + notifyAttributeRemoved(name, value); + } + + /** + * Removes a session attribute listener. + * + * @param listener The listener to remove. + */ + public void removeSessionAttributeListener(FlexSessionAttributeListener listener) + { + // No need to check validity; removing a listener is always ok. + if (listener != null && attributeListeners != null) + attributeListeners.remove(listener); + } + + /** + * Removes a session destroy listener. + * + * @see flex.messaging.FlexSessionListener + * + * @param listener The listener to remove. + */ + public void removeSessionDestroyedListener(FlexSessionListener listener) + { + // No need to check validity; removing a listener is always ok. + if (listener != null && destroyedListeners != null) + destroyedListeners.remove(listener); + } + + /** + * Binds an attribute value to the session under the specified name. + * + * @param name The name to bind the attribute under. + * @param value The value of the attribute. + */ + public void setAttribute(String name, Object value) + { + // Null value is the same as removeAttribute(). + if (value == null) + { + removeAttribute(name); + return; + } + + Object oldValue; // Used to determine which events to dispatch after the set is performed. + + // Only synchronize for the attribute mutation; event dispatch doesn't require it. + synchronized (lock) + { + checkValid(); // Re-enters lock but should be fast because we're already holding it. + + if (attributes == null) + attributes = new HashMap<String, Object>(); + + oldValue = attributes.put(name, value); + } + + if (oldValue == null) + { + notifyAttributeBound(name, value); + notifyAttributeAdded(name, value); + } + else + { + notifyAttributeUnbound(name, oldValue); + notifyAttributeReplaced(name, oldValue); + notifyAttributeBound(name, value); + } + } + + /** + * Stores remote credentials in the session for proxied calls to remote systems. + * + * @param credentials The remote credentials. + */ + public void putRemoteCredentials(FlexRemoteCredentials credentials) + { + if (credentials != null) + { + // We only need to hold the lock to lazy-init the remoteCredentials variable. + if (remoteCredentials == null) + { + synchronized (lock) + { + // Init size to 4 because that's the number of shipping service types + // (messaging, remoting, proxy, data management). + if (remoteCredentials == null) + remoteCredentials = new HashMap(4); + } + } + synchronized (remoteCredentials) + { + Map serviceMap = (Map)remoteCredentials.get(credentials.getService()); + if (serviceMap == null) + { + // Init size to half the normal number of buckets; most services won't have a large + // number of destinations with remote credentials. + serviceMap = new HashMap(7); + remoteCredentials.put(credentials.getService(), serviceMap); + } + serviceMap.put(credentials.getDestination(), credentials); + } + } + } + + /** + * Returns the remote credentials stored in the session for the specified service destination. + * + * @param serviceId The service id. + * @param destinationId The destination id. + * @return The stored remote credentials for the specified service destination. + */ + public FlexRemoteCredentials getRemoteCredentials(String serviceId, String destinationId) + { + if (serviceId != null && destinationId != null) + { + if (remoteCredentials == null) + return null; + synchronized (remoteCredentials) + { + Map serviceMap = (Map)remoteCredentials.get(serviceId); + return (serviceMap != null) ? (FlexRemoteCredentials)serviceMap.get(destinationId) : null; + } + } + return null; + } + + /** + * Clears any stored remote credentials from the session for the specified service destination. + * + * @param serviceId The service Id. + * @param destinationId The destination Id. + */ + public void clearRemoteCredentials(String serviceId, String destinationId) + { + if (serviceId != null && destinationId != null) + { + if (remoteCredentials == null) + return; + synchronized (remoteCredentials) + { + Map serviceMap = (Map)remoteCredentials.get(serviceId); + if (serviceMap != null) + { + serviceMap.put(destinationId, null); + } + } + } + } + + /** + * Invalidates the FlexSession. + */ + public void invalidate() + { + synchronized (lock) + { + if (!valid || invalidating) + return; // Already shutting down. + + invalidating = true; // This thread gets to shut the FlexSession down. + cancelTimeout(); + if (sessionProvider != null) + sessionProvider.removeFlexSession(this); + } + + // Unregister all FlexClients. + if (!flexClients.isEmpty()) + { + for (FlexClient flexClient : flexClients) + unregisterFlexClient(flexClient); + } + + // Invalidate associated MessageClient subscriptions. + if (messageClients != null && !messageClients.isEmpty()) + { + for (Iterator<MessageClient> iter = messageClients.iterator(); iter.hasNext();) + { + MessageClient messageClient = iter.next(); + messageClient.removeMessageClientDestroyedListener(this); + messageClient.invalidate(); + } + messageClients.clear(); + } + + + // Notify destroy listeners that we're shutting the FlexSession down. + if (destroyedListeners != null && !destroyedListeners.isEmpty()) + { + for (FlexSessionListener destroyListener : destroyedListeners) + { + destroyListener.sessionDestroyed(this); + } + destroyedListeners.clear(); + } + + // Unbind all attributes. + if (attributes != null && !attributes.isEmpty()) + { + Set<String> keySet = attributes.keySet(); + String[] keys = keySet.toArray(new String[keySet.size()]); + for (String key : keys) + removeAttribute(key); + + attributes = null; + } + + internalInvalidate(); + + synchronized (lock) + { + valid = false; + invalidating = false; + } + + // Notify any waiting threads. + if (waitMonitor != null) + { + for (FlexClient.EndpointQueue endpointQueue : waitMonitor.values()) + { + synchronized (endpointQueue) + { + endpointQueue.notifyAll(); + } + } + } + } + + /** + * Hook for subclasses to perform any custom shutdown. + * Invoked after the FlexSession has performed generic shutdown but right before the session's valid + * property flips to false. + */ + protected void internalInvalidate() {} + + /** + * Returns a snapshot of the FlexClients associated with the FlexSession + * when this method is invoked. + * This list is not guaranteed to remain consistent with the actual list + * of active FlexClients associated with the FlexSession over time. + * + * @return A snapshot of the current list of FlexSessions associated with the FlexClient. + */ + public List<FlexClient> getFlexClients() + { + List<FlexClient> currentFlexClients = null; + synchronized (lock) + { + checkValid(); // Re-enters lock but should be fast because we're already holding it. + + currentFlexClients = new ArrayList<FlexClient>(flexClients); // Make a copy of the current list to return. + } + return currentFlexClients; + } + + /** + * Returns a snapshot of the MessageClients (subscriptions) associated with the FlexSession + * when this method is invoked. + * This list is not guaranteed to remain consistent with the actual list + * of active MessageClients associated with the FlexSession over time. + * + * @return A snapshot of the current list of MessageClients associated with the FlexSession. + */ + public List<MessageClient> getMessageClients() + { + List<MessageClient> currentMessageClients = null; + synchronized (lock) + { + checkValid(); // Re-enters lock but should be fast because we're already holding it. + + currentMessageClients = (messageClients != null) ? new ArrayList<MessageClient>(messageClients) // Make a copy of the current list to return. + : new ArrayList<MessageClient>(); // Return an empty list. + } + return currentMessageClients; + } + + /** + * Returns the Id for the session. + * + * @return The Id for the session. + */ + public abstract String getId(); + + /** + * Returns whether the current user is in the specified role. + * + * @param role The role to test. + * @return true if the user is in the role; otherwise false. + */ + public boolean isUserInRole(String role) + { + ArrayList list = new ArrayList(); + list.add(role); + return FlexContext.getMessageBroker().getLoginManager().checkRoles(userPrincipal, list); + } + + /** + * Returns whether the session is valid. + * + * @return true if the session is valid; otherwise false. + */ + public boolean isValid() + { + synchronized (lock) + { + return valid; + } + } + + /** + * + * Implements FlexClientListener interface. + * Notification that a FlexClient was created. + * This is a no-op because the FlexSession is never added as a static FlexClient created listener + * but this method is required by the interface. We only listen for the destroyed event from + * associated FlexClients. + * + * @param flexClient The FlexClient that was created. + */ + public void clientCreated(FlexClient flexClient) {} + + /** + * + * Implements FlexClientListener interface. + * Notification that an associated FlexClient was destroyed. + * + * @param flexClient The FlexClient that was destroyed. + */ + public void clientDestroyed(FlexClient flexClient) + { + unregisterFlexClient(flexClient); + } + + /** + * + * Used internally to associate a FlexClient with the FlexSession. + * + * @param flexClient The FlexClient to assocaite with the session. + */ + public void registerFlexClient(FlexClient flexClient) + { + if (flexClients.addIfAbsent(flexClient)) + { + flexClient.addClientDestroyedListener(this); + flexClient.registerFlexSession(this); + } + } + + /** + * + * Used internally to disassociate a FlexClient from the FlexSession. + * + * @param flexClient The FlexClient to disassociate from the session. + */ + public void unregisterFlexClient(FlexClient flexClient) + { + if (flexClients.remove(flexClient)) + { + flexClient.removeClientDestroyedListener(this); + flexClient.unregisterFlexSession(this); + } + } + + /** + * + * Used internally to associate a MessagClient (subscription) with the FlexSession. + * + * @param messageClient The MessageClient to associate with the session. + */ + public void registerMessageClient(MessageClient messageClient) + { + if (messageClients == null) + { + synchronized (lock) + { + if (messageClients == null) + messageClients = new CopyOnWriteArrayList<MessageClient>(); + } + } + + if (messageClients.addIfAbsent(messageClient)) + messageClient.addMessageClientDestroyedListener(this); + } + + /** + * + * Used internally to disassociate a MessageClient (subscription) from a FlexSession. + * + * @param messageClient The MessageClient to disassociate from the session. + */ + public void unregisterMessageClient(MessageClient messageClient) + { + if (messageClients != null && messageClients.remove(messageClient)) + messageClient.removeMessageClientDestroyedListener(this); + } + + /** + * Default implementation invokes <code>invalidate()</code> upon timeout. + * + * @see flex.messaging.util.TimeoutCapable#timeout() + */ + public void timeout() + { + invalidate(); + } + + //-------------------------------------------------------------------------- + // + // Protected Methods + // + //-------------------------------------------------------------------------- + + /** + * Ensures that the session has not been invalidated. + */ + protected void checkValid() + { + synchronized (lock) + { + if (!valid) + { + LocalizedException e = new LocalizedException(); + e.setMessage(FLEX_SESSION_INVALIDATED); + throw e; + } + } + } + + /** + * Notify attribute listeners that an attribute has been added. + * + * @param name The name of the attribute. + * @param value The new value of the attribute. + */ + protected void notifyAttributeAdded(String name, Object value) + { + if (attributeListeners != null && !attributeListeners.isEmpty()) + { + FlexSessionBindingEvent event = new FlexSessionBindingEvent(this, name, value); + // CopyOnWriteArrayList is iteration-safe from ConcurrentModificationExceptions. + for (FlexSessionAttributeListener attribListener : attributeListeners) + attribListener.attributeAdded(event); + } + } + + /** + * Notify binding listener that it has been bound to the session. + * + * @param name The attribute name. + * @param value The attribute that has been bound. + */ + protected void notifyAttributeBound(String name, Object value) + { + if ((value != null) && (value instanceof FlexSessionBindingListener)) + { + FlexSessionBindingEvent bindingEvent = new FlexSessionBindingEvent(this, name); + ((FlexSessionBindingListener)value).valueBound(bindingEvent); + } + } + + /** + * Notify attribute listeners that an attribute has been removed. + * + * @param name The name of the attribute. + * @param value The previous value of the attribute. + */ + protected void notifyAttributeRemoved(String name, Object value) + { + if (attributeListeners != null && !attributeListeners.isEmpty()) + { + FlexSessionBindingEvent event = new FlexSessionBindingEvent(this, name, value); + // CopyOnWriteArrayList is iteration-safe from ConcurrentModificationExceptions. + for (FlexSessionAttributeListener attribListener : attributeListeners) + attribListener.attributeRemoved(event); + } + } + + /** + * Notify attribute listeners that an attribute has been replaced. + * + * @param name The name of the attribute. + * @param value The previous value of the attribute. + */ + protected void notifyAttributeReplaced(String name, Object value) + { + if (attributeListeners != null && !attributeListeners.isEmpty()) + { + FlexSessionBindingEvent event = new FlexSessionBindingEvent(this, name, value); + // CopyOnWriteArrayList is iteration-safe from ConcurrentModificationExceptions. + for (FlexSessionAttributeListener attribListener : attributeListeners) + attribListener.attributeReplaced(event); + } + } + + /** + * Notify binding listener that it has been unbound from the session. + * + * @param name The attribute name. + * @param value The attribute that has been unbound. + */ + protected void notifyAttributeUnbound(String name, Object value) + { + if ((value != null) && (value instanceof FlexSessionBindingListener)) + { + FlexSessionBindingEvent bindingEvent = new FlexSessionBindingEvent(this, name); + ((FlexSessionBindingListener)value).valueUnbound(bindingEvent); + } + } + + /** + * Invoked by subclass upon session creation to notify all registered + * session create listeners of the event. + * This method must be invoked in the subclass constructor. + */ + protected void notifyCreated() + { + // This guard is here only to prevent duplicate notifications if there's a coding error + // in the subclass. Not likely.. + synchronized (lock) + { + if (creationNotified) + return; + + creationNotified = true; + } + + if (!createdListeners.isEmpty()) + { + // CopyOnWriteArrayList is iteration-safe from ConcurrentModificationExceptions. + for (Iterator<FlexSessionListener> iter = createdListeners.iterator(); iter.hasNext();) + iter.next().sessionCreated(this); + } + } +}
http://git-wip-us.apache.org/repos/asf/flex-blazeds/blob/bf2e1dc9/core/src/flex/messaging/FlexSessionAttributeListener.java ---------------------------------------------------------------------- diff --git a/core/src/flex/messaging/FlexSessionAttributeListener.java b/core/src/flex/messaging/FlexSessionAttributeListener.java new file mode 100644 index 0000000..feba243 --- /dev/null +++ b/core/src/flex/messaging/FlexSessionAttributeListener.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (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.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package flex.messaging; + +/** + * Interface for Flex session attribute listeners. + */ +public interface FlexSessionAttributeListener +{ + /** + * Callback invoked after an attribute is added to the session. + * + * @param event The event containing the associated session and attribute + * information. + */ + void attributeAdded(FlexSessionBindingEvent event); + + /** + * Callback invoked after an attribute is removed from the session. + * + * @param event The event containing the associated session and attribute + * information. + */ + void attributeRemoved(FlexSessionBindingEvent event); + + /** + * Callback invoked after an attribute has been replaced with a new value. + * + * @param event The event containing the associated session and attribute + * information. + */ + void attributeReplaced(FlexSessionBindingEvent event); +} http://git-wip-us.apache.org/repos/asf/flex-blazeds/blob/bf2e1dc9/core/src/flex/messaging/FlexSessionBindingEvent.java ---------------------------------------------------------------------- diff --git a/core/src/flex/messaging/FlexSessionBindingEvent.java b/core/src/flex/messaging/FlexSessionBindingEvent.java new file mode 100644 index 0000000..512c7a9 --- /dev/null +++ b/core/src/flex/messaging/FlexSessionBindingEvent.java @@ -0,0 +1,114 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (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.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package flex.messaging; + +/** + * Event used to notify FlexSessionAttributeListeners of changes to session + * attributes. + */ +public class FlexSessionBindingEvent +{ + //-------------------------------------------------------------------------- + // + // Constructor + // + //-------------------------------------------------------------------------- + + /** + * Constructs an event for an attribute that is bound or unbound from a session. + * + * @param session The associated session. + * @param name The attribute name. + */ + public FlexSessionBindingEvent(FlexSession session, String name) + { + this.session = session; + this.name = name; + } + + /** + * Constructs an event for an attribute that is added to a session or + * replaced by a new value. + * + * @param session The associated session. + * @param name The attribute name. + * @param value The attribute value. + */ + public FlexSessionBindingEvent(FlexSession session, String name, Object value) + { + this.session = session; + this.name = name; + this.value = value; + } + + //-------------------------------------------------------------------------- + // + // Variables + // + //-------------------------------------------------------------------------- + + /** + * The session that generated the event. + */ + private FlexSession session; + + /** + * The name of the attribute associated with the event. + */ + private String name; + + /** + * The value of the attribute associated with the event. + */ + private Object value; + + //-------------------------------------------------------------------------- + // + // Methods + // + //-------------------------------------------------------------------------- + + /** + * Returns the Flex session that generated the event. + * + * @return The Flex session that generated the event. + */ + public FlexSession getSession() + { + return session; + } + + /** + * Returns the name of the attribute associated with the event. + * + * @return The name of the attribute associated with the event. + */ + public String getName() + { + return name; + } + + /** + * Returns the value of the attribute associated with the event. + * + * @return The value of the attribute associated with the event. + */ + public Object getValue() + { + return value; + } +} http://git-wip-us.apache.org/repos/asf/flex-blazeds/blob/bf2e1dc9/core/src/flex/messaging/FlexSessionBindingListener.java ---------------------------------------------------------------------- diff --git a/core/src/flex/messaging/FlexSessionBindingListener.java b/core/src/flex/messaging/FlexSessionBindingListener.java new file mode 100644 index 0000000..3c9b98e --- /dev/null +++ b/core/src/flex/messaging/FlexSessionBindingListener.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (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.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package flex.messaging; + +/** + * Interface to be notified when the object is bound or unbound from the Flex + * session. + */ +public interface FlexSessionBindingListener +{ + /** + * Callback invoked when the object is bound to a Flex session. + * + * @param event The event containing the associated session and attribute + * information. + */ + void valueBound(FlexSessionBindingEvent event); + + /** + * Callback invoked when the object is unbound from a Flex session. + * + * @param event The event containing the associated session and attribute + * information. + */ + void valueUnbound(FlexSessionBindingEvent event); +} http://git-wip-us.apache.org/repos/asf/flex-blazeds/blob/bf2e1dc9/core/src/flex/messaging/FlexSessionConnectivityEvent.java ---------------------------------------------------------------------- diff --git a/core/src/flex/messaging/FlexSessionConnectivityEvent.java b/core/src/flex/messaging/FlexSessionConnectivityEvent.java new file mode 100644 index 0000000..4f1e9bd --- /dev/null +++ b/core/src/flex/messaging/FlexSessionConnectivityEvent.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (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.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package flex.messaging; + +import java.util.EventObject; + +/** + * An event dispatched when the connection state for a session changes. + */ +public class FlexSessionConnectivityEvent extends EventObject +{ + /** + * + */ + private static final long serialVersionUID = 8622680412552475829L; + + /** + * Constructs a new <tt>FlexSessionConnectivityEvent</tt> using the supplied source <tt>ConnectionAwareSession</tt>. + * + * @param session The session whose connection state has changed. + */ + public FlexSessionConnectivityEvent(ConnectionAwareSession session) + { + super(session); + } + + /** + * Returns the session whose connection state has changed. + * + * @return The session whose connection state has changed. + */ + public ConnectionAwareSession getFlexSession() + { + return (ConnectionAwareSession)getSource(); + } +} http://git-wip-us.apache.org/repos/asf/flex-blazeds/blob/bf2e1dc9/core/src/flex/messaging/FlexSessionConnectivityListener.java ---------------------------------------------------------------------- diff --git a/core/src/flex/messaging/FlexSessionConnectivityListener.java b/core/src/flex/messaging/FlexSessionConnectivityListener.java new file mode 100644 index 0000000..2fc1137 --- /dev/null +++ b/core/src/flex/messaging/FlexSessionConnectivityListener.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (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.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package flex.messaging; + +/** + * The listener interface for receiving FlexSession connectivity events. + */ + +public interface FlexSessionConnectivityListener +{ + /** + * Invoked when the session has connected to the remote host. + * + * @param event The <tt>FlexSessionConnectivityEvent</tt> for the connect event. + */ + void sessionConnected(FlexSessionConnectivityEvent event); + + /** + * Invoked when the session has disconnected from or lost connectivity to the remote host. + * + * @param event The <tt>FlexSessionConnectivityEvent</tt> for the disconnect event. + */ + void sessionDisconnected(FlexSessionConnectivityEvent event); +} http://git-wip-us.apache.org/repos/asf/flex-blazeds/blob/bf2e1dc9/core/src/flex/messaging/FlexSessionListener.java ---------------------------------------------------------------------- diff --git a/core/src/flex/messaging/FlexSessionListener.java b/core/src/flex/messaging/FlexSessionListener.java new file mode 100644 index 0000000..b9058d5 --- /dev/null +++ b/core/src/flex/messaging/FlexSessionListener.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (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.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package flex.messaging; + +/** + * Interface to be notified when a FlexSession is created or destroyed. Implementations of this interface + * may add themselves as session created listeners statically via <code>FlexSession.addSessionCreatedListener()</code>. + * To listen for FlexSession destruction, the implementation class instance must add itself as a listener to + * a specific FlexSession instance via the <code>addSessionDestroyedListener()</code> method. + */ +public interface FlexSessionListener +{ + /** + * Notification that a FlexSession was created. + * + * @param session The FlexSession that was created. + */ + void sessionCreated(FlexSession session); + + /** + * Notification that a FlexSession is about to be destroyed. + * + * @param session The FlexSession that will be destroyed. + */ + void sessionDestroyed(FlexSession session); +} http://git-wip-us.apache.org/repos/asf/flex-blazeds/blob/bf2e1dc9/core/src/flex/messaging/FlexSessionManager.java ---------------------------------------------------------------------- diff --git a/core/src/flex/messaging/FlexSessionManager.java b/core/src/flex/messaging/FlexSessionManager.java new file mode 100644 index 0000000..bec0352 --- /dev/null +++ b/core/src/flex/messaging/FlexSessionManager.java @@ -0,0 +1,299 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (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.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package flex.messaging; + +import flex.management.ManageableComponent; +import flex.messaging.log.LogCategories; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Manages FlexSession instances for a MessageBroker. + */ +public class FlexSessionManager extends ManageableComponent +{ + public static final String TYPE = "FlexSessionManager"; + + private static final long MILLIS_IN_HOUR = 3600000; + + //-------------------------------------------------------------------------- + // + // Constructors + // + //-------------------------------------------------------------------------- + + /** + * + * Constructs a <tt>FlexSessionManager</tt> for the passed <tt>MessageBroker</tt>. + * + * @param broker The root <tt>MessageBroker</tt> using this <tt>FlexSessionManager</tt>. + */ + public FlexSessionManager(MessageBroker broker) + { + this(false, broker); + } + + /** + * + * Constructs a <tt>FlexSessionManager</tt> for the passed <tt>MessageBroker</tt> and optionally enables management. + * + * @param enableManagement <code>true</code> if the <tt>FlexSessionManager</tt> + * is manageable; otherwise <code>false</code>. + * @param broker the message broker + */ + public FlexSessionManager(boolean enableManagement, MessageBroker broker) + { + super(enableManagement); + + super.setId(TYPE); + + this.broker = broker; + + this.setParent(broker); + } + + //-------------------------------------------------------------------------- + // + // Variables + // + //-------------------------------------------------------------------------- + + /** + * Instance-level lock. + */ + private final Object lock = new Object(); + + //-------------------------------------------------------------------------- + // + // Properties + // + //-------------------------------------------------------------------------- + + //---------------------------------- + // logCategory + //---------------------------------- + + /** + * Returns the log category for this component. + * + * @return The log category for this component. + */ + @Override + protected String getLogCategory() + { + return LogCategories.ENDPOINT_FLEXSESSION; + } + + //---------------------------------- + // flexSessionCount + //---------------------------------- + + private int flexSessionCount; + + /** + * Returns the total count of active FlexSessions. + * + * @return The total count of active FlexSessions. + */ + public int getFlexSessionCount() + { + synchronized (lock) + { + return flexSessionCount; + } + } + + //---------------------------------- + // flexSessionProviders + //---------------------------------- + + private final ConcurrentHashMap<Class<? extends FlexSession>, AbstractFlexSessionProvider> providers = new ConcurrentHashMap<Class<? extends FlexSession>, AbstractFlexSessionProvider>(); + + /** + * Returns the registered <tt>FlexSessionProvider</tt> implementation for the specified <tt>FlexSession</tt> type. + * + * @param sessionClass The specific <tt>FlexSession</tt> type to get a provider for. + * @return The registered <tt>FlexSessionProvider</tt> or <code>null</code> if no provider is registered. + */ + public AbstractFlexSessionProvider getFlexSessionProvider(Class<? extends FlexSession> sessionClass) + { + return providers.get(sessionClass); + } + + /** + * Registers a <tt>FlexSessionProvider</tt> implementation for a specified <tt>FlexSession</tt> type. + * + * @param sessionClass The specific <tt>FlexSession</tt> type to register a provider for. + * @param provider The corresponding <tt>FlexSessionProvider</tt> to register. + * @return The previously registered provider, or <code>null</code> if no provider was registered for this session type. + */ + public AbstractFlexSessionProvider registerFlexSessionProvider(Class<? extends FlexSession> sessionClass, AbstractFlexSessionProvider provider) + { + provider.setFlexSessionManager(this); + AbstractFlexSessionProvider previousProvider = providers.putIfAbsent(sessionClass, provider); + + if (previousProvider != null) + { + previousProvider.stop(); + previousProvider.setFlexSessionManager(null); + } + + if (isStarted()) + provider.start(); + + return previousProvider; + } + + /** + * Unregisters a <tt>FlexSessionProvider</tt> implementation for a specified <tt>FlexSession</tt> type. + * + * @param sessionClass The specific <tt>FlexSession</tt> type to unregister a provider for. + */ + public void unregisterFlexSessionProvider(Class<? extends FlexSession> sessionClass) + { + AbstractFlexSessionProvider provider = providers.remove(sessionClass); + if (provider != null) + { + provider.stop(); + provider.setFlexSessionManager(null); + } + } + + //---------------------------------- + // flexSessions + //---------------------------------- + + /** + * Registers a new <tt>FlexSession</tt> with the <tt>FlexSessionManager</tt>. + * + * @param session The new <tt>FlexSession</tt>. + */ + public void registerFlexSession(FlexSession session) + { + synchronized (lock) + { + ++flexSessionCount; + resetMaxFlexSessionsInCurrentHour(flexSessionCount); + } + } + + /** + * Unregisters an invalidated <tt>FlexSession</tt> from the <tt>FlexSessionManager</tt>. + * + * @param session The invalidated <tt>FlexSession</tt>. + */ + public void unregisterFlexSession(FlexSession session) + { + synchronized (lock) + { + --flexSessionCount; + resetMaxFlexSessionsInCurrentHour(flexSessionCount); + } + } + + //---------------------------------- + // maxFlexSessionsInCurrentHour + //---------------------------------- + + private int maxSessionCountInCurrentHour; + private long currentHourStartTimestamp = System.currentTimeMillis(); + + public int getMaxFlexSessionsInCurrentHour() + { + synchronized (lock) + { + // Make sure we report the correct value if the system has been idle across an hour transition. + resetMaxFlexSessionsInCurrentHour(flexSessionCount); + + return maxSessionCountInCurrentHour; + } + } + + /* Must be called within a synchronized block. */ + private void resetMaxFlexSessionsInCurrentHour(int currentCount) + { + long offset = (System.currentTimeMillis() - currentHourStartTimestamp) / MILLIS_IN_HOUR; + if (offset > 0) // Shift to the current hour and reset to the current session count. + { + currentHourStartTimestamp += (MILLIS_IN_HOUR * offset); + maxSessionCountInCurrentHour = currentCount; + } + else if (maxSessionCountInCurrentHour < currentCount) + { + maxSessionCountInCurrentHour = currentCount; + } + } + + //---------------------------------- + // messageBroker + //---------------------------------- + + private final MessageBroker broker; + + /** + * Returns the <tt>MessageBroker</tt> instance that owns this <tt>FlexSessionManager</tt>. + * + * @return The parent <tt>MessageBroker</tt> instance. + */ + public MessageBroker getMessageBroker() + { + return broker; + } + + //-------------------------------------------------------------------------- + // + // Public Methods + // + //-------------------------------------------------------------------------- + + /** + * Starts the <tt>FlexSessionManager</tt>. + * Any registered <tt>FlexSession</tt>s providers are also started. + */ + @Override + public void start() + { + if (isStarted()) + return; + + for (AbstractFlexSessionProvider provider : providers.values()) + { + if (!provider.isStarted()) + provider.start(); + } + + super.start(); + } + + /** + * Stops the <tt>FlexSessionManager</tt>. + * Any registered <tt>FlexSession</tt> providers are stopped and unregistered. + */ + @Override + public void stop() + { + if (!isStarted()) + return; + + super.stop(); + + for (Class<? extends FlexSession> sessionClass : providers.keySet()) + { + unregisterFlexSessionProvider(sessionClass); + } + providers.clear(); + } +} http://git-wip-us.apache.org/repos/asf/flex-blazeds/blob/bf2e1dc9/core/src/flex/messaging/HttpFlexSession.java ---------------------------------------------------------------------- diff --git a/core/src/flex/messaging/HttpFlexSession.java b/core/src/flex/messaging/HttpFlexSession.java new file mode 100644 index 0000000..3bf626a --- /dev/null +++ b/core/src/flex/messaging/HttpFlexSession.java @@ -0,0 +1,668 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (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.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package flex.messaging; + +import flex.messaging.log.Log; +import flex.messaging.log.LogCategories; + +import javax.servlet.ServletContext; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; +import javax.servlet.http.HttpSessionAttributeListener; +import javax.servlet.http.HttpSessionBindingEvent; +import javax.servlet.http.HttpSessionBindingListener; +import javax.servlet.http.HttpSessionEvent; +import javax.servlet.http.HttpSessionListener; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.security.Principal; +import java.util.Enumeration; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * FlexSession implementation for use with HTTP-based channels. + */ +public class HttpFlexSession extends FlexSession + implements HttpSessionBindingListener, HttpSessionListener, HttpSessionAttributeListener, Serializable +{ + //-------------------------------------------------------------------------- + // + // Constructor + // + //-------------------------------------------------------------------------- + + /** + * + * Not for public use. This constructor is used to create an instance of this class that + * will operate as a javax.servlet.http.HttpSessionListener registered in web.xml. + */ + public HttpFlexSession() + {} + + /** + * + * Not for public use. Constructs new instances that effectively wrap pre-existing JEE HttpSession instances. + * + * @param provider HttpFlexSessionProvider object + */ + public HttpFlexSession(HttpFlexSessionProvider provider) + { + super(provider); + } + + //-------------------------------------------------------------------------- + // + // Constants + // + //-------------------------------------------------------------------------- + + /** + * Serializable version uid. + */ + private static final long serialVersionUID = -1260409488935306147L; + + /** + * Attribute name that HttpFlexSession is stored under in the HttpSession. + */ + /* package-private */ static final String SESSION_ATTRIBUTE = "__flexSession"; + + /** + * This attribute is set on the request associated with a Flex Session when + * a logout command is being processed. The reason that this is necessary is + * that a single HttpServletRequest may contain more than one Flex command/message. + * In this case, every message following a "logout" command should behave as if the + * user has logged out. However, since in getUserPrincipal, we check the request + * object if the Session has no principal, if the current request object + * has a principal object associated with it (as it does on Tomcat/JBoss), + * messages following a "logout" will use this principal. We thus need to + * invalidate the user principal in the request on logout. Since the field + * is read-only we do so using this attribute. + */ + private static final String INVALIDATED_REQUEST = "__flexInvalidatedRequest"; + + public static final String SESSION_MAP = "LCDS_HTTP_TO_FLEX_SESSION_MAP"; + + /** + * Internal flag indicating whether we are a registered listener in web.xml. + */ + /* package-private */ static volatile boolean isHttpSessionListener; + + /** + * Flag to indicate whether we've logged a warning if we weren't registered in web.xml and + * can't redispatch attribute and binding events to Flex listeners. + */ + /* package-private */ static volatile boolean warnedNoEventRedispatch; + + /** + * The log category to send the warning for no event redispatch to. + */ + /* package-private */ static String WARN_LOG_CATEGORY = LogCategories.CONFIGURATION; + + //-------------------------------------------------------------------------- + // + // Variables + // + //-------------------------------------------------------------------------- + + /** + * Reference to the HttpSession allows us to invalidate it and use it for attribute management. + */ + /* package-private */ HttpSession httpSession; + + /** + * + * Static lock for creating httpSessionToFlexSession map + */ + public static final Object mapLock = new Object(); + + + //-------------------------------------------------------------------------- + // + // Public Methods + // + //-------------------------------------------------------------------------- + + /** + * HttpSessionAttributeListener callback; processes the addition of an attribute to an HttpSession. + * + * NOTE: Callback is not made against an HttpFlexSession associated with a request + * handling thread. + * @param event the HttpSessionBindingEvent + */ + public void attributeAdded(HttpSessionBindingEvent event) + { + if (!event.getName().equals(SESSION_ATTRIBUTE)) + { + // Accessing flexSession via map because it may have already been unbound from httpSession. + Map httpSessionToFlexSessionMap = getHttpSessionToFlexSessionMap(event.getSession()); + HttpFlexSession flexSession = (HttpFlexSession)httpSessionToFlexSessionMap.get(event.getSession().getId()); + if (flexSession != null) + { + String name = event.getName(); + Object value = event.getValue(); + flexSession.notifyAttributeBound(name, value); + flexSession.notifyAttributeAdded(name, value); + } + } + } + + /** + * HttpSessionAttributeListener callback; processes the removal of an attribute from an HttpSession. + * + * NOTE: Callback is not made against an HttpFlexSession associated with a request + * handling thread. + * @param event the HttpSessionBindingEvent + */ + public void attributeRemoved(HttpSessionBindingEvent event) + { + if (!event.getName().equals(SESSION_ATTRIBUTE)) + { + // Accessing flexSession via map because it may have already been unbound from httpSession. + Map httpSessionToFlexSessionMap = getHttpSessionToFlexSessionMap(event.getSession()); + HttpFlexSession flexSession = (HttpFlexSession)httpSessionToFlexSessionMap.get(event.getSession().getId()); + if (flexSession != null) + { + String name = event.getName(); + Object value = event.getValue(); + flexSession.notifyAttributeUnbound(name, value); + flexSession.notifyAttributeRemoved(name, value); + } + } + } + + /** + * HttpSessionAttributeListener callback; processes the replacement of an attribute in an HttpSession. + * + * NOTE: Callback is not made against an HttpFlexSession associated with a request + * handling thread. + * @param event the HttpSessionBindingEvent + */ + public void attributeReplaced(HttpSessionBindingEvent event) + { + if (!event.getName().equals(SESSION_ATTRIBUTE)) + { + // Accessing flexSession via map because it may have already been unbound from httpSession. + Map httpSessionToFlexSessionMap = getHttpSessionToFlexSessionMap(event.getSession()); + HttpFlexSession flexSession = (HttpFlexSession)httpSessionToFlexSessionMap.get(event.getSession().getId()); + if (flexSession != null) + { + String name = event.getName(); + Object value = event.getValue(); + Object newValue = flexSession.getAttribute(name); + flexSession.notifyAttributeUnbound(name, value); + flexSession.notifyAttributeReplaced(name, value); + flexSession.notifyAttributeBound(name, newValue); + } + } + } + + /** + * Creates or retrieves a FlexSession for the current Http request. + * The HttpFlexSession wraps the underlying J2EE HttpSession. + * Not intended for public use. + * + * @param req The Http request. + * + * @return The HttpFlexSession. + * + * @deprecated This method has been deprecated in favor of session providers registered with a <tt>MessageBroker</tt>. + * @see flex.messaging.FlexSessionManager + * @see flex.messaging.HttpFlexSessionProvider + */ + public static HttpFlexSession getFlexSession(HttpServletRequest req) + { + HttpFlexSession flexSession; + HttpSession httpSession = req.getSession(true); + + if (!isHttpSessionListener && !warnedNoEventRedispatch) + { + warnedNoEventRedispatch = true; + if (Log.isWarn()) + Log.getLogger(WARN_LOG_CATEGORY).warn("HttpFlexSession has not been registered as a listener in web.xml for this application so no events will be dispatched to FlexSessionAttributeListeners or FlexSessionBindingListeners. To correct this, register flex.messaging.HttpFlexSession as a listener in web.xml."); + } + + boolean isNew = false; + synchronized (httpSession) + { + flexSession = (HttpFlexSession)httpSession.getAttribute(HttpFlexSession.SESSION_ATTRIBUTE); + if (flexSession == null) + { + flexSession = new HttpFlexSession(); + // Correlate this FlexSession to the HttpSession before triggering any listeners. + FlexContext.setThreadLocalSession(flexSession); + httpSession.setAttribute(SESSION_ATTRIBUTE, flexSession); + flexSession.setHttpSession(httpSession); + isNew = true; + } + else + { + FlexContext.setThreadLocalSession(flexSession); + if (flexSession.httpSession == null) + { + // httpSession is null if the instance is new or is from + // serialization. + flexSession.setHttpSession(httpSession); + isNew = true; + } + } + } + + if (isNew) + { + flexSession.notifyCreated(); + + if (Log.isDebug()) + Log.getLogger(FLEX_SESSION_LOG_CATEGORY).debug("FlexSession created with id '" + flexSession.getId() + "' for an Http-based client connection."); + } + + return flexSession; + } + + /** + * Returns the user principal associated with the session. This will + * be null if the user has not authenticated. + * + * @return The Principal associated with the session. + */ + public Principal getUserPrincipal() + { + Principal p = super.getUserPrincipal(); + if (p == null) + { + HttpServletRequest req = FlexContext.getHttpRequest(); + if (req != null && req.getAttribute(INVALIDATED_REQUEST) == null) + p = req.getUserPrincipal(); + } + return p; + } + + /** + * Invalidates the session. + */ + public void invalidate() + { + // If the HttpFlexSession is the current active FlexSession for the thread + // we'll invalidate it but we need to recreate a new HttpFlexSession because + // the client's HttpSession is still active. + boolean recreate = FlexContext.getFlexSession() == this; + invalidate(recreate); + } + + /** + * + * Used by Http endpoints when they receive notification from a client that it has + * disconnected its channel. + * Supports invalidating the HttpFlexSession and underlying JEE HttpSession without + * triggering session recreation. + * + * @param recreate true if the http session should be recreated. + */ + public void invalidate(boolean recreate) + { + synchronized (httpSession) + { + try + { + // Invalidating the HttpSession will trigger invalidation of the HttpFlexSession + // either via the sessionDestroyed() event if registration as an HttpSession listener worked + // or via the valueUnbound() event if it didn't. + httpSession.invalidate(); + } + catch (IllegalStateException e) + { + // Make sure any related mapping is removed. + try + { + Map httpSessionToFlexSessionMap = getHttpSessionToFlexSessionMap(httpSession); + httpSessionToFlexSessionMap.remove(httpSession.getId()); + } + catch (Exception ignore) + { + // NOWARN + } + + // And invalidate this FlexSession. + super.invalidate(); + } + } + if (recreate) + { + HttpServletRequest req = FlexContext.getHttpRequest(); + + if (req != null) + { + // Set an attribute on the request denoting that the userPrincipal in the request + // is now invalid. + req.setAttribute(INVALIDATED_REQUEST, "true"); + + AbstractFlexSessionProvider sessionProvider = getFlexSessionProvider(); + + // BLZ-531: When using spring integration getting a null pointer exception when calling invalidate + // on a FlexSession twice + // If originally the HttpFlexSession was created using the deprecated HttpFlexSession.getFlexSession(request) API, + // it does not have an associated AbstractFlexSessionProvider. Invoking invalidate(true) on such a session + // results in the "recreated" FlexSession being NULL. To prevent this from happening, in case session provider + // is NULL, we create the session using the deprecated HttpFlexSession.getFlexSession(request) API. + FlexSession session = sessionProvider == null ? + getFlexSession(req) : ((HttpFlexSessionProvider)sessionProvider).getOrCreateSession(req); + + FlexContext.setThreadLocalObjects(FlexContext.getFlexClient(), + session, FlexContext.getMessageBroker(), req, + FlexContext.getHttpResponse(), FlexContext.getServletConfig()); + } + // else, the session was invalidated outside of a request being processed. + } + } + + /** + * Returns the attribute bound to the specified name in the session, or null + * if no attribute is bound under the name. + * + * @param name The name the target attribute is bound to. + * @return The attribute bound to the specified name. + */ + public Object getAttribute(String name) + { + return httpSession.getAttribute(name); + } + + /** + * Returns the names of all attributes bound to the session. + * + * @return The names of all attributes bound to the session. + */ + public Enumeration getAttributeNames() + { + return httpSession.getAttributeNames(); + } + + /** + * Returns the Id for the session. + * + * @return The Id for the session. + */ + public String getId() + { + return httpSession.getId(); + } + + /** + * + * FlexClient invokes this to determine whether the session can be used to push messages + * to the client. + * + * @return true if the FlexSession supports direct push; otherwise false (polling is assumed). + */ + public boolean isPushSupported() + { + return false; + } + + /** + * Removes the attribute bound to the specified name in the session. + * + * @param name The name of the attribute to remove. + */ + public void removeAttribute(String name) + { + httpSession.removeAttribute(name); + } + + /** + * Implements HttpSessionListener. + * HttpSession created events are handled by setting an internal flag indicating that registration + * as an HttpSession listener was successful and we will be notified of session attribute changes and + * session destruction. + * NOTE: This method is not invoked against an HttpFlexSession associated with a request + * handling thread. + * @param event the HttpSessionEvent + */ + public void sessionCreated(HttpSessionEvent event) + { + isHttpSessionListener = true; + } + + /** + * Implements HttpSessionListener. + * When an HttpSession is destroyed, the associated HttpFlexSession is also destroyed. + * NOTE: This method is not invoked against an HttpFlexSession associated with a request + * handling thread. + * @param event the HttpSessionEvent + */ + public void sessionDestroyed(HttpSessionEvent event) + { + HttpSession session = event.getSession(); + Map httpSessionToFlexSessionMap = getHttpSessionToFlexSessionMap(session); + HttpFlexSession flexSession = (HttpFlexSession)httpSessionToFlexSessionMap.remove(session.getId()); + if (flexSession != null) + { + // invalidate the flex session + flexSession.superInvalidate(); + + // Send notifications to attribute listeners if needed. + // This may send extra notifications if attributeRemoved is called first by the server, + // but Java servlet 2.4 says session destroy is first, then attributes. + // Guard against pre-2.4 containers that dispatch events in an incorrect order, + // meaning skip attribute processing here if the underlying session state is no longer valid. + try + { + for (Enumeration e = session.getAttributeNames(); e.hasMoreElements(); ) + { + String name = (String) e.nextElement(); + if (name.equals(SESSION_ATTRIBUTE)) + continue; + Object value = session.getAttribute(name); + if (value != null) + { + flexSession.notifyAttributeUnbound(name, value); + flexSession.notifyAttributeRemoved(name, value); + } + } + } + catch (IllegalStateException ignore) + { + // NOWARN + // Old servlet container that dispatches events out of order. + } + } + } + + /** + * Binds an attribute to the session under the specified name. + * + * @param name The name to bind the attribute under. + * + * @param value The attribute value. + */ + public void setAttribute(String name, Object value) + { + httpSession.setAttribute(name, value); + } + + /** + * Implements HttpSessionBindingListener. + * This is a no-op. + * NOTE: This method is not invoked against an HttpFlexSession associated with a request + * handling thread. + * @param event the HttpSessionBindingEvent + */ + public void valueBound(HttpSessionBindingEvent event) + { + // No-op. + } + + /** + * Implements HttpSessionBindingListener. + * This callback will destroy the HttpFlexSession upon being unbound, only in the + * case where we haven't been registered as an HttpSessionListener in web.xml and + * can't shut down based on the HttpSession being invalidated. + * NOTE: This method is not invoked against an HttpFlexSession associated with a request + * handling thread. + * @param event the HttpSessionBindingEvent + */ + public void valueUnbound(HttpSessionBindingEvent event) + { + if (!isHttpSessionListener) + { + Map httpSessionToFlexSessionMap = getHttpSessionToFlexSessionMap(event.getSession()); + HttpFlexSession flexSession = (HttpFlexSession)httpSessionToFlexSessionMap.remove(event.getSession().getId()); + if (flexSession != null) + flexSession.superInvalidate(); + } + } + + //-------------------------------------------------------------------------- + // + // Protected Methods + // + //-------------------------------------------------------------------------- + + /** + * We don't need to do anything here other than log out some info about the session that's shutting down. + */ + protected void internalInvalidate() + { + if (Log.isDebug()) + Log.getLogger(FLEX_SESSION_LOG_CATEGORY).debug("FlexSession with id '" + getId() + "' for an Http-based client connection has been invalidated."); + } + + //-------------------------------------------------------------------------- + // + // Private Methods + // + //-------------------------------------------------------------------------- + + /** + * Associates a HttpSession with the FlexSession. + * + * @param httpSession The HttpSession to associate with the FlexSession. + */ + /* package-private */ void setHttpSession(HttpSession httpSession) + { + synchronized (lock) + { + this.httpSession = httpSession; + // Update lookup table for event redispatch. + Map httpSessionToFlexSessionMap = getHttpSessionToFlexSessionMap(httpSession); + httpSessionToFlexSessionMap.put(httpSession.getId(), this); + } + } + + /** + * + * Invoked by HttpSessionListener or binding listener on HttpSession invalidation to invalidate the wrapping + * FlexSession. + */ + private void superInvalidate() + { + super.invalidate(); + } + + /** + * Implements Serializable; only the Principal needs to be serialized as all + * attribute storage is delegated to the associated HttpSession. + * + * @param stream The stream to read instance state from. + */ + private void writeObject(ObjectOutputStream stream) + { + try + { + Principal principal = super.getUserPrincipal(); + if (principal != null && principal instanceof Serializable) + stream.writeObject(principal); + } + catch (IOException e) + { + // Principal was Serializable and non-null; if this happens there's nothing we can do. + // The user will need to reauthenticate if necessary. + } + catch (LocalizedException ignore) + { + // This catch block added for bug 194144. + // On BEA WebLogic, writeObject() is sometimes invoked on invalidated session instances + // and in this case the checkValid() invocation in super.getUserPrincipal() throws. + // Ignore this exception. + } + } + + /** + * Implements Serializable; only the Principal needs to be serialized as all + * attribute storage is delegated to the associated HttpSession. + * + * @param stream The stream to write instance state to. + */ + private void readObject(ObjectInputStream stream) + { + try + { + setUserPrincipal((Principal)stream.readObject()); + } + catch (Exception e) + { + // Principal was not serialized or failed to serialize; ignore. + // The user will need to reauthenticate if necessary. + } + } + + /** + * Map of HttpSession Ids to FlexSessions. We need this when registered as a listener + * in web.xml in order to trigger the destruction of a FlexSession when its associated HttpSession + * is invalidated/destroyed. The Servlet spec prior to version 2.4 defined the session destruction event + * to be dispatched after attributes are unbound from the session so when we receive notification that + * an HttpSession is destroyed there's no way to get to the associated FlexSession attribute because it + * has already been unbound... Additionally, we need it to handle attribute removal events that happen + * during HttpSession destruction because the FlexSession can be unbound from the session before the + * other attributes we receive notification for. + * + * Because of this, it's simplest to just maintain this lookup table and use it for all HttpSession + * related event handling. + * + * The table is maintained on the servlet context instead of statically in order to prevent collisions + * across web-apps. + */ + private Map getHttpSessionToFlexSessionMap(HttpSession session) + { + try + { + ServletContext context = session.getServletContext(); + Map map = (Map)context.getAttribute(SESSION_MAP); + + if(map==null){ + // map should never be null here as it is created during MessageBrokerServlet start-up + if (Log.isError()) + Log.getLogger(FLEX_SESSION_LOG_CATEGORY).error("HttpSession to FlexSession map not created in message broker for " + + session.getId()); + MessageException me = new MessageException(); + me.setMessage(10032, new Object[] {session.getId()}); + throw me; + } + return map; + } + catch(Exception e) + { + if (Log.isDebug()) + Log.getLogger(FLEX_SESSION_LOG_CATEGORY).debug("Unable to get HttpSession to FlexSession map for " + + session.getId() + " " + e.toString()); + return new ConcurrentHashMap(); + } + } + +}
