Package flex.messaging.services

Source Code of flex.messaging.services.MessageService

/*************************************************************************
*
* ADOBE CONFIDENTIAL
* __________________
*
*  Copyright 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.services;

import flex.management.runtime.messaging.MessageDestinationControl;
import flex.management.runtime.messaging.services.MessageServiceControl;
import flex.messaging.Destination;
import flex.messaging.FlexContext;
import flex.messaging.MessageBroker;
import flex.messaging.MessageClient;
import flex.messaging.MessageDestination;
import flex.messaging.MessageException;
import flex.messaging.MessageRoutedNotifier;
import flex.messaging.client.FlushResult;
import flex.messaging.cluster.ClusterManager;
import flex.messaging.cluster.Cluster;
import flex.messaging.config.ServerSettings;
import flex.messaging.log.LogCategories;
import flex.messaging.log.Log;
import flex.messaging.messages.AcknowledgeMessage;
import flex.messaging.messages.AsyncMessage;
import flex.messaging.messages.CommandMessage;
import flex.messaging.messages.Message;
import flex.messaging.messages.MessagePerformanceUtils;
import flex.messaging.services.messaging.MessagingConstants;
import flex.messaging.services.messaging.RemoteSubscriptionManager;
import flex.messaging.services.messaging.SubscriptionManager;
import flex.messaging.services.messaging.Subtopic;
import flex.messaging.services.messaging.ThrottleManager.ThrottleResult;
import flex.messaging.services.messaging.adapters.MessagingAdapter;
import flex.messaging.services.messaging.selector.JMSSelector;
import flex.messaging.util.StringUtils;

import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import edu.emory.mathcs.backport.java.util.concurrent.locks.ReadWriteLock;
import edu.emory.mathcs.backport.java.util.concurrent.locks.ReentrantReadWriteLock;

/**
* The MessageService class is the Service implementation that manages point-to-point
* and publish-subscribe messaging.
*
* @author neville
*/
public class MessageService extends AbstractService implements MessagingConstants
{
    /** Log category for <code>MessageService</code>. */
    public static final String LOG_CATEGORY = LogCategories.SERVICE_MESSAGE;
    /** Log category for <code>MessageService</code> that captures message timing. */
    public static final String TIMING_LOG_CATEGORY = LogCategories.MESSAGE_TIMING;

    /** @exclude **/
    public static final String NOT_SUBSCRIBED_CODE = "Server.Processing.NotSubscribed";

    // Errors
    private static final int BAD_SELECTOR = 10550;
    private static final int NOT_SUBSCRIBED = 10551;
    private static final int UNKNOWN_COMMAND = 10552;

    private MessageServiceControl controller;

    private ReadWriteLock subscribeLock = new ReentrantReadWriteLock();

    //--------------------------------------------------------------------------
    //
    // Constructor
    //
    //--------------------------------------------------------------------------

    /**
     * Constructs an unmanaged <code>MessageService</code>.
     */
    public MessageService()
    {
        super(false);
    }

    /**
     * Constructs an <code>MessageService</code> with the indicated management.
     *
     * @param enableManagement <code>true</code> if the <code>MessageService</code>
     * is manageable; otherwise <code>false</code>.
     */
    public MessageService(boolean enableManagement)
    {
        super(enableManagement);
    }

    //--------------------------------------------------------------------------
    //
    // Initialize, validate, start, and stop methods.
    //
    //--------------------------------------------------------------------------

    public void start()
    {
        String serviceType = getClass().getName();
        ClusterManager clm = getMessageBroker().getClusterManager();

        super.start();

        /*
         * For any destinations which are not using broadcast mode,
         * we need to init the remote subscriptions.  First we send out
         * the requestSubscription messages, then we wait for the sendSubscriptions
         * messages to come in.
         */
        for (Iterator it = destinations.keySet().iterator(); it.hasNext(); )
        {
            String destName = (String) it.next();
            MessageDestination dest = (MessageDestination) getDestination(destName);
            if (!dest.getServerSettings().isBroadcastRoutingMode() && dest.isClustered())
                initRemoteSubscriptions(destName);
        }

        /* Now go through and wait for the response to these messages... */
        for (Iterator it = destinations.keySet().iterator(); it.hasNext(); )
        {
            String destName = (String) it.next();
            MessageDestination dest = (MessageDestination) getDestination(destName);
            if (!dest.getServerSettings().isBroadcastRoutingMode() && dest.isClustered())
            {
                List members = clm.getClusterMemberAddresses(serviceType, destName);
                for (int i = 0; i < members.size(); i++)
                {
                    Object addr = members.get(i);
                    if (!clm.getLocalAddress(serviceType, destName).equals(addr))
                    {
                        RemoteSubscriptionManager subMgr = dest.getRemoteSubscriptionManager();
                        subMgr.waitForSubscriptions(addr);
                    }
                }
            }
        }
    }

    //--------------------------------------------------------------------------
    //
    // Public Getters and Setters for AbstractService properties
    //
    //--------------------------------------------------------------------------

    /**
     * Creates a <code>MessageDestination</code> instance, sets its id, sets it manageable
     * if the <code>AbstractService</code> that created it is manageable,
     * and sets its <code>Service</code> to the <code>AbstractService</code> that
     * created it.
     *
     * @param id The id of the <code>MessageDestination</code>.
     * @return The <code>Destination</code> instanced created.
     */
    public Destination createDestination(String id)
    {
        MessageDestination destination = new MessageDestination();
        destination.setId(id);
        destination.setManaged(isManaged());
        destination.setService(this);

        return destination;
    }

    /**
     * Casts the <code>Destination</code> into <code>MessageDestination</code>
     * and calls super.addDestination.
     *
     * @param destination The <code>Destination</code> instance to be added.
     */
    public void addDestination(Destination destination)
    {
        MessageDestination messageDestination = (MessageDestination)destination;
        super.addDestination(messageDestination);
    }

    //--------------------------------------------------------------------------
    //
    // Other Public APIs
    //
    //--------------------------------------------------------------------------

    public Object serviceMessage(Message message)
    {
        return serviceMessage(message, true);
    }

    /**
     * @exclude
     */
    public Object serviceMessage(Message message, boolean throttle)
    {
        Object result = null;

        incrementMessageCount(false, message);

        MessageDestination dest = (MessageDestination) getDestination(message);

        // Throttle the inbound message - this also attempts to prevent duplicate
        // messages sent by a client.
        ThrottleResult throttleResult;
        if (throttle)
            throttleResult = dest.getThrottleManager().throttleIncomingMessage(message);
        else
            throttleResult = new ThrottleResult(ThrottleResult.RESULT_OK);

        int throttleResultCode = throttleResult.getResultCode();
        MessageException me = throttleResult.getException();
        if (throttleResultCode == ThrottleResult.RESULT_ERROR)
        {
            throw me;
        }
        else if (throttleResultCode == ThrottleResult.RESULT_IGNORE)
        {
            if (Log.isDebug())
                Log.getLogger(LOG_CATEGORY).debug(me.getMessage(), me);
        }
        else
        {
            // Block any sent messages that have a subtopic header containing
            // wildcards - wildcards are only supported in subscribe/unsubscribe
            // commands (see serviceCommand() and manageSubscriptions()).

            Object subtopicObj = message.getHeader(AsyncMessage.SUBTOPIC_HEADER_NAME);

            if (subtopicObj instanceof Object[])
                subtopicObj = Arrays.asList((Object[])subtopicObj);

            if (subtopicObj instanceof String)
            {
                String subtopicString = (String) subtopicObj;
                testProducerSubtopic(dest, subtopicString);
            }
            else if (subtopicObj instanceof List)
            {
                List subtopicList = (List) subtopicObj;

                for (int i = 0; i < subtopicList.size(); i++)
                    testProducerSubtopic(dest, (String) subtopicList.get(i));
            }

            // override TTL if there was one specifically configured for this destination
            ServerSettings destServerSettings = dest.getServerSettings();
            if (destServerSettings.getMessageTTL() >= 0)
                message.setTimeToLive(destServerSettings.getMessageTTL());

            long start = 0;
            if (Log.isDebug())
                start = System.currentTimeMillis();

            // Give MessagingAdapter a chance to block the send.
            ServiceAdapter adapter = dest.getAdapter();
            if (adapter instanceof MessagingAdapter)
                ((MessagingAdapter)adapter).getSecurityConstraintManager().assertSendAuthorization();

            MessagePerformanceUtils.markServerPreAdapterTime(message);
            result = adapter.invoke(message);
            MessagePerformanceUtils.markServerPostAdapterTime(message);

            if (Log.isDebug())
            {
                long end = System.currentTimeMillis();
                Log.getLogger(TIMING_LOG_CATEGORY).debug("After invoke service: " + getId() + "; execution time = " + (end - start) + "ms");
            }
        }
        return result;
    }

    /**
     * @exclude
     */
    public Object serviceCommand(CommandMessage message)
    {
        incrementMessageCount(true, message);
        Object commandResult = super.serviceCommonCommands(message);
        if (commandResult == null)
        {
            commandResult = manageSubscriptions(message);
        }
        return commandResult;
    }

    /**
     * This method is called from a messaging adapter to handle the delivery
     * of messages to one or more clients.
     * If you pass in the <code>sendToAllSubscribers</code> parameter as <code>true</code>, the message is
     * routed to all clients who are subscribed to receive messages
     * from this destination.  When you use this method, the selector expressions
     * for all subscribing clients are not evaluated. If you want the selector
     * expressions to be evaluated, use a combination of the <code>pushMessageToClients</code>
     * method and the <code>sendPushMessageFromPeer</code> methods.
     *
     * @param message The <code>Message</code> to send.
     * @param sendToAllSubscribers If <code>true</code>, send this message to all clients
     * subscribed to the destination of this message.  If <code>false</code>, send the message
     * only to the clientId specified in the message.
     */
    public void serviceMessageFromAdapter(Message message, boolean sendToAllSubscribers)
    {
        // Update management metrics.
        if (isManaged())
        {
            MessageDestination destination = (MessageDestination)getDestination(message.getDestination());
            if (destination != null && destination.isManaged())
            {
                MessageDestinationControl destinationControl = (MessageDestinationControl)destination.getControl();
                if (destinationControl != null) // Should not happen but just in case.
                    destinationControl.incrementServiceMessageFromAdapterCount();
            }
        }

        // in this service's case, this invocation occurs when an adapter has asynchronously
        // received a message from one of its adapters acting as a consumer
        if (sendToAllSubscribers)
        {
            pushMessageToClients(message, false);
            sendPushMessageFromPeer(message, false);
        }
        else
        {
            // TODO - need to do something here to locate the proper qualified client.
            // the adapter has already processed the subscribers
            Set subscriberIds = new TreeSet();
            subscriberIds.add(message.getClientId());
            pushMessageToClients(subscriberIds, message, false);
        }
    }

    /**
     * This method sends the passed message to clients connected to other
     * server peer nodes in the cluster.  If you are using broadcast cluster-messaging-routing
     * mode, the message is broadcast through the cluster.  If you are using the
     * server-to-server mode, the message is sent only to servers from which we have
     * received a matching subscription request.
     *
     * @param message The <code>Message</code> to push to peer server nodes in the cluster.
     *
     * @param evalSelector <code>true</code> to evaluate each remote subscriber's selector before pushing
     *        the message to them; <code>false</code> to skip selector evaluation.
     */
    public void sendPushMessageFromPeer(Message message, boolean evalSelector)
    {
        MessageDestination destination = (MessageDestination) getDestination(message);

           if (destination.isClustered())
        {
            ClusterManager clm = getMessageBroker().getClusterManager();
            if (destination.getServerSettings().isBroadcastRoutingMode())
            {
                if (Log.isDebug())
                    Log.getLogger(LOG_CATEGORY).debug("Broadcasting message to peer servers: " + message + " evalSelector: " + evalSelector);
                // tell the message service on other nodes to push the message
                clm.invokeServiceOperation(getClass().getName(), message.getDestination(),
                        "pushMessageFromPeer", new Object[] { message, Boolean.valueOf(evalSelector) });
            }
            else
            {
                RemoteSubscriptionManager mgr = destination.getRemoteSubscriptionManager();
                Set serverAddresses = mgr.getSubscriberIds(message, evalSelector);

                if (Log.isDebug())
                    Log.getLogger(LOG_CATEGORY).debug("Sending message to peer servers: " + serverAddresses + StringUtils.NEWLINE + " message: " + message + StringUtils.NEWLINE + " evalSelector: " + evalSelector);

                for (Iterator it = serverAddresses.iterator(); it.hasNext(); )
                {
                    Object remoteAddress = it.next();

                    clm.invokePeerToPeerOperation(getClass().getName(), message.getDestination(),
                            "pushMessageFromPeerToPeer", new Object[] { message, Boolean.valueOf(evalSelector)}, remoteAddress);
                }
            }
        }
    }

    /**
     * @exclude
     * This method is provided for a cluster peer broadcast from a single remote node.  Because the
     * cluster handling code adds the remote server's address as a paramter when you call invokePeerToPeerOperation
     * we need a new variant of this method which takes the remote node's address.
     */
    public void pushMessageFromPeerToPeer(AsyncMessage message, Boolean evalSelector, Object address)
    {
        pushMessageFromPeer(message, evalSelector);
    }

    /**
     * @exclude
     * This method is provided for a cluster peer broadcast, it is not intended to be
     * invoked locally.
     */
    public void pushMessageFromPeer(AsyncMessage message, Boolean evalSelector)
    {
        if (!isStarted())
        {
            Log.getLogger(LOG_CATEGORY).debug("Received message from peer server before server is started - ignoring: " + message + " evalSelector: " + evalSelector);
            return;
        }
        if (Log.isDebug())
            Log.getLogger(LOG_CATEGORY).debug("Received message from peer server: " + message + " evalSelector: " + evalSelector);

        // Update the FlexContext for this thread to indicate we're processing a message from
        // a server peer.
        FlexContext.setMessageFromPeer(true);
        // we are not confirming that replication is enabled again here, so if the remote
        // peer has replication enabled and therefore broadcast to this peer, then this peer
        // will complete the operation even if it locally does not have replication enabled
        pushMessageToClients(message, evalSelector.booleanValue());
        // And unset.
        FlexContext.setMessageFromPeer(false);
    }

    /**
     * Pushes a message to all clients that are subscribed to the destination targeted by this message.
     *
     * @param message The <code>Message</code> to push to the destination's subscribers.
     *
     * @param evalSelector <code>true</code> to evaluate each subscriber's selector before pushing
     *        the message to them; <code>false</code> to skip selector evaluation.
     */
    public void pushMessageToClients(Message message, boolean evalSelector)
    {
        MessageDestination destination = (MessageDestination)getDestination(message);
        SubscriptionManager subscriptionManager = destination.getSubscriptionManager();
        Set subscriberIds = subscriptionManager.getSubscriberIds(message, evalSelector);

        if (Log.isDebug())
            Log.getLogger(LOG_CATEGORY).debug("Sending message: " + message + StringUtils.NEWLINE + "    to subscribed clientIds: " + subscriberIds);

        if ((subscriberIds != null) && !subscriberIds.isEmpty())
        {
            /* We have already filtered based on the selector and so pass false below */
            pushMessageToClients(destination, subscriberIds, message, false);
        }
    }

    /**
     * Returns a Set of clientIds of the clients subscribed to receive this message.
     * If the message has a subtopic header, the subtopics are used to gather the
     * subscribers.  If there is no subtopic header, subscribers to the destination
     * with no subtopic are used.  If a subscription has a selector expression associated
     * with it and evalSelector is true, the subscriber is only returned if the selector
     * expression evaluates to true.
     * <p>
     * In normal usage, you can use the pushMessageToClients(message, evalSelector) method
     * to both find the subscribers and send the message.  You use this method only if
     * you want to do additional processing to the subscribers list - for example, merging
     * it into another list of subscribers or logging the ids of the subscribers who should
     * receive the message.  Once this method returns, you can use the pushMessageToClients
     * variant which takes the set of subscriber ids to deliver these messages.
     * </p><p>
     * This method only returns subscriptions maintained by the current server instance.
     * It does not return any information for subscribers that might be connected to
     * remote instances.  To send the message to remotely connected servers, use the
     * sendPushMessageFromPeer method.
     * </p>
     *
     * @param message The <code>Messsage</code>
     * Typically
     */
    public Set getSubscriberIds(Message message, boolean evalSelector)
    {
        MessageDestination destination = (MessageDestination) getDestination(message);
        SubscriptionManager subscriptionManager = destination.getSubscriptionManager();
        return subscriptionManager.getSubscriberIds(message, evalSelector);
    }

    /**
     * Returns the set of subscribers for the specified destination, subtopic/subtopic pattern
     * and message headers.  The message headers can be null.  If specified, they are used
     * to match against any selector patterns that were used for subscribers.
     */
    public Set getSubscriberIds(String destinationId, String subtopicPattern, Map messageHeaders)
    {
        MessageDestination destination = (MessageDestination) getDestination(destinationId);
        SubscriptionManager subscriptionManager = destination.getSubscriptionManager();
        return subscriptionManager.getSubscriberIds(subtopicPattern, messageHeaders);
    }

    /**
     * This method is not invoked across a cluster, it is always locally invoked.
     * The passed message will be pushed to the subscribers in the passed set, conditionally depending
     * upon the execution of their selector expressions.
     *
     * @param subscriberIds The set of subscribers to push the message to.
     *
     * @param message The <code>Message</code> to push.
     *
     * @param evalSelector <code>true</code> to evaluate each subscriber's selector before pushing
     *        the message to them; <code>false</code> to skip selector evaluation.
     */
    public void pushMessageToClients(Set subscriberIds, Message message, boolean evalSelector)
    {
        MessageDestination destination = (MessageDestination)getDestination(message);
        pushMessageToClients(destination, subscriberIds, message, evalSelector);
    }

    /**
     * @exclude
     * This method is used by messaging adapters to send a message to a specific
     * set of clients that are directly connected to this server.  It does not
     * propagate the message to other servers in the cluster.
     */
    public void pushMessageToClients(MessageDestination destination, Set subscriberIds, Message message, boolean evalSelector)
    {
        if (subscriberIds != null)
        {
            try
            {
                // Place notifier in thread-local scope.
                MessageRoutedNotifier routingNotifier = new MessageRoutedNotifier(message);
                FlexContext.setMessageRoutedNotifier(routingNotifier);

                // Throttle outgoing at the destination level
                ThrottleResult throttleResult = destination.getThrottleManager().throttleOutgoingMessage(message, null);
                int throttleResultCode = throttleResult.getResultCode();
                MessageException me = throttleResult.getException();
                if (throttleResultCode == ThrottleResult.RESULT_ERROR)
                {
                    throw me;
                }
                else if (throttleResultCode == ThrottleResult.RESULT_IGNORE)
                {
                    if (Log.isDebug())
                        Log.getLogger(LOG_CATEGORY).debug(me.getMessage(), me);
                }
                else
                {
                    SubscriptionManager subscriptionManager = destination.getSubscriptionManager();

                    for (Iterator clientIter = subscriberIds.iterator(); clientIter.hasNext();)
                    {
                        Object clientId = clientIter.next();
                        MessageClient client = (MessageClient)subscriptionManager.getSubscriber(clientId);

                        // Skip if the client is null or invalidated.
                        if (client == null || !client.isValid())
                        {
                            if (Log.isDebug())
                                Log.getLogger(MessageService.LOG_CATEGORY).debug("Warning: could not find MessageClient for clientId in pushMessageToClients: " + clientId + " for destination: " + destination.getId());

                            continue;
                        }

                        pushMessageToClient(client, destination, message, evalSelector, throttleResult);
                    }
                }

                // Done with the push, notify any listeners.
                routingNotifier.notifyMessageRouted();
            }
            finally
            {
                // Unset the notifier for this message.
                FlexContext.setMessageRoutedNotifier(null);
            }
        }
    }

    void pushMessageToClient(MessageClient client, MessageDestination destination, Message message,
                             boolean evalSelector, ThrottleResult throttleResult)
    {
        // Normally we'll process the message selector criteria as part of fetching the
        // clients which should receive this message. However, because the API exposed the evalSelecor flag
        // in pushMessageToClients(Set, Message, boolean), we need to run the client.testMessage() method
        // here to make sure subtopic and selector expressions are evaluated correctly in this case.
        // The general code path passes evalSelector as false, so the testMessage() method is not generally
        // invoked as part of a message push operation.
        if (evalSelector && !client.testMessage(message, destination))
        {
            return;
        }

        if (throttleResult.getResultCode() == ThrottleResult.RESULT_OK)
        {
            // Throttle at client level.
            throttleResult = destination.getThrottleManager().throttleOutgoingMessage(message, client.getClientId());

            int throttleResultCode = throttleResult.getResultCode();
            MessageException me = throttleResult.getException();
            if (throttleResultCode == ThrottleResult.RESULT_ERROR)
            {
                // Log these, but they are not propagated to subscriber, as that would defeat
                // the purpose of throttling outbound messages to that subscriber client
                Log.getLogger(LOG_CATEGORY).error(me.getMessage(), me);
            }
            else if (throttleResultCode == ThrottleResult.RESULT_IGNORE)
            {
                if (Log.isDebug())
                    Log.getLogger(LOG_CATEGORY).debug(me.getMessage(), me);
            }
            else
            {
                // Push the message.
                try
                {
                    // Only update client last use if the message is not a pushed server command.
                    if (!(message instanceof CommandMessage))
                        client.updateLastUse();

                    // Remove any data in the base message that should not be included in the multicast copies.
                    Map messageHeaders = message.getHeaders();
                    messageHeaders.remove(Message.FLEX_CLIENT_ID_HEADER);
                    messageHeaders.remove(Message.ENDPOINT_HEADER);

                    // FIXME: [Pete] Investigate whether this is a performance issue.
                    // We also need to ensure message ids do not expose FlexClient ids
                    //message.setMessageId(UUIDUtils.createUUID());

                    // We need a unique instance of the message for each client; both to prevent
                    // outbound queue processing for various clients from interfering with each other
                    // as well as needing to target the copy of the message to a specific MessageAgent
                    // instance on the client.
                    Message messageForClient = (Message)message.clone();

                    // the MPIUTil call will be a no-op if MPI is not enabled.  Otherwise it will add
                    // a server pre-push processing timestamp to the MPI object
                    MessagePerformanceUtils.markServerPrePushTime(message);
                    MessagePerformanceUtils.markServerPostAdapterTime(message);
                    MessagePerformanceUtils.markServerPostAdapterExternalTime(message);

                    // Target the message to a specific MessageAgent on the client.
                    messageForClient.setClientId(client.getClientId());

                    if (Log.isDebug())
                        Log.getLogger(MessageService.LOG_CATEGORY).debug("Routing message to FlexClient id:" + client.getFlexClient().getId() + "', MessageClient id: " + client.getClientId());

                    getMessageBroker().routeMessageToMessageClient(messageForClient, client);
                }
                catch (MessageException ignore)
                {
                    // Client is subscribed but has disconnected or the network failed.
                    // There's nothing we can do to correct this so just continue server processing.
                }
            }
        }
    }

    /**
     * Issue messages to request the remote subscription table from each server in the cluster (except this one).
     */
    public void initRemoteSubscriptions(String destinationId)
    {
        ClusterManager clm = getMessageBroker().getClusterManager();
        String serviceType = getClass().getName();
        MessageDestination dest = (MessageDestination) getDestination(destinationId);

        Cluster cluster = clm.getCluster(serviceType, destinationId);
        if (cluster != null)
            cluster.addRemoveNodeListener(dest.getRemoteSubscriptionManager());

        List members = clm.getClusterMemberAddresses(serviceType, destinationId);
        for (int i = 0; i < members.size(); i++)
        {
            Object addr = members.get(i);
            if (!clm.getLocalAddress(serviceType, destinationId).equals(addr))
                requestSubscriptions(destinationId, addr);
        }

    }

    /**
     * This method is provided for a clustered messaging with the routing-mode set to point-to-point.
     * On startup, a server invokes this method for each server to request its local subscription state.
     *
     * @exclude
     */
    public void requestSubscriptions(String destinationId, Object remoteAddress)
    {
        ClusterManager clm = getMessageBroker().getClusterManager();
        clm.invokePeerToPeerOperation(getClass().getName(), destinationId,
                "sendSubscriptions", new Object[] { destinationId }, remoteAddress);
    }

    /**
     * This method is invoked remotely via jgroups.  It builds a snapshot of the local
     * subscription state and sends it back to the requesting server by calling its
     * receiveSubscriptions method.
     *
     * @exclude
     */
    public void sendSubscriptions(String destinationId, Object remoteAddress)
    {
        MessageDestination destination = (MessageDestination) getDestination(destinationId);
        Object subscriptions;

        /*
         * Avoid trying to use the cluster stuff if this destination does not
         * exist or is not clustered on this server.
         */
        if (destination == null)
        {
            if (Log.isError())
                Log.getLogger(LOG_CATEGORY).error("Destination: " + destinationId + " does not exist on this server but we received a request for the subscription info from a peer server where the destination exists as clustered.  Check the cluster configuration for this destination and make sure it matches on all servers.");
            return;
        }
        else if (!destination.isClustered())
        {
            if (Log.isError())
                Log.getLogger(LOG_CATEGORY).error("Destination: " + destinationId + " is not clustered on this server but we received a request for the subscription info from a peer server which is clustered.  Check the cluster configuration for this destination and make sure it matches on all servers.");
            return;
        }

        RemoteSubscriptionManager subMgr = destination.getRemoteSubscriptionManager();

        /*
         * The remote server has no subscriptions initially since it has not
         * started yet.  We initialize the server here so that when it sends
         * the first add subscription request, we'll receive it.  This is because
         * servers will not process remote add/remove subscribe requests until
         * they have received the subscription state from each server.
         */
        subMgr.setSubscriptionState(Collections.EMPTY_LIST, remoteAddress);

        /*
         * To ensure that we send the remote server a clean copy of the subscription
         * table we need to block out the code which adds/removes subscriptions and sends
         * them to remote servers between here...
         */
        try
        {
            subscribeLock.writeLock().lock();

            if (destination instanceof MessageDestination)
                subscriptions = ((MessageDestination) destination).getSubscriptionManager().getSubscriptionState();
            else
                subscriptions = null;
            ClusterManager clm = getMessageBroker().getClusterManager();
            clm.invokePeerToPeerOperation(getClass().getName(), destinationId,
                    "receiveSubscriptions", new Object[] { destinationId, subscriptions }, remoteAddress);
        }
        finally
        {
            /* ... And here */
            subscribeLock.writeLock().unlock();
        }
    }

    /**
     * This method is provided for a cluster peer broadcast, it is not invoked locally.  It is used
     * by remote clients to send their subscription table to this server.
     *
     * @exclude
     */
    public void receiveSubscriptions(String destinationId, Object subscriptions, Object senderAddress)
    {
        Destination destination = getDestination(destinationId);
        if (destination instanceof MessageDestination)
        {
            ((MessageDestination) destination).getRemoteSubscriptionManager().setSubscriptionState(subscriptions, senderAddress);
        }
        else if (subscriptions != null)
        {
            if (Log.isError())
                Log.getLogger(LOG_CATEGORY).error("receiveSubscriptions called with non-null value but destination: " + destinationId + " is not a MessageDestination");
        }
    }

    /**
     * Called when we need to push a local subscribe/unsubscribe to all of the remote
     * servers.
     *
     * @exclude
     */
    public void sendSubscribeFromPeer(String destinationId, Boolean subscribe, String selector, String subtopic)
    {
        ClusterManager clm = getMessageBroker().getClusterManager();

        String serviceType = getClass().getName();

        clm.invokeServiceOperation(serviceType, destinationId,
                        "subscribeFromPeer", new Object[] { destinationId, subscribe, selector, subtopic, clm.getLocalAddress(serviceType, destinationId)});
    }

    /**
     * This is called remotely from other cluster members when a new remote subscription is identified.
     *
     * We add or remove a remote subscription...
     */
    public void subscribeFromPeer(String destinationId, Boolean subscribe, String selector, String subtopic, Object remoteAddress)
    {
        Destination destination = getDestination(destinationId);

        RemoteSubscriptionManager subMgr = ((MessageDestination) destination).getRemoteSubscriptionManager();

        if (destination instanceof MessageDestination)
        {
            if (Log.isDebug())
                Log.getLogger(MessageService.LOG_CATEGORY).debug("Received subscription from peer: " + remoteAddress + " subscribe? " + subscribe + " selector: " + selector + " subtopic: " + subtopic);
            if (subscribe.booleanValue())
                subMgr.addSubscriber(remoteAddress, selector, subtopic, null);
            else
                subMgr.removeSubscriber(remoteAddress, selector, subtopic, null);
        }
        else if (Log.isError())
            Log.getLogger(LOG_CATEGORY).error("subscribeFromPeer called with destination: " + destinationId + " that is not a MessageDestination");
    }

    //--------------------------------------------------------------------------
    //
    // Protected/private APIs
    //
    //--------------------------------------------------------------------------

    /**
     * Used to increment the message count metric for the <code>MessageService</code>. This value is
     * stored in the corresponding MBean. The <code>MessageService</code> already invokes this method
     * in its <code>serviceMessage()</code> and <code>serviceCommand()</code> implementations, but if
     * a subclass overrides these methods completely it should invoke this method appropriately as
     * it processes messages.
     *
     * @param commandMessage Pass <code>true</code> if the message being processed is a <code>CommandMessage</code>;
     *                       otherwise <code>false</code>.
     */
    protected void incrementMessageCount(boolean commandMessage, Message message)
    {
        // Update management metrics.
        if (isManaged())
        {
            MessageDestination destination = (MessageDestination)getDestination(message.getDestination());
            if (destination != null && destination.isManaged())
            {
                MessageDestinationControl destinationControl = (MessageDestinationControl)destination.getControl();
                if (destinationControl != null) // Should not happen but just in case.
                {
                    if (commandMessage)
                        destinationControl.incrementServiceCommandCount();
                    else
                        destinationControl.incrementServiceMessageCount();
                }
            }
        }
    }

    /**
     * Processes subscription related <code>CommandMessage</code>s. Subclasses that perform additional
     * custom subscription management should invoke <code>super.manageSubscriptions()</code> if they
     * choose to override this method.
     *
     * @param command The <code>CommandMessage</code> to process.
     */
    protected Message manageSubscriptions(CommandMessage command)
    {
        Message replyMessage = null;

        MessageDestination destination = (MessageDestination)getDestination(command);
        SubscriptionManager subscriptionManager = destination.getSubscriptionManager();

        Object clientId = command.getClientId();
        String endpointId = (String)command.getHeader(Message.ENDPOINT_HEADER);

        String subtopicString = (String) command.getHeader(AsyncMessage.SUBTOPIC_HEADER_NAME);

        ServiceAdapter adapter = destination.getAdapter();

        if (command.getOperation() == CommandMessage.SUBSCRIBE_OPERATION)
        {
            String selectorExpr = (String) command.getHeader(CommandMessage.SELECTOR_HEADER);

            getMessageBroker().inspectChannel(command, destination);

            // Give MessagingAdapter a chance to block the subscribe.
            if ((adapter instanceof MessagingAdapter))
                ((MessagingAdapter)adapter).getSecurityConstraintManager().assertSubscribeAuthorization();

            try
            {
                /*
                 * This allows parallel add/remove subscribe calls (protected by the
                 * concurrent hash table) but prevents us from doing any table mods
                 * when the getSubscriptionState method is active
                 */
                subscribeLock.readLock().lock();

                if (adapter.handlesSubscriptions())
                {
                    replyMessage = (Message) adapter.manage(command);
                }
                else
                {
                    testSelector(selectorExpr, command);
                }
                /*
                 * Even if the adapter is managing the subscription, we still need to
                 * register this with the subscription manager so that we can match the
                 * endpoint with the clientId.  I am not sure I like this though because
                 * now the subscription is registered both with the adapter and with our
                 * system so keeping them in sync is potentially problematic.   Also, it
                 * seems like the adapter should have the option to manage endpoints themselves?
                 */
                subscriptionManager.addSubscriber(clientId, selectorExpr, subtopicString, endpointId);
            }
            finally
            {
                subscribeLock.readLock().unlock();
            }

            if (replyMessage == null)
                replyMessage = new AcknowledgeMessage();
        }
        else if (command.getOperation() == CommandMessage.UNSUBSCRIBE_OPERATION)
        {
            // Give MessagingAdapter a chance to block the unsubscribe, as long as the subscription
            // has not been invalidated
            if ((adapter instanceof MessagingAdapter) && command.getHeader(CommandMessage.SUBSCRIPTION_INVALIDATED_HEADER) == null)
                ((MessagingAdapter)adapter).getSecurityConstraintManager().assertSubscribeAuthorization();

            String selectorExpr = (String) command.getHeader(CommandMessage.SELECTOR_HEADER);

            try
            {
                subscribeLock.readLock().lock();

                if (adapter.handlesSubscriptions())
                {
                    replyMessage = (Message) adapter.manage(command);
                }
                subscriptionManager.removeSubscriber(clientId, selectorExpr, subtopicString, endpointId);
            }
            finally
            {
                subscribeLock.readLock().unlock();
            }

            if (replyMessage == null)
                replyMessage = new AcknowledgeMessage();
        }
        else if (command.getOperation() == CommandMessage.MULTI_SUBSCRIBE_OPERATION)
        {
            getMessageBroker().inspectChannel(command, destination);

            // Give MessagingAdapter a chance to block the multi subscribe.
            if ((adapter instanceof MessagingAdapter))
                ((MessagingAdapter)adapter).getSecurityConstraintManager().assertSubscribeAuthorization();

            try
            {
                /*
                 * This allows parallel add/remove subscribe calls (protected by the
                 * concurrent hash table) but prevents us from doing any table mods
                 * when the getSubscriptionState method is active
                 */
                subscribeLock.readLock().lock();

                if (adapter.handlesSubscriptions())
                {
                    replyMessage = (Message) adapter.manage(command);
                }

                // Deals with legacy collection setting
                Object[] adds = getObjectArrayFromHeader(command.getHeader(CommandMessage.ADD_SUBSCRIPTIONS));
                Object[] rems = getObjectArrayFromHeader(command.getHeader(CommandMessage.REMOVE_SUBSCRIPTIONS));

                if (adds != null)
                {
                    for (int i = 0; i < adds.length; i++)
                    {
                        String ss = (String) adds[i];
                        int ix = ss.indexOf(CommandMessage.SUBTOPIC_SEPARATOR);
                        if (ix != -1)
                        {
                            String subtopic = (ix == 0 ? null : ss.substring(0, ix));
                            String selector = ss.substring(ix+CommandMessage.SUBTOPIC_SEPARATOR.length());
                            if (selector.length() == 0)
                                selector = null;

                            subscriptionManager.addSubscriber(clientId, selector, subtopic, endpointId);
                        }
                        // invalid message
                    }
                }

                if (rems != null)
                {
                    for (int i = 0; i < rems.length; i++)
                    {
                        String ss = (String) rems[i];
                        int ix = ss.indexOf(CommandMessage.SUBTOPIC_SEPARATOR);
                        if (ix != -1)
                        {
                            String subtopic = (ix == 0 ? null : ss.substring(0, ix));
                            String selector = ss.substring(ix+CommandMessage.SUBTOPIC_SEPARATOR.length());
                            if (selector.length() == 0)
                                selector = null;

                            subscriptionManager.removeSubscriber(clientId, selector, subtopic, endpointId);
                        }
                    }
                }
            }
            finally
            {
                subscribeLock.readLock().unlock();
            }

            if (replyMessage == null)
                replyMessage = new AcknowledgeMessage();
        }
        else if (command.getOperation() == CommandMessage.POLL_OPERATION)
        {
            // This code path handles poll messages sent by Consumer.receive().
            // This API should not trigger server side waits, so we invoke poll
            // and if there are no queued messages for this Consumer instance we
            // return an empty acknowledgement immediately.
            MessageClient client = null;
            try
            {
                client = subscriptionManager.getMessageClient(clientId, endpointId);

                if (client != null)
                {
                    if (adapter.handlesSubscriptions())
                    {
                        List missedMessages = (List)adapter.manage(command);
                        if (missedMessages != null && !missedMessages.isEmpty())
                        {
                            MessageBroker broker = getMessageBroker();
                            for (Iterator iter = missedMessages.iterator(); iter.hasNext();)
                                broker.routeMessageToMessageClient((Message)iter.next(), client);
                        }
                    }
                    FlushResult flushResult = client.getFlexClient().poll(client);
                    List messagesToReturn = (flushResult != null) ? flushResult.getMessages() : null;
                    if (messagesToReturn != null && !messagesToReturn.isEmpty())
                    {
                        replyMessage = new CommandMessage(CommandMessage.CLIENT_SYNC_OPERATION);
                        replyMessage.setBody(messagesToReturn.toArray());
                    }
                    else
                    {
                        replyMessage = new AcknowledgeMessage();
                    }
                    // Adaptive poll wait is never used in responses to Consumer.receive() calls.
                }
                else
                {
                    ServiceException se = new ServiceException();
                    se.setCode(NOT_SUBSCRIBED_CODE);
                    se.setMessage(NOT_SUBSCRIBED, new Object[] {destination.getId()});
                    throw se;
                }
            }
            finally
            {
                subscriptionManager.releaseMessageClient(client);
            }
        }
        else
        {
            ServiceException se = new ServiceException();
            se.setMessage(UNKNOWN_COMMAND, new Object[] {new Integer(command.getOperation())});
            throw se;
        }

        return replyMessage;
    }


    /**
     * Returns the log category of the <code>MessageService</code>.
     *
     * @return The log category of the component.
     */
    protected String getLogCategory()
    {
        return LOG_CATEGORY;
    }

    /**
     * This method is invoked to allow the <code>MessageService</code> to instantiate and register its
     * MBean control.
     *
     * @param broker The <code>MessageBroker</code> to pass to the <code>MessageServiceControl</code> constructor.
     */
    protected void setupServiceControl(MessageBroker broker)
    {
        controller = new MessageServiceControl(this, broker.getControl());
        controller.register();
        setControl(controller);
    }

    /**
     * @exclude
     * Tests a selector in an attempt to avoid runtime errors that we could catch at startup.
     *
     * @param selectorExpression The expression to test.
     * @param msg A test message.
     */
    private void testSelector(String selectorExpression, Message msg)
    {
        try
        {
            JMSSelector selector = new JMSSelector(selectorExpression);
            selector.match(msg);
        }
        catch (Exception e)
        {
            ServiceException se = new ServiceException();
            se.setMessage(BAD_SELECTOR, new Object[] {selectorExpression});
            se.setRootCause(e);
            throw se;
        }
    }

    private void testProducerSubtopic(MessageDestination dest, String subtopicString)
    {
        if ((subtopicString != null) && (subtopicString.length() > 0))
        {
            Subtopic subtopic = new Subtopic(subtopicString, dest.getServerSettings().getSubtopicSeparator());
            if (subtopic.containsSubtopicWildcard())
            {
                ServiceException se = new ServiceException();
                se.setMessage(10556, new Object[] {subtopicString});
                throw se;
            }
        }
    }

    private Object[] getObjectArrayFromHeader(Object header)
    {
        if (header instanceof Object[])
            return (Object []) header;
        else if (header instanceof List)
            return ((List) header).toArray();
        else if (header == null)
            return null;

        ServiceException se = new ServiceException();
        se.setMessage("Invalid header: " + header + " in message.  expected array or list and found: " + header.getClass().getName());
        throw se;
    }

}
TOP

Related Classes of flex.messaging.services.MessageService

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.