/**
   * 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);
        }
      }
    }
  }
Beispiel #4
0
  /**
   * @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);
    }
  }
Beispiel #6
0
  /**
   * 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;
  }