/** * Implement a serializer instance which wraps the subscription manager in a transient variable. * It will need to block out all sub/unsub messages before they are broadcast to the remote * server, iterate through the maps of subscriptions and for each "unique" subscription it writes * the selector and subtopic. * * <p>synchronization note: this assumes no add/remove subscriptions are occurring while this * method is called. * * @return a List of subscriptions selectors and subtopics */ public Object getSubscriptionState() { ArrayList<String> subState = new ArrayList<String>(); if (globalSubscribers.defaultSubscriptions != null && !globalSubscribers.defaultSubscriptions.isEmpty()) { subState.add(null); // selector string subState.add(null); // subtopic string } if (globalSubscribers.selectorSubscriptions != null) { for (String s : globalSubscribers.selectorSubscriptions.keySet()) { subState.add(s); subState.add(null); // subtopic } } addSubscriptionState(subState, subscribersPerSubtopic); addSubscriptionState(subState, subscribersPerSubtopicWildcard); if (Log.isDebug()) Log.getLogger(MessageService.LOG_CATEGORY) .debug( "Retrieved subscription state to send to new cluster member for destination: " + destination.getId() + ": " + StringUtils.NEWLINE + subState); return subState; }
/** * Broadcast this subscribe/unsubscribe message to the cluster so everyone is aware of this * server's interest in messages matching this selector and subtopic. * * @param subscribe are we subscribing? * @param selector the selector * @param subtopic the subtopic */ protected void sendSubscriptionToPeer(boolean subscribe, String selector, String subtopic) { if (Log.isDebug()) Log.getLogger(MessageService.LOG_CATEGORY) .debug( "Sending subscription to peers for subscribe? " + subscribe + " selector: " + selector + " subtopic: " + subtopic); ((MessageService) destination.getService()) .sendSubscribeFromPeer(destination.getId(), subscribe, selector, subtopic); }
void addSubtopicSubscribers( Subtopic subtopic, Message message, Set<Object> ids, boolean evalSelector) { // If we have a subtopic, we need to route the message only to that // subset of subscribers. if (!destination.getServerSettings().getAllowSubtopics()) { // Throw an error - the destination doesn't allow subtopics. ServiceException se = new ServiceException(); se.setMessage( SUBTOPICS_NOT_SUPPORTED, new Object[] {subtopic.getValue(), destination.getId()}); throw se; } // Give a MessagingAdapter a chance to block the send to this subtopic. ServiceAdapter adapter = destination.getAdapter(); if (adapter instanceof MessagingSecurity) { if (!((MessagingSecurity) adapter).allowSend(subtopic)) { ServiceException se = new ServiceException(); se.setMessage(10558, new Object[] {subtopic.getValue()}); throw se; } } TopicSubscription ts; if (subscribersPerSubtopic.containsKey(subtopic)) { ts = subscribersPerSubtopic.get(subtopic); addTopicSubscribers(ts, message, ids, evalSelector); } /* * TODO: performance - organize these into a tree so we can find consumers via * a hashtable lookup rather than a linear search */ Set<Subtopic> subtopics = subscribersPerSubtopicWildcard.keySet(); if (!subtopics.isEmpty()) { for (Subtopic st : subtopics) { if (st.matches(subtopic)) { ts = subscribersPerSubtopicWildcard.get(st); addTopicSubscribers(ts, message, ids, 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); 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); } } }
/** * Add a subscriber. * * @param clientId the client id * @param selector the selector * @param subtopicString the subtopic * @param endpointId the endpoint * @param maxFrequency maximum frequency */ public void addSubscriber( Object clientId, String selector, String subtopicString, String endpointId, int maxFrequency) { Subtopic subtopic = getSubtopic(subtopicString); MessageClient client = null; TopicSubscription topicSub; Map<Object, MessageClient> subs; Map<Subtopic, TopicSubscription> map; try { // Handle resubscribes from the same client and duplicate subscribes from different clients boolean subscriptionAlreadyExists = (getSubscriber(clientId) != null); client = getMessageClient(clientId, endpointId); FlexClient flexClient = FlexContext.getFlexClient(); if (subscriptionAlreadyExists) { // Block duplicate subscriptions from multiple FlexClients if they // attempt to use the same clientId. (when this is called from a remote // subscription, there won't be a flex client so skip this test). if (flexClient != null && !flexClient.getId().equals(client.getFlexClient().getId())) { ServiceException se = new ServiceException(); se.setMessage(10559, new Object[] {clientId}); throw se; } // It's a resubscribe. Reset the endpoint push state for the subscription to make sure its // current // because a resubscribe could be arriving over a new endpoint or a new session. client.resetEndpoint(endpointId); } ServiceAdapter adapter = destination.getAdapter(); client.updateLastUse(); if (subtopic == null) { topicSub = globalSubscribers; } else { if (!destination.getServerSettings().getAllowSubtopics()) { // Throw an error - the destination doesn't allow subtopics. ServiceException se = new ServiceException(); se.setMessage( SUBTOPICS_NOT_SUPPORTED, new Object[] {subtopicString, destination.getId()}); throw se; } if (subtopic.containsSubtopicWildcard() && destination.getServerSettings().isDisallowWildcardSubtopics()) { // Attempt to subscribe to the subtopic, ''{0}'', on destination, ''{1}'', that does not // allow wilcard subtopics failed. ServiceException se = new ServiceException(); se.setMessage( WILDCARD_SUBTOPICS_NOT_ALLOWED, new Object[] {subtopicString, destination.getId()}); throw se; } // Give a MessagingAdapter a chance to block the subscribe. if ((adapter instanceof MessagingSecurity) && (subtopic != null)) { if (!((MessagingSecurity) adapter).allowSubscribe(subtopic)) { ServiceException se = new ServiceException(); se.setMessage(10557, new Object[] {subtopicString}); throw se; } } /* * If there is a wildcard, we always need to match that subscription * against the producer. If it has no wildcard, we can do a quick * lookup to find the subscribers. */ if (subtopic.containsSubtopicWildcard()) map = subscribersPerSubtopicWildcard; else map = subscribersPerSubtopic; synchronized (this) { topicSub = map.get(subtopic); if (topicSub == null) { topicSub = new TopicSubscription(); map.put(subtopic, topicSub); } } } /* Subscribing with no selector */ if (selector == null) { subs = topicSub.defaultSubscriptions; if (subs == null) { synchronized (this) { if ((subs = topicSub.defaultSubscriptions) == null) topicSub.defaultSubscriptions = subs = new ConcurrentHashMap<Object, MessageClient>(); } } } /* Subscribing with a selector - store all subscriptions under the selector key */ else { synchronized (this) { if (topicSub.selectorSubscriptions == null) topicSub.selectorSubscriptions = new ConcurrentHashMap<String, Map<Object, MessageClient>>(); } subs = topicSub.selectorSubscriptions.get(selector); if (subs == null) { synchronized (this) { if ((subs = topicSub.selectorSubscriptions.get(selector)) == null) topicSub.selectorSubscriptions.put( selector, subs = new ConcurrentHashMap<Object, MessageClient>()); } } } if (subs.containsKey(clientId)) { /* I'd rather this be an error but in 2.0 we allowed this without error */ if (Log.isWarn()) Log.getLogger(JMSSelector.LOG_CATEGORY) .warn( "Client: " + clientId + " already subscribed to: " + destination.getId() + " selector: " + selector + " subtopic: " + subtopicString); } else { client.addSubscription(selector, subtopicString, maxFrequency); synchronized (this) { /* * Make sure other members of the cluster know that we are subscribed to * this info if we are in server-to-server mode * * This has to be done in the synchronized section so that we properly * order subscribe and unsubscribe messages for our peers so their * subscription state matches the one in the local server. */ if (subs.isEmpty() && destination.isClustered() && destination.getServerSettings().getRoutingMode() == RoutingMode.SERVER_TO_SERVER) sendSubscriptionToPeer(true, selector, subtopicString); subs.put(clientId, client); } monitorTimeout( client); // local operation, timeouts on remote host are not started until failover // Finally, if a new MessageClient was created, notify its created // listeners now that MessageClient's subscription state is setup. if (!subscriptionAlreadyExists) client.notifyCreatedListeners(); } } finally { releaseMessageClient(client); } }
/** * 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; }