Package flex.messaging.client

Source Code of flex.messaging.client.FlexClient

/*************************************************************************
*
* ADOBE CONFIDENTIAL
* __________________
*
*  [2002] - [2007] Adobe Systems Incorporated
*  All Rights Reserved.
*
* NOTICE:  All information contained herein is, and remains
* the property of Adobe Systems Incorporated and its suppliers,
* if any.  The intellectual and technical concepts contained
* herein are proprietary to Adobe Systems Incorporated
* and its suppliers and may be covered by U.S. and Foreign Patents,
* patents in process, and are protected by trade secret or copyright law.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from Adobe Systems Incorporated.
**************************************************************************/
package flex.messaging.client;

import edu.emory.mathcs.backport.java.util.concurrent.CopyOnWriteArrayList;
import flex.messaging.FlexContext;
import flex.messaging.FlexSession;
import flex.messaging.FlexSessionListener;
import flex.messaging.MessageClient;
import flex.messaging.MessageClientListener;
import flex.messaging.MessageException;
import flex.messaging.log.Log;
import flex.messaging.log.LogCategories;
import flex.messaging.messages.CommandMessage;
import flex.messaging.messages.Message;
import flex.messaging.services.MessageService;
import flex.messaging.util.StringUtils;
import flex.messaging.util.TimeoutAbstractObject;
import flex.messaging.util.UUIDUtils;

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.TimerTask;

/**
* Represents a Flex client application instance on the server.
*/
public class FlexClient extends TimeoutAbstractObject implements FlexSessionListener, MessageClientListener
{   
    //--------------------------------------------------------------------------
    //
    // Public Static Constants
    //
    //--------------------------------------------------------------------------

    /**
     * Log category for FlexClient related messages.
     */
    public static final String FLEX_CLIENT_LOG_CATEGORY = LogCategories.CLIENT_FLEXCLIENT;

    /**
     * This value is passed to the server in an initial client connect to
     * indicate that the client needs a server-assigned FlexClient Id.
     * @exclude
     */
    public static final String NULL_FLEXCLIENT_ID = "nil";

    //--------------------------------------------------------------------------
    //
    // Private Static Constants
    //
    //--------------------------------------------------------------------------
   
    /**
     * Error string constants.
     */
    private static final int FLEX_CLIENT_INVALIDATED = 10027;
    private static final int ENDPOINT_PUSH_HANDLER_ALREADY_REGISTERED = 10033;
   
    private static final String POLL_WAIT_THREAD_NAME_EXTENSION = "-in-poll-wait";
   
    //--------------------------------------------------------------------------
    //
    // Private Static Variables
    //
    //--------------------------------------------------------------------------
   
    /**
     * List of registered FlexClient created listeners.
     */
    private static final CopyOnWriteArrayList createdListeners = new CopyOnWriteArrayList();
       
    //--------------------------------------------------------------------------
    //
    // Public Static Methods
    //
    //--------------------------------------------------------------------------

    /**
     * Adds a create listener that will be notified when new FlexClients
     * are created.
     *
     * @see flex.messaging.client.FlexClientListener
     *
     * @param listener The listener to add.
     */
    public static void addClientCreatedListener(FlexClientListener listener)
    {
        if (listener != null)
            createdListeners.addIfAbsent(listener);              
    }

    /**
     * Removes a FlexClient created listener.
     *
     * @see flex.messaging.client.FlexClientListener
     *
     * @param listener The listener to remove.
     */
    public static void removeClientCreatedListener(FlexClientListener listener)
    {
        if (listener != null)
            createdListeners.remove(listener);
    }  
   
    //--------------------------------------------------------------------------
    //
    // Constructor
    //
    //--------------------------------------------------------------------------
   
    /**
     * @exclude
     * Constructs a new FlexClient instance.
     *
     * @param manager The FlexClientManager managing this instance.
     */
    public FlexClient(FlexClientManager manager)
    {
        this(manager, UUIDUtils.createUUID());
    }
   
    /**
     * @exclude
     * Constructs a new FlexClient instance having the specified Id.
     *
     * @param manager The FlexClientManager managing this instance.
     * @param id The Id for this instance.
     */
    public FlexClient(FlexClientManager manager, String id)
    {
        this.id = id;
        flexClientManager = manager;
        updateLastUse();
        valid = true;
       
        if (Log.isDebug())
            Log.getLogger(FLEX_CLIENT_LOG_CATEGORY).debug("FlexClient created with id '" + this.id + "'.");
    }
   
    //--------------------------------------------------------------------------
    //
    // Variables
    //
    //--------------------------------------------------------------------------
   
    /**
     * Storage for custom attributes.
     */
    private volatile Map attributes;
   
    /**
     * List of registered FlexClient attribute listeners.
     */
    private volatile CopyOnWriteArrayList attributeListeners;
   
    /**
     * List of registered FlexClient destroyed listeners.
     */
    private volatile CopyOnWriteArrayList destroyedListeners;
       
    /**
     * The manager for the FlexClient.
     */
    private final FlexClientManager flexClientManager;
   
    /**
     * The unique Id for the instance.
     */
    private final String id;
   
    /**
     * Flag used to break cycles during invalidation.
     */
    /* package visibility for FlexClientManager */ volatile boolean invalidating;  
   
    /**
     * Instance level lock to sync for state changes.
     */
    private final Object lock = new Object();
   
    /**
     * MessageClient subscriptions for this MessageClient.
     */
    private volatile CopyOnWriteArrayList messageClients;
   
    /**
     * Queues of outbound messages to push to the client keyed by endpoint id.
     * Map(String endpointId, EndpointQueue queue).
     */
    private final Map outboundQueues = new HashMap(1);
   
    /**
     * EndpointPushHandlers keyed by endpointId that the FlexClient
     * can use to push messages to remote clients.
     * NOTE: these can't be added to the EndpointQueue data type because the existence of queues depends
     * upon client subscription state whereas endpoints that support push will generally set up their push
     * handling before any subscriptions have been created.
     */
    private Map endpointPushHandlers;   
   
    /**
     * Associated FlexSessions that represent the connections the FlexClient makes to the server.
     */
    private final CopyOnWriteArrayList sessions = new CopyOnWriteArrayList(); // We always have at least one session.  
   
    /**
     * Flag indicating whether the instance is valid; once invalidated this flag is
     * set to false.
     */
    private boolean valid;   
   
    /**
     * The principal associated with this client.  Only used when perClientAuthentication
     * is being used.
     */
    private Principal userPrincipal;
       
    //--------------------------------------------------------------------------
    //
    // Public Methods
    //
    //--------------------------------------------------------------------------

    /**
     * Adds a FlexClient attribute listener that will be notified when an
     * attribute is added, removed or changed. If the attribute implements
     * FlexClientBindingListener, it will be notified before any
     * FlexClientAttributeListeners are notified.
     *
     * @param listener The listener to add.
     */
    public void addClientAttributeListener(FlexClientAttributeListener listener)
    {       
        if (listener != null)
        {
            checkValid();
           
            if (attributeListeners == null)
            {
                synchronized (lock)
                {
                    if (attributeListeners == null)
                        attributeListeners = new CopyOnWriteArrayList();
                }
            }

            attributeListeners.addIfAbsent(listener);
        }
    }   
   
    /**
     * Adds a destroy listener that will be notified when the FlexClient
     * is destroyed. Listeners are notified after all attributes
     * have been unbound from the FlexClient and any FlexClientBindingListeners
     * and FlexClientAttributeListeners have been notified.
     *
     * @see flex.messaging.client.FlexClientListener
     *
     * @param listener The listener to add.
     */
    public void addClientDestroyedListener(FlexClientListener listener)
    {       
        if (listener != null)
        {
            checkValid();
           
            if (destroyedListeners == null)
            {
                synchronized (lock)
                {
                    if (destroyedListeners == null)
                        destroyedListeners = new CopyOnWriteArrayList();
                }
            }
           
            destroyedListeners.addIfAbsent(listener);
        }
    }
   
    /**
     * Returns the attribute bound to the specified name for the FlexClient, or null
     * if no attribute is bound under the name.
     *
     * @param name The name the attribute is bound to.
     * @return The attribute bound to the specified name.
     */
    public Object getAttribute(String name)
    {                    
        synchronized (lock)
        {
            checkValid();
           
            updateLastUse();
           
            return (attributes == null) ? null : attributes.get(name);
        }
    }
   
    /**
     * Returns a snapshot of the names of all attributes bound to the FlexClient.
     *
     * @return A snapshot of the names of all attributes bound to the FlexClient.
     */
    public Enumeration getAttributeNames()
    {       
        synchronized (lock)
        {
            checkValid();
           
            updateLastUse();
           
            if (attributes == null)
                return Collections.enumeration(Collections.EMPTY_LIST);
               
            // 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(attributes.keySet()));
        }
    }
   
    /**
     * @exclude
     * Returns the push handler registered with the FlexClient with the supplied
     * endpoint id, or null if no push handler was registered with the FlexClient
     * for that endpoint.
     * 
     * @return The push handler registered with the FlexClient with the supplied
     * endpoint id, or null if no push handler was registered with the FlexClient
     * for that endpoint.
     */
    public EndpointPushHandler getEndpointPushHandler(String endpointId)
    {
        synchronized (lock)
        {       
            if (endpointPushHandlers != null && endpointPushHandlers.containsKey(endpointId))
                return (EndpointPushHandler)endpointPushHandlers.get(endpointId);
            return null;
        }
    } 
   
    /**
     * Override {@link flex.messaging.util.TimeoutAbstractObject#getLastUse()} to make timeout
     * dependent upon FlexClient inactivity but also upon the presence of an active push-enabled session,
     * async or waited poll, or registered endpoint push handler (all of which indicate that a client has
     * an active, open connection to the server).
     *
     * @return The 'last use' timestamp for the FlexClient, which may be the current system time if the FlexClient
     *         has been idle but an open connection from the client to the server exists.
     */
    public long getLastUse()
    {
        synchronized (lock)
        {
            long currentLastUse = super.getLastUse();
            long idleTime = System.currentTimeMillis() - currentLastUse;
            if (idleTime < flexClientManager.getFlexClientTimeoutMillis())
                return currentLastUse; // Not timed out; this will trigger the timeout to be rescheduled.
           
            if (!outboundQueues.isEmpty())
            {
                EndpointQueue queue = null;
                for (Iterator iter = outboundQueues.values().iterator(); iter.hasNext();)
                {
                    queue = (EndpointQueue)iter.next();
                    if (queue.pushSession != null)
                        return System.currentTimeMillis();
                    if (queue.asyncPoll != null)
                        return System.currentTimeMillis();
                    if (endpointPushHandlers != null && endpointPushHandlers.containsKey(queue.endpointId))
                        return System.currentTimeMillis();
                }
            }
           
            // And check for an associated session performing a long poll based on wait()/notify() for this FlexClient.
            for (Iterator iter = sessions.iterator(); iter.hasNext();)
            {
                FlexSession session = (FlexSession)iter.next();
                if (session.waitMonitor != null)
                {
                    for (EndpointQueue queue : session.waitMonitor.values())
                    {
                        if (queue.flexClient.equals(this))
                            return System.currentTimeMillis();
                    }
                }
            }
           
            return currentLastUse; // Allow the FlexClient to timeout.
        }          
    }
       
    /**
     * @exclude
     *
     * Returns the principal associated with this client.  If the client has not
     * authenticated the principal will be null.  Should only be called from FlexContext
     * and only if perClientAuthentication is used.  Not available to users.
     *
     * @return The principal associated with the session.
     */
    public Principal getUserPrincipal()
    {       
        synchronized (lock)
        {
            checkValid();
            return userPrincipal;
        }
    }

    /**
     * @exclude
     *
     * Should only be called from FlexContext and only if perClientAuthentication is used. 
     * Not available to users.
     *
     * @param userPrincipal The principal to associate with the session.
     */
    public void setUserPrincipal(Principal userPrincipal)
    {       
        synchronized (lock)
        {
            checkValid();
            this.userPrincipal = userPrincipal;
        }
    }   
   
    /**
     * Invalidates the FlexClient.
     */
    public void invalidate()
    {
        synchronized (lock)
        {
            if (!valid || invalidating)
                return; // Already shutting down.
                       
            invalidating = true; // This thread gets to shut the FlexClient down.
            flexClientManager.removeFlexClient(this);
            cancelTimeout();
        }
       
        // Unregister from all FlexSessions.
        if (!sessions.isEmpty())
        {
            for (Iterator iter = sessions.iterator(); iter.hasNext();)
                unregisterFlexSession((FlexSession)iter.next());
        }       
       
        // Invalidate associated MessageClient subscriptions.
        if (messageClients != null && !messageClients.isEmpty())
        {
            for (Iterator iter = messageClients.iterator(); iter.hasNext();)
            {
                MessageClient messageClient = (MessageClient)iter.next();
                messageClient.removeMessageClientDestroyedListener(this);
                messageClient.invalidate();
            }
            messageClients.clear();
        }             
       
        // Notify destroy listeners that we're shutting the FlexClient down.
        if (destroyedListeners != null && !destroyedListeners.isEmpty())
        {
            for (Iterator iter = destroyedListeners.iterator(); iter.hasNext();)
            {
                ((FlexClientListener)iter.next()).clientDestroyed(this);               
            }
            destroyedListeners.clear();
        }       
       
        // Unbind all attributes.
        if (attributes != null && !attributes.isEmpty())
        {
            Object[] keys = attributes.keySet().toArray();
            for (int i = 0; i < keys.length; ++i)
                removeAttribute((String)keys[i]);              
        }
       
        // Close any registered push handlers.
        if (endpointPushHandlers != null && !endpointPushHandlers.isEmpty())
        {
            for (Iterator iter = endpointPushHandlers.values().iterator(); iter.hasNext();)
            {
                ((EndpointPushHandler)iter.next()).close(true /* notify Channel of disconnect */);
            }
            endpointPushHandlers = null;
        }
           
        synchronized (lock)
        {
            valid = false;
            invalidating = false;
        }

        if (Log.isDebug())
            Log.getLogger(FLEX_CLIENT_LOG_CATEGORY).debug("FlexClient with id '" + this.id + "' has been invalidated.");           
    }
   
    /**
     * Returns true if the FlexClient is valid; false if it has been invalidated.
     *
     * @return true if the FlexClient is valid; otherwise false.
     */
    public boolean isValid()
    {
        synchronized (lock)
        {
            return valid;
        }
    }
   
    /**
     * Returns a snapshot of the FlexSessions associated with the FlexClient
     * when this method is invoked.
     * This list is not guaranteed to remain consistent with the actual list
     * of active FlexSessions associated with the FlexClient over time.
     *
     * @return A snapshot of the current list of FlexSessions associated with the FlexClient.
     */
    public List getFlexSessions()
    {
        List currentSessions = null;
        synchronized (lock)
        {
            checkValid();

            updateLastUse();
           
            currentSessions = new ArrayList(sessions); // Make a copy of the current list to return.
        }
        return currentSessions;
    }
   
    /**
     * @return The number of sessions associated with this FlexClient.
     */
    public int getSessionCount()
    {
        int sessionCount;
        synchronized (lock)
        {
            sessionCount = (sessions != null) ? sessions.size() : 0; // Make a copy of the current list to return.
        }
        return sessionCount;
    }
   
    /**
     * @return The number of subscriptions associated with this FlexClient.
     */
    public int getSubscriptionCount()
    {
        int count = 0;
        synchronized (lock)
        {
           
            if (messageClients != null && !messageClients.isEmpty())
            {
                for (Iterator iter = messageClients.iterator(); iter.hasNext();)
                {
                    MessageClient messageClient = (MessageClient)iter.next();
                    count += messageClient.getSubscriptionCount();
                }
            }
           
        }
        return count;
    }
   
    /**
     * Returns a snapshot of the MessageClients (subscriptions) associated with the FlexClient
     * when this method is invoked.
     * This list is not guaranteed to remain consistent with the actual list
     * of active MessageClients associated with the FlexClient over time.
     *
     * @return A snapshot of the current list of MessageClients associated with the FlexClient.
     */
    public List getMessageClients()
    {
        List currentMessageClients = null;
        synchronized (lock)
        {
            checkValid();
           
            updateLastUse();
           
            currentMessageClients = (messageClients != null) ? new ArrayList(messageClients) // Make a copy of the current list to return.
                                                             : new ArrayList(); // Return an empty list.
        }
        return currentMessageClients;
    }
   
    /**
     * Returns the unique Id for the FlexClient.
     *
     * @return The unique Id for the FlexClient.
     */
    public String getId()
    {
        return id;
    }
   
    /**
     * @exclude
     * Implements TimeoutCapable.
     * Determine the time, in milliseconds, that this object is allowed to idle
     * before having its timeout method invoked.
     */
    public long getTimeoutPeriod()
    {
        return flexClientManager.getFlexClientTimeoutMillis();
    }
   
    /**
     * @exclude
     * Implements MessageClientListener.
     * Handling created events is a no-op.
     *
     * @param messageClient The new MessageClient.
     */
    public void messageClientCreated(MessageClient messageClient) {}
   
    /**
     * @exclude
     * Implements MessageClientListener.
     * Notification that an associated FlexSession was destroyed.
     *
     * @param messageClient The MessageClient that was destroyed.
     */
    public void messageClientDestroyed(MessageClient messageClient)
    {
        unregisterMessageClient(messageClient);
    }
   
    /**
     * @exclude
     * Poll for outbound messages for the FlexClient.
     * This method is only invoked by internal code while processing a client poll request; it
     * is not intended for general public use.
     * Poll requests that trigger this method come from client-side polling channels and the request
     * is not specific to a single Consumer/MessageClient instance so process any queued messages for
     * the specified endpoint across all subscriptions.
     *
     * @param endpointId The Id of the endpoint that received the poll request.
     * @return The flush result including messages to return in the poll response and
     *         an optional wait time for the next poll/flush.
     */
    public FlushResult poll(String endpointId)
    {
        FlushResult flushResult = null;
        synchronized (lock)
        {
            checkValid();
           
            EndpointQueue queue = (EndpointQueue)outboundQueues.get(endpointId);
           
            if (queue != null)
            {
                if (!queue.messages.isEmpty())
                    flushResult = internalFlush(queue);
                // Else, flushResult remains null.
            }
            else
            {
                throwNotSubscribedException(endpointId);
            }
        }
        return flushResult;
    }
   
    /**
     * @exclude
     * Poll for outbound messages for the FlexClient and if no messages are available
     * immediately, store a reference to the passed async handler and call back when messages arrive.
     *
     * @param endpointId The Id of the endpoint that received the poll request.
     * @param handler The handler to callback when messages arrive.
     * @param waitIntervalMillis The wait interval in milliseconds for the poll to wait for data to arrive
     *        before returning an empty poll response.
     *       
     * @return A <tt>TimeoutAbstractObject</tt> representing the asynchronous poll, or <code>null</code>
     *         if the poll request was handled immediately because data was available to return.
     */
    public TimeoutAbstractObject pollAsync(String endpointId, AsyncPollHandler handler, long waitIntervalMillis)
    {
        EndpointQueue queue;
        TimeoutAbstractObject asyncPollTask = null;
        synchronized (lock)
        {
            checkValid();
           
            queue = (EndpointQueue)outboundQueues.get(endpointId);
           
            // If the queue exists and is not empty, flush immediately.
            if (queue != null)
            {
                if (!queue.messages.isEmpty())
                {
                    handler.asyncPollComplete(internalFlush(queue));
                }
                else // Set up an async long-poll.
                {
                    // Avoid monopolizing user agent connections.
                    FlexSession session = FlexContext.getFlexSession();
                    synchronized (session)
                    {
                        if (session.asyncPollMap != null)
                        {
                            AsyncPollWithTimeout parkedPoll = session.asyncPollMap.get(endpointId);
                            if (parkedPoll != null)
                            {
                                // If the poll is from the same client for this endpoint, treat it as a no-op.
                                if (parkedPoll.getFlexClient().equals(this))
                                {
                                    PollFlushResult result = new PollFlushResult();
                                    result.setClientProcessingSuppressed(true);
                                    handler.asyncPollComplete(result);
                                }
                                else // If the poll is for a different client on the same session, swap their waits.
                                {
                                    PollFlushResult result = new PollFlushResult();
                                    result.setAvoidBusyPolling(true);
                                    completeAsyncPoll(parkedPoll, result);
                                }
                            }
                        }
                        AsyncPollWithTimeout asyncPoll = new AsyncPollWithTimeout(this, session, queue, handler, waitIntervalMillis, endpointId);
                        synchronized (session)
                        {
                            if (session.asyncPollMap == null)
                                session.asyncPollMap = new HashMap<String, AsyncPollWithTimeout>();
                            session.asyncPollMap.put(endpointId, asyncPoll);
                        }
                        queue.asyncPoll = asyncPoll; 
                        asyncPollTask = asyncPoll;
                    }
                }                   
            }
            else
            {
                // The queue was null; let the client know that there are no active subscriptions.
                throwNotSubscribedException(endpointId);
            }           
        }
        return asyncPollTask;
    }
   
    /**
     * @exclude
     * Poll for outbound messages for the FlexClient and if no messages are available
     * immediately, put processing into a wait state until messages arrive.
     * This method is only invoked by internal code while processing a client poll request; it
     * is not intended for general public use.
     * Poll requests that trigger this method come from client-side polling channels and the request
     * is not specific to a single Consumer/MessageClient instance so process any queued messages for
     * the specified endpoint across all subscriptions.
     *
     * @param endpointId The Id of the endpoint that received the poll request.
     * @param session The FlexSession associated with this waitable poll request.
     * @param listener The listener to notify before a wait begins and as soon as one completes.
     * @param waitIntervalMillis The maximum amount of time to wait for messages in milliseconds.
     * @return The flush result including messages to return in the poll response and
     *         an optional wait time for the next poll/flush.
     */
    public FlushResult pollWithWait(String endpointId, FlexSession session, PollWaitListener listener, long waitIntervalMillis)
    {
        EndpointQueue queue;
        synchronized (lock)
        {
            checkValid();
                    
            queue = (EndpointQueue)outboundQueues.get(endpointId);
           
            // If the queue exists and is not empty there's no reason to wait; flush immediately.
            if (queue != null && !queue.messages.isEmpty())
                return internalFlush(queue);               
        }

        // The queue exists but it was empty; we can try to wait for messages.
        if (queue != null)
        {           
            synchronized (session)
            {
                // Set up the waitMonitor on the session; this is a reference to the queue that the
                // current poll request targets and we use it as a wait/notify monitor.
                // This also lets us prevent busy polling cycles from a single client. If we already have a waited
                // poll request a subsequent poll request is treated as a no-op.
                if (session.waitMonitor != null)
                {
                    final EndpointQueue waitingQueue = session.waitMonitor.get(endpointId);
                    if (waitingQueue != null)
                    {
                        // If the poll is from the same client swf, and the same endpoint, treat it as a no-op poll.
                        if (waitingQueue.flexClient.equals(this))
                        {
                            PollFlushResult result = new PollFlushResult();
                            result.setClientProcessingSuppressed(true);
                            return result;
                        }
                    }
                }
                else
                {
                    session.waitMonitor = new HashMap<String, EndpointQueue>();
                }

                // Set the waitMonitor for the session to the queue
                // for this poll request before releasing the lock.
                session.waitMonitor.put(endpointId, queue);
            }
                       
            // Now that the session references the wait monitor this thread will use to wait we can enter
            // the wait state.   
            // -1 wait-interval actually means wait until notified.           
            waitIntervalMillis = (waitIntervalMillis == -1) ? 0 : waitIntervalMillis;
            String threadName = Thread.currentThread().getName();
            try
            {
                boolean didWait = false;
                boolean avoidBusyPolling = false;
                synchronized (queue)
                {      
                    // If the message queue is still empty, wait for a message to be added before invoking flush.
                    if (queue.messages.isEmpty())
                    {
                        if (Log.isDebug())
                            Log.getLogger(FLEX_CLIENT_LOG_CATEGORY).debug("Poll wait thread '" + threadName + "' for FlexClient with id '" + this.id +
                                "' is waiting for new messages to arrive.");
                       
                        didWait = true;
                       
                        // Tag thread name during the wait.
                        Thread currentThread = Thread.currentThread();
                        currentThread.setName(threadName + POLL_WAIT_THREAD_NAME_EXTENSION);
                       
                        if (listener != null)
                            listener.waitStart(queue);
                       
                        queue.wait(waitIntervalMillis);
                       
                        // Reset thread name now that the wait is over.
                        currentThread.setName(threadName);                       
                       
                        if (listener != null)
                            listener.waitEnd(queue);
                       
                        if (queue.avoidBusyPolling)
                        {
                            avoidBusyPolling = true;
                            queue.avoidBusyPolling = false;
                        }
                    }
                }
               
                synchronized (session)
                {
                    if (session.waitMonitor != null)
                    {
                        session.waitMonitor.remove(endpointId);
                    }
                }
               
                if (Log.isDebug())
                {
                    if (didWait)                       
                        Log.getLogger(FLEX_CLIENT_LOG_CATEGORY).debug("Poll wait thread '" + threadName + "' for FlexClient with id '" + this.id +
                            "' is done waiting for new messages to arrive and is flushing the outbound queue.");
                    else
                        Log.getLogger(FLEX_CLIENT_LOG_CATEGORY).debug("Poll wait thread '" + threadName + "' for FlexClient with id '" + this.id +
                            "' didn't need to wait and is flushing the outbound queue.");
                }

                // We need to hold the FlexClient lock to invoke flush.
                FlushResult result;
                synchronized (lock)
                {                   
                    result = internalFlush(queue);                     
                }                   
                if (avoidBusyPolling)
                {
                    PollFlushResult swappedPollResult = new PollFlushResult();
                    if (result != null)
                    {
                        swappedPollResult.setMessages(result.getMessages());
                        swappedPollResult.setNextFlushWaitTimeMillis(result.getNextFlushWaitTimeMillis());
                    }
                    swappedPollResult.setAvoidBusyPolling(true);
                    result = swappedPollResult;
                }
                return result;
            }
            catch (InterruptedException e)
            {
                if (Log.isWarn())
                    Log.getLogger(FLEX_CLIENT_LOG_CATEGORY).warn("Poll wait thread '" + threadName + "' for FlexClient with id '" + this.id +
                            "' could not finish waiting for new messages to arrive " +
                            "because it was interrupted: " + e.toString());
            }
        }
        else
        {
            // The queue was null; let the client know that there are no active subscriptions.
            throwNotSubscribedException(endpointId);
        }
        return null;
    }
   
    /**
     * @exclude
     * Poll for outbound messages for a specific MessageClient/Consumer.
     * This overload of poll() is only invoked when handling a Consumer.receive() request.
     *
     * @param client The specific MessageClient instance to poll for messages for.
     * @return The flush result including messages to return in the poll response.
     *         The nextFlushWaitTimeMillis value is always forced to a value of 0 because
     *         Consumer.receive() calls are driven by client code and this setting has no meaning.
     */
    public FlushResult poll(MessageClient client)
    {
        FlushResult flushResult = null;
        synchronized (lock)
        {
            checkValid();
           
            String endpointId = client.getEndpointId();
            EndpointQueue queue = (EndpointQueue)outboundQueues.get(endpointId);
            if (queue != null)
            {               
                try
                {                   
                    flushResult = internalFlush(queue, client);
                }
                catch (RuntimeException e)
                {
                    if (Log.isError())
                        Log.getLogger(FLEX_CLIENT_LOG_CATEGORY).error("Failed to flush an outbound queue for MessageClient '" + client.getClientId() + "' for FlexClient '" + getId() + "'.", e);
                    throw e;
                }
                if (flushResult != null)
                    flushResult.setNextFlushWaitTimeMillis(0); // Force to 0.
            }
            else
            {
                throwNotSubscribedException(endpointId);
            }
        }
        return flushResult;
    }
   
    /**
     * @exclude
     * Push a message to the FlexClient.
     * The message is added to the outbound queue of messages for the client and
     * will be pushed if possible or retrieved via a client poll request.
     *
     * @param message The Message to push.
     * @param messageClient The MessageClient subscription that this message targets.
     */
    public void push(Message message, MessageClient messageClient)
    {       
        synchronized (lock)
        {
            // If the FlexClient is not valid, skip further processing.
            if (!valid)
                return;
           
            updateLastUse();
           
            // Route this message to the proper per-endpoint outbound queue.
            EndpointQueue queue = (EndpointQueue)outboundQueues.get(messageClient.getEndpointId());
           
            // This queue may be null if all corresponding subscriptions have been invalidated.
            // If the queue has been shut down, we don't need to try to deliver the message.
            boolean empty;
            if (queue != null)
            {
                synchronized (queue) // To protect the list during the add and allow for notification.
                {
                    // Let the processor add the message to the queue.
                    try
                    {
                        queue.processor.add(queue.messages, message);
                        empty = queue.messages.isEmpty();                       
                       
                        if (Log.isDebug())
                            Log.getLogger(LogCategories.MESSAGE_GENERAL).debug(
                               "Queuing message: " + message.getMessageId() +
                               StringUtils.NEWLINE +
                               "  to send to MessageClient: " + messageClient.getClientId() +
                               StringUtils.NEWLINE +
                               "  for FlexClient: " + messageClient.getFlexClient().getId() +
                               StringUtils.NEWLINE +
                               "  via endpoint: " + queue.endpointId +
                               StringUtils.NEWLINE +
                               "  client outbound queue size: " + queue.messages.size());
                    }
                    catch (RuntimeException e)
                    {
                        if (Log.isError())
                            Log.getLogger(FLEX_CLIENT_LOG_CATEGORY).error("Failed to add a message to an outbound queue for FlexClient '" + getId() + "'.", e);
                        throw e;
                    }
                    // And notify any threads that may be in a poll wait state.
                    if (!empty)
                        queue.notifyAll();
                }
               
                if (!empty)
                {
                    if (queue.asyncPoll != null)
                    {
                        completeAsyncPoll(queue.asyncPoll, internalFlush(queue));
                    }
                    else if (!empty && queue.flushTask == null &&
                            (queue.pushSession != null || (endpointPushHandlers != null && endpointPushHandlers.containsKey(queue.endpointId))))
                    {
                        // If a delayed flush is not scheduled and we have a push-enabled session associated with the queue
                        // or a push-enabled endpoint, try a direct push to the client.
                        directFlush(queue);
                    }
                }
            }
        }
    }   
   
    /**
     * @exclude
     * Registers an <tt>EndpointPushHandler</tt> for the specified endpoint to handle pushing messages
     * to remote clients.
     *
     * @param handler The <tt>EndpointPushHandler</tt> to register.
     * @param endpointId The endpoint to register for.
     */
    public void registerEndpointPushHandler(EndpointPushHandler handler, String endpointId)
    {
        synchronized (lock)
        {
            if (endpointPushHandlers == null)
                endpointPushHandlers = new HashMap(1);
           
            if (endpointPushHandlers.containsKey(endpointId))
            {
                MessageException me = new MessageException();
                me.setMessage(ENDPOINT_PUSH_HANDLER_ALREADY_REGISTERED, new Object[] {getId(), endpointId});
                throw me;
            }
           
            endpointPushHandlers.put(endpointId, handler);
        }
    }    
   
    /**
     * @exclude
     * Used internally to associate a FlexSession with this FlexClient.
     *
     * @param session The FlexSession to associate with this FlexClient.
     */
    public void registerFlexSession(FlexSession session)
    {
        if (sessions.addIfAbsent(session))
        {
            session.addSessionDestroyedListener(this);
            session.registerFlexClient(this);
        }
    }
   
    /**
     * @exclude
     * Used internally to associate a MessageClient with this FlexClient.
     *
     * @param messageClient The MessageClient to associate with this FlexClient.
     */
    public void registerMessageClient(MessageClient messageClient)
    {
        if (messageClients == null)
        {
            synchronized (lock)
            {
                if (messageClients == null)
                    messageClients = new CopyOnWriteArrayList();
            }
        }
       
        if (messageClients.addIfAbsent(messageClient))
        {
            messageClient.addMessageClientDestroyedListener(this);
            String endpointId = messageClient.getEndpointId();
            // Manage the outbound queue this MessageClient's subscription(s) will use
            // and associate the MessageClient with an EndpointPushHandler if one exists for the
            // endpoint the subscription was made over; this allows the shut-down of a
            // push connection to invalidate any subscriptions that are using it.
            synchronized (lock)
            {
                getOrCreateEndpointQueueAndRegisterSubscription(messageClient, endpointId);
                if (endpointPushHandlers != null)
                {
                    EndpointPushHandler handler = (EndpointPushHandler)endpointPushHandlers.get(endpointId);
                    if (handler != null)
                        handler.registerMessageClient(messageClient);
                }
            }
        }
    }  

    /**
     * Removes the attribute bound to the specified name for the FlexClient.
     *
     * @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();         

            updateLastUse();
           
            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 FlexClient attribute listener.
     *
     * @param listener The listener to remove.
     */
    public void removeClientAttributeListener(FlexClientAttributeListener listener)
    {  
        // No need to check validity; removing a listener is always ok.
        if (listener != null && attributeListeners != null)
            attributeListeners.remove(listener);
    }   
   
    /**
     * Removes a FlexClient destroyed listener.
     *
     * @see flex.messaging.client.FlexClientListener
     *
     * @param listener The listener to remove.
     */
    public void removeClientDestroyedListener(FlexClientListener listener)
    {      
        // No need to check validity; removing a listener is always ok.
        if (listener != null && destroyedListeners != null)
            destroyedListeners.remove(listener);
    }

    /**
     * @exclude
     * Implements FlexSessionListener interface.
     * Notification that a FlexSession was created.
     * This is a no-op because the FlexClient is never added as a static FlexSession created listener
     * but this method is required by the interface. We only listen for the destroyed event from
     * associated FlexSessions.
     *
     * @param session The FlexSession that was created.
     */
    public void sessionCreated(FlexSession session) {}   
   
    /**
     * @exclude
     * Implements FlexSessionListener interface.
     * Notification that an associated FlexSession was destroyed.
     *
     * @param session The FlexSession that was destroyed.
     */
    public void sessionDestroyed(FlexSession session)
    {
        unregisterFlexSession(session);
    }
   
    /**
     * Binds an attribute value for the FlexClient 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 set 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();
           
            updateLastUse();
           
            if (attributes == null)
                attributes = new HashMap();
           
            oldValue = attributes.put(name, value);                     
        }
       
        if (oldValue == null)
        {
            notifyAttributeBound(name, value);
            notifyAttributeAdded(name, value);
        }
        else
        {
            notifyAttributeUnbound(name, oldValue);
            notifyAttributeReplaced(name, oldValue);
            notifyAttributeBound(name, value);         
        }
    }
   
    /**
     * @exclude
     * Implements TimeoutCapable.
     * Inform the object that it has timed out.
     */
    public void timeout()
    {
        invalidate();
    }    
   
    /**
     * @exclude
     * Unregisters an <tt>EndpointPushHandler</tt> from the specified endpoint.
     *
     * @param handler The <tt>EndpointPushHandler</tt> to unregister.
     * @param endpointId The endpoint to unregister from.
     */
    public void unregisterEndpointPushHandler(EndpointPushHandler handler, String endpointId)
    {
        synchronized (lock)
        {
            if (endpointPushHandlers == null)
                return; // No-op.
           
            if (endpointPushHandlers.get(endpointId).equals(handler))
                endpointPushHandlers.remove(endpointId);
        }
    }   
   
    /**
     * @exclude
     * Used internally to disassociate a FlexSession from this FlexClient.
     *
     * @param session The FlexSession to disassociate from this FlexClient.
     */
    public void unregisterFlexSession(FlexSession session)
    {
        if (sessions.remove(session))
        {
            session.removeSessionDestroyedListener(this);
            session.unregisterFlexClient(this);
            // Once all client sessions/connections terminate; shut down.
            if (sessions.isEmpty())
                invalidate();
        }       
    }
   
    /**
     * @exclude
     * Used internally to disassociate a MessageClient (subscription) from a FlexClient.
     *
     * @param messageClient The MessageClient to disassociate from the FlexClient.
     */
    public void unregisterMessageClient(MessageClient messageClient)
    {
        if (messageClients != null)
        {
            if (messageClients.remove(messageClient))
            {
                messageClient.removeMessageClientDestroyedListener(this);
                String endpointId = messageClient.getEndpointId();
                // Manage the outbound queue that this subscription uses.
                synchronized (lock)
                {
                    EndpointQueue queue = (EndpointQueue)outboundQueues.get(endpointId);
                    if (queue != null)
                    {                       
                        // Decrement the ref count of MessageClients using this queue.
                        queue.messageClientRefCount--;
                       
                        // If we're not attempting to notify the remote client that this MessageClient has
                        // been invalidated, remove any associated messages from the queue.
                        if (!messageClient.isAttemptingInvalidationClientNotification())
                        {
                            Object messageClientId = messageClient.getClientId();
                            for (Iterator iter = queue.messages.iterator(); iter.hasNext(); )
                            {
                                Message message = (Message)iter.next();
                                if (message.getClientId().equals(messageClientId))
                                    iter.remove();
                            }
                        }
                       
                        // If no active subscriptions require the queue, clean it up if possible.
                        if (queue.messageClientRefCount == 0)
                        {
                            if (queue.messages.isEmpty() || messageClient.isClientChannelDisconnected())
                            {                               
                                if (queue.asyncPoll != null) // Close out async long-poll if one is registered.
                                {
                                    FlushResult flushResult = internalFlush(queue);
                                    // If the MessageClient isn't attempting client notification, override
                                    // and do so in this case to suppress the next poll request from the remote client
                                    // which will fail triggering an unnecessary channel disconnect on the client.
                                    if (!messageClient.isAttemptingInvalidationClientNotification())
                                    {
                                        CommandMessage msg = new CommandMessage();
                                        msg.setClientId(messageClient.getClientId());
                                        msg.setOperation(CommandMessage.SUBSCRIPTION_INVALIDATE_OPERATION);
                                        List messages = flushResult.getMessages();
                                        if (messages == null)
                                            messages = new ArrayList(1);
                                        messages.add(msg);
                                    }
                                    completeAsyncPoll(queue.asyncPoll, flushResult);
                                }
                               
                                // Remove the empty, unused queue.
                                outboundQueues.remove(endpointId);                              
                            }
                            // Otherwise, the queue is being used by a polling client or contains messages
                            // that will be written by a delayed flush.
                            // Leave it in place. Once the next poll request or delayed flush occurs the
                            // queue will be cleaned up at that point. See internalFlush() and shutdownQueue().
                        }
                       
                        // Make sure to notify any threads waiting on this queue that may be associated
                        // with the subscription that's gone away.
                        synchronized (queue)
                        {
                            queue.notifyAll();
                        }
                    }
                    // And if this subscription was associated with an endpoint push handler, unregister it.
                    if (endpointPushHandlers != null)
                    {
                        EndpointPushHandler handler = (EndpointPushHandler)endpointPushHandlers.get(endpointId);
                        if (handler != null)
                            handler.unregisterMessageClient(messageClient);
                    }
                }
            }
        }
    }   
   
    //--------------------------------------------------------------------------
    //
    // Package Private Methods
    //
    //--------------------------------------------------------------------------
   
    /**
     * Invoked by FlexClientManager after this new FlexClient has been constructed and
     * is fully configured.
     */
    void notifyCreated()
    {
        if (!createdListeners.isEmpty())
        {
            // CopyOnWriteArrayList is iteration-safe from ConcurrentModificationExceptions.
            for (Iterator iter = createdListeners.iterator(); iter.hasNext();)
                ((FlexClientListener)iter.next()).clientCreated(this);
        }
    }
   
    //--------------------------------------------------------------------------
    //
    // Private Methods
    //
    //--------------------------------------------------------------------------
   
    /**
     * Invoked to clean up a timed out or closed async poll.
     *
     * @param asyncPoll The async poll to complete.
     * @param result The FlushResult for the poll response.
     */
    private void completeAsyncPoll(AsyncPollWithTimeout asyncPoll, FlushResult result)
    {
        synchronized (lock)
        {
            asyncPoll.cancelTimeout();
            EndpointQueue queue = asyncPoll.getEndpointQueue();
            if (queue.asyncPoll.equals(asyncPoll))
                queue.asyncPoll = null;
            FlexSession session = asyncPoll.getFlexSession();
            synchronized (session)
            {
                if (session.asyncPollMap != null)
                    session.asyncPollMap.remove(asyncPoll.getEndpointId());
            }
            asyncPoll.getHandler().asyncPollComplete(result);
        }
    }
   
    /**
     * Invoked to flush queued outbound messages to a client directly using a session
     * that supports real-time push.
     * Called by push() or delayed flush tasks for push-enabled sessions/connections.
     */
    private void directFlush(EndpointQueue queue)
    {
        synchronized (lock)
        {
            // No need to invoke flush if the FlexClient has been invalidated.
            if (!valid)
                return;                      
           
            // If this invocation is a callback from a flush task, null out the task ref on
            // the queue to allow a subsequent delayed flush to be scheduled.
            if (queue.flushTask != null)
                queue.flushTask = null;
             
            FlushResult flushResult = internalFlush(queue);
            if (flushResult != null)
            {
                // Pass any messages that are ready to flush off to the network layer.
                List messages = flushResult.getMessages();
                if (messages != null && !messages.isEmpty())
                {
                    // Update last use because we're writing back to the client.
                    updateLastUse();
                   
                    if (queue.pushSession != null)
                    {
                        for (Iterator iter = messages.iterator(); iter.hasNext();)
                            queue.pushSession.push((Message)iter.next());
                    }
                    else if (endpointPushHandlers != null)
                    {
                        EndpointPushHandler handler = (EndpointPushHandler)endpointPushHandlers.get(queue.endpointId);
                        handler.pushMessages(messages);
                    }
                }

                // Schedule a delayed flush if necessary.
                int flushWaitTime = flushResult.getNextFlushWaitTimeMillis();
                if (flushWaitTime > 0)
                {
                    // Set up and schedule the delayed flush task.
                    queue.flushTask = new FlexClientFlushTask(queue);
                    flexClientManager.scheduleFlush(queue.flushTask, flushWaitTime);
                }
                else
                {
                    // Don't delay the next flush.
                    return;
                }               
            }
        }
    }   
   
    /**
     * Utility method to initialize an EndpointQueue (if necessary) and associate a subscription (MessageClient) with it.
     */
    private EndpointQueue getOrCreateEndpointQueueAndRegisterSubscription(MessageClient messageClient, String endpointId)
    {
        EndpointQueue newQueue = null;
        if (!outboundQueues.containsKey(endpointId))
        {
            newQueue = new EndpointQueue();
            newQueue.flexClient = this;
            newQueue.endpointId = endpointId;
            newQueue.messages = new ArrayList(); /* Default size of 10 is fine */
            FlexSession session = messageClient.getFlexSession();
            if (session.isPushSupported())
                newQueue.pushSession = session;
            newQueue.processor = flexClientManager.createOutboundQueueProcessor(this, endpointId);
            newQueue.messageClientRefCount = 1;
           
            outboundQueues.put(endpointId, newQueue);
        }
        else
        {
            newQueue = (EndpointQueue)outboundQueues.get(endpointId);
            newQueue.messageClientRefCount++;
            // Resubscribes as a result of network connectivity issues may arrive over the same
            // endpoint but use a new session.
            FlexSession session = messageClient.getFlexSession();
            if (session.isPushSupported())
                newQueue.pushSession = session;
        }
        return newQueue;
    }
   
    /**
     * Utility method to flush the outbound queue and log any problems.
     * Any exceptions are logged and then rethrown.
     */
    private FlushResult internalFlush(EndpointQueue queue)
    {
        return internalFlush(queue, null);
    }
   
    /**
     * Utility method to flush the outbound queue and log any problems.
     * If a specific client is passed, we need to invoke a client-specific flush.
     * If the passed client is null, we do a general flush of the queue.
     * Any exceptions are logged and then rethrown.
     */
    private FlushResult internalFlush(EndpointQueue queue, MessageClient client)
    {
        FlushResult flushResult = null;
        try
        {           
            flushResult = (client == null) ? queue.processor.flush(queue.messages)
                                           : queue.processor.flush(client, queue.messages);  
            shutdownQueue(queue);
           
            // Update the last-use timestamp if we're writing messages back to the client.
            List messages = (flushResult != null) ? flushResult.getMessages() : null;
            if (messages != null && !messages.isEmpty())
                updateLastUse();
        }
        catch (RuntimeException e)
        {
            if (Log.isError())
                Log.getLogger(FLEX_CLIENT_LOG_CATEGORY).error("Failed to flush an outbound queue for FlexClient '" + getId() + "'.", e);
            throw e;
        }
        return flushResult;
    }
   
    /**
     * Notify attribute listeners that an attribute has been added.
     *
     * @param name The name of the attribute.
     *
     * @param value The new value of the attribute.
     */
    private void notifyAttributeAdded(String name, Object value)
    {
        if (attributeListeners != null && !attributeListeners.isEmpty())
        {
            FlexClientBindingEvent event = new FlexClientBindingEvent(this, name, value);
            // CopyOnWriteArrayList is iteration-safe from ConcurrentModificationExceptions.
            for (Iterator iter = attributeListeners.iterator(); iter.hasNext();)
                ((FlexClientAttributeListener)iter.next()).attributeAdded(event);
        }
    }
   
    /**
     * Notify binding listener that it has been bound to the FlexClient.
     *
     * @param name The attribute name.
     *
     * @param value The attribute that has been bound.
     */
    private void notifyAttributeBound(String name, Object value)
    {
        if ((value != null) && (value instanceof FlexClientBindingListener))
        {
            FlexClientBindingEvent bindingEvent = new FlexClientBindingEvent(this, name);
            ((FlexClientBindingListener)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.
     */
    private void notifyAttributeRemoved(String name, Object value)
    {
        if (attributeListeners != null && !attributeListeners.isEmpty())
        {
            FlexClientBindingEvent event = new FlexClientBindingEvent(this, name, value);
            // CopyOnWriteArrayList is iteration-safe from ConcurrentModificationExceptions.
            for (Iterator iter = attributeListeners.iterator(); iter.hasNext();)
                ((FlexClientAttributeListener)iter.next()).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.
     */
    private void notifyAttributeReplaced(String name, Object value)
    {
        if (attributeListeners != null && !attributeListeners.isEmpty())
        {
            FlexClientBindingEvent event = new FlexClientBindingEvent(this, name, value);
            // CopyOnWriteArrayList is iteration-safe from ConcurrentModificationExceptions.
            for (Iterator iter = attributeListeners.iterator(); iter.hasNext();)
                ((FlexClientAttributeListener)iter.next()).attributeReplaced(event);
        }
    }
   
    /**
     * Notify binding listener that it has been unbound from the FlexClient.
     *
     * @param name The attribute name.
     *
     * @param value The attribute that has been unbound.
     */
    private void notifyAttributeUnbound(String name, Object value)
    {
        if ((value != null) && (value instanceof FlexClientBindingListener))
        {
            FlexClientBindingEvent bindingEvent = new FlexClientBindingEvent(this, name);
            ((FlexClientBindingListener)value).valueUnbound(bindingEvent);
        }
    }
   
    /**
     * Utility method that tests validity and throws an exception if the instance
     * has been invalidated.
     */
    private void checkValid()
    {
        synchronized (lock)
        {
            if (!valid)
            {
                MessageException e = new MessageException();
                e.setMessage(FLEX_CLIENT_INVALIDATED);
                throw e;
            }
        }
    }
   
    /**
     * Utility method used to shutdown endpoint queues accessed via polling channels
     * that have no more active subscriptions and no more pending outbound messages.
     *
     * @param queue The queue to potentially shutdown.
     * @return true if the queue was cleaned up/removed; otherwise false.
     */
    private boolean shutdownQueue(EndpointQueue queue)
    {
        // If no more subscriptions are using the queue and it is empty, shut it down.
        if (queue.messageClientRefCount == 0 && queue.messages.isEmpty())
        {
            outboundQueues.remove(queue.endpointId);
            // Notify any threads waiting on this queue.
            synchronized (queue)
            {
                queue.notifyAll();
            }
            return true;
        }
        else
            return false;
    }
   
    /**
     * Utility method to throw a not subscribed exception back to the client
     * if they issue a poll request to an endpoint that they haven't subscribed over.
     *
     * @param endpointId The endpoint Id.
     */
    private void throwNotSubscribedException(String endpointId)
    {
        // Pre-3.1 versions of the client library did not handle URL session tokens properly
        // and may incorrectly issue a poll, after subscribing, that does not contain the proper
        // FlexClient id.
        // This scenario looks like a poll from a client that is not subscribed, but it is not,
        // and deserves a more useful error message.
        // We determine this by checking for an (orphaned) FlexClient instance associated with the
        // current session that has a subscription established through the target endpoint.
        List<FlexClient> flexClients = FlexContext.getFlexSession().getFlexClients();
        for (FlexClient otherClient : flexClients)
        {
            if (!otherClient.equals(this))
            {
                List<MessageClient> otherSubs = otherClient.getMessageClients();
                for (MessageClient otherSub : otherSubs)
                {
                    if (otherSub.getEndpointId().equals(endpointId))
                    {
                        // Throw not-subscribed exception with extra guidance.
                        FlexClientNotSubscribedException e = new FlexClientNotSubscribedException();
                        e.setMessage(10036, new Object[]{endpointId});
                        e.setCode(MessageService.NOT_SUBSCRIBED_CODE);
                        throw e;
                    }
                }
            }
        }
       
        // Throw general not-subscribed exception.
        FlexClientNotSubscribedException e = new FlexClientNotSubscribedException();
        e.setMessage(10028, new Object[]{endpointId});
        e.setCode(MessageService.NOT_SUBSCRIBED_CODE);
        throw e;
    }
   
    //--------------------------------------------------------------------------
    //
    // Inner Classes
    //
    //--------------------------------------------------------------------------
   
    /**
     * Helper class for handling async poll requests. This class allows the response for an async poll
     * to be delayed until data arrives to return to the client or the specified wait interval elapses.
     * Wait timeouts are monitored by the <tt>FlexClientManager</tt> which contains a <tt>TimeoutManager</tt>
     * instance that is started and stopped during application bootstrap and shutdown. Managing timeouts
     * locally or statically isn't a good option because they lack a useful shutdown hook that's necessary
     * in order to close down the timeout manager cleanly.
     */
    public class AsyncPollWithTimeout extends TimeoutAbstractObject
    {
        public AsyncPollWithTimeout(FlexClient flexClient, FlexSession session, EndpointQueue queue, AsyncPollHandler handler, long waitIntervalMillis, String endpointId)
        {
            this.flexClient = flexClient;
            this.session = session;
            this.queue = queue;
            this.handler = handler;           
            setTimeoutPeriod(waitIntervalMillis);
            flexClientManager.monitorAsyncPollTimeout(this);
            this.endpointId = endpointId;
        }
       
        private final FlexClient flexClient;
       
        public FlexClient getFlexClient()
        {
            return flexClient;
        }
       
        private final FlexSession session;
       
        public FlexSession getFlexSession()
        {
            return session;
        }
       
        private final EndpointQueue queue;
       
        public EndpointQueue getEndpointQueue()
        {
            return queue;
        }
       
        private final AsyncPollHandler handler;
       
        public AsyncPollHandler getHandler()
        {
            return handler;
        }
       
        private final String endpointId;
       
        public String getEndpointId()
        {
            return endpointId;
        }
       
        public void timeout()
        {
            completeAsyncPoll(this, null /* nothing to return */);
        }
    }
       
    /**
     * Helper class to flush a FlexClient's outbound queue after a specified delay when
     * a session or endpoint that supports direct push has requested that the next flush be
     * delayed.
     * Delayed flushes are handled by the <tt>FlexClientManager</tt> using a simple <tt>Timer</tt>.
     */
    class FlexClientFlushTask extends TimerTask
    {
        public FlexClientFlushTask(EndpointQueue queue)
        {
            this.queue = queue;
        }
       
        private final EndpointQueue queue;
       
        public synchronized void run()
        {
            FlexContext.setThreadLocalFlexClient(FlexClient.this);
            directFlush(queue);
            FlexContext.setThreadLocalFlexClient(null);
        }
    }
   
    /**
     * @exclude
     * Helper class that stores per-endpoint outbound queue state including:
     * <ul>
     *   <li>flexClient - The <tt>FlexClient</tt> the queue is used by.
     *   <li>messages - The outbound queue of messages for the endpoint.</li>
     *   <li>processor - The processor that handles adding messages to the queue as well as flushing
     *       them to the network.</li>
     *   <li>asyncPoll - The async poll to timeout or callback when messages arrive
     *       (null if the endpoint or session supports direct push).</li>
     *   <li>pushSession - A reference to a pushSession to use for direct writes to the
     *       client (null if the endpoint uses polling or handles push directly).</li>
     *   <li>
     *   <li>flushTask - A reference to a pending flush task that will perform a delayed flush of the queue;
     *       null if no delayed flush has been scheduled.</li>
     *   <li>messageClientRefCount - A reference count of MessageClients subcribed over this endpoint.
     *       Once all MessageClients unsubscribe this queue can be shut down.</li>
     *   <li>avoidBusyPolling - Used to signal poll result generation for the queue to avoid busy polling.</li>
     * </ul>
     */
    public static class EndpointQueue
    {
        public FlexClient flexClient;
        public String endpointId;
        public List messages;
        public FlexClientOutboundQueueProcessor processor;
        public AsyncPollWithTimeout asyncPoll;
        public FlexSession pushSession;       
        public TimerTask flushTask;
        public int messageClientRefCount;
        public boolean avoidBusyPolling;
    }   
}
TOP

Related Classes of flex.messaging.client.FlexClient

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.