/** * 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(); subscriptions = destination.getSubscriptionManager().getSubscriptionState(); ClusterManager clm = getMessageBroker().getClusterManager(); clm.invokePeerToPeerOperation( getClass().getName(), destinationId, "receiveSubscriptions", new Object[] {destinationId, subscriptions}, remoteAddress); } finally { /* ... And here */ subscribeLock.writeLock().unlock(); } }
/** * 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 (debug) 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); } }
/** * @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); SubscriptionManager subscriptionManager = destination.getSubscriptionManager(); for (Object clientId : subscriberIds) { MessageClient client = subscriptionManager.getSubscriber(clientId); // Skip if the client is null or invalidated. if (client == null || !client.isValid()) { if (debug) 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); } // Done with the push, notify any listeners. routingNotifier.notifyMessageRouted(); } finally { // Unset the notifier for this message. FlexContext.setMessageRoutedNotifier(null); } } }
/** * 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)) { MessagingSecurityConstraintManager manager = ((MessagingAdapter) adapter).getSecurityConstraintManager(); if (manager != null) manager.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? */ // Extract the maxFrequency that might have been specified by the client. int maxFrequency = processMaxFrequencyHeader(command); subscriptionManager.addSubscriber( clientId, selectorExpr, subtopicString, endpointId, maxFrequency); } 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) { MessagingSecurityConstraintManager manager = ((MessagingAdapter) adapter).getSecurityConstraintManager(); if (manager != null) manager.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)) { MessagingSecurityConstraintManager manager = ((MessagingAdapter) adapter).getSecurityConstraintManager(); if (manager != null) manager.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) { // Extract the maxFrequency that might have been specified // by the client for every subscription (selector/subtopic). int maxFrequency = processMaxFrequencyHeader(command); for (int i = 0; i < adds.length; i++) { // Use the maxFrequency by default. int maxFrequencyPerSubscription = maxFrequency; 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 = null; String selectorAndMaxFrequency = ss.substring(ix + CommandMessage.SUBTOPIC_SEPARATOR.length()); if (selectorAndMaxFrequency.length() != 0) { int ix2 = selectorAndMaxFrequency.indexOf(CommandMessage.SUBTOPIC_SEPARATOR); if (ix2 != -1) { selector = (ix2 == 0 ? null : selectorAndMaxFrequency.substring(0, ix2)); String maxFrequencyString = selectorAndMaxFrequency.substring( ix2 + CommandMessage.SUBTOPIC_SEPARATOR.length()); if (maxFrequencyString.length() != 0) { // Choose the minimum of Consumer level maxFrequency and subscription level // maxFrequency. int maxFrequencyCandidate = Integer.parseInt(maxFrequencyString); maxFrequencyPerSubscription = maxFrequencyPerSubscription == 0 ? maxFrequencyCandidate : Math.min(maxFrequencyPerSubscription, maxFrequencyCandidate); } } } subscriptionManager.addSubscriber( clientId, selector, subtopic, endpointId, maxFrequencyPerSubscription); } // 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 = null; String selectorAndMaxFrequency = ss.substring(ix + CommandMessage.SUBTOPIC_SEPARATOR.length()); if (selectorAndMaxFrequency.length() != 0) { int ix2 = selectorAndMaxFrequency.indexOf(CommandMessage.SUBTOPIC_SEPARATOR); if (ix2 != -1) selector = ix2 == 0 ? null : selectorAndMaxFrequency.substring(0, ix2); } 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 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); }
/** * 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>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. * * @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); }