/**
   * This implementation handles the following flags, in addition to those described in the {@link
   * SubscriptionManager} interface:
   *
   * <ul>
   *   <li>subscriberExecutionManagerTag - a tag to pass to execution manager (without setting any
   *       execution semantics / TaskPreprocessor); if not supplied and there is a subscriber, this
   *       will be inferred from the subscriber and set up with SingleThreadedScheduler (supply this
   *       flag with value null to prevent any task preprocessor from being set)
   *   <li>eventFilter - a Predicate&lt;SensorEvent&gt; instance to filter what events are delivered
   * </ul>
   *
   * @see SubscriptionManager#subscribe(Map, Entity, Sensor, SensorEventListener)
   */
  public synchronized <T> SubscriptionHandle subscribe(
      Map<String, Object> flags,
      Entity producer,
      Sensor<T> sensor,
      SensorEventListener<? super T> listener) {
    Subscription s = new Subscription(producer, sensor, listener);
    s.subscriber = flags.containsKey("subscriber") ? flags.remove("subscriber") : listener;
    if (flags.containsKey("subscriberExecutionManagerTag")) {
      s.subscriberExecutionManagerTag = flags.remove("subscriberExecutionManagerTag");
      s.subscriberExecutionManagerTagSupplied = true;
    } else {
      s.subscriberExecutionManagerTag =
          s.subscriber instanceof Entity
              ? "subscription-delivery-entity-"
                  + ((Entity) s.subscriber).getId()
                  + "["
                  + s.subscriber
                  + "]"
              : s.subscriber instanceof String
                  ? "subscription-delivery-string[" + s.subscriber + "]"
                  : s != null ? "subscription-delivery-object[" + s.subscriber + "]" : null;
      s.subscriberExecutionManagerTagSupplied = false;
    }
    s.eventFilter = (Predicate) flags.remove("eventFilter");
    s.flags = flags;

    if (LOG.isDebugEnabled())
      LOG.debug(
          "Creating subscription {} for {} on {} {} in {}",
          new Object[] {s, s.subscriber, producer, sensor, this});
    allSubscriptions.put(s.id, s);
    LanguageUtils.addToMapOfSets(
        subscriptionsByToken, makeEntitySensorToken(s.producer, s.sensor), s);
    if (s.subscriber != null) {
      LanguageUtils.addToMapOfSets(subscriptionsBySubscriber, s.subscriber, s);
    }
    if (!s.subscriberExecutionManagerTagSupplied && s.subscriberExecutionManagerTag != null) {
      ((BasicExecutionManager) em)
          .setTaskSchedulerForTag(s.subscriberExecutionManagerTag, SingleThreadedScheduler.class);
    }
    return s;
  }