/*
   * (non-Javadoc)
   *
   * @see
   * org.mpilone.hazelcastmq.core.HazelcastMQConsumer#setMessageListener(org
   * .mpilone.hazelcastmq.core.HazelcastMQMessageHandler)
   */
  @Override
  public void setMessageListener(HazelcastMQMessageListener messageListener) {
    this.messageListener = messageListener;

    if (messageListener != null && active) {
      // Signal that we're dispatch ready so the context will drain the queue if
      // there are pending messages.
      hazelcastMQContext.onConsumerDispatchReady(id);
    }
  }
  /*
   * (non-Javadoc)
   *
   * @see org.mpilone.hazelcastmq.core.HazelcastMQConsumer#close()
   */
  @Override
  public void close() {

    receiveLock.lock();
    try {
      hazelcastMQContext.onConsumerClose(id);
      closed = true;
      receiveCondition.signalAll();
    } finally {
      receiveLock.unlock();
    }
  }
  /**
   * Starts the consumer which will register a queue or topic listener with Hazelcast and enable
   * message consumption (push or pull). If the consumer is already active, this method does
   * nothing.
   */
  void start() {
    if (active) {
      return;
    }

    receiveLock.lock();
    try {
      // Start listening for events. We currently always listen for events even
      // if we don't have a message listener. If this has a performance impact
      // on Hazelcast we may want to only listen if there is a registered
      // message listener that we need to notify.
      IQueue<byte[]> queue = hazelcastMQContext.resolveQueue(destination);
      if (queue != null) {
        // Get the raw queue outside of any transactional context so we can add
        // an item listener.
        queue = config.getHazelcastInstance().getQueue(queue.getName());
        queueListener = new HzQueueListener(queue);
      }

      // If we are a consumer on a topic, immediately start listening for events
      // so we can buffer them for (a)synchronous consumption.
      ITopic<byte[]> topic = hazelcastMQContext.resolveTopic(destination);
      if (topic != null) {
        topicListener = new HzTopicListener(topic);
      }

      active = true;

      if (messageListener != null) {
        // We have a message listener, so tell the context to drain the dispatch
        // ready queues.
        hazelcastMQContext.onConsumerDispatchReady(id);
      } else {
        // Signal that any receive requests can continue.
        receiveCondition.signalAll();
      }
    } finally {
      receiveLock.unlock();
    }
  }
  /**
   * Constructs the consumer which will read from the given destination and is a child of the given
   * context.
   *
   * @param destination the destination that this consumer will read from
   * @param hazelcastMQContext the parent context of this consumer
   */
  DefaultHazelcastMQConsumer(String destination, DefaultHazelcastMQContext hazelcastMQContext) {
    super();

    this.destination = destination;
    this.receiveLock = new ReentrantLock();
    this.receiveCondition = receiveLock.newCondition();
    this.closed = false;
    this.active = false;

    this.hazelcastMQContext = hazelcastMQContext;
    this.config = hazelcastMQContext.getHazelcastMQInstance().getConfig();

    HazelcastInstance hazelcast =
        this.hazelcastMQContext.getHazelcastMQInstance().getConfig().getHazelcastInstance();

    IdGenerator idGenerator = hazelcast.getIdGenerator("hazelcastmqconsumer");
    this.id = "hazelcastmqconsumer-" + String.valueOf(idGenerator.newId());
  }
  /**
   * Attempts to receive a message using the given strategy. The method will continue to attempt to
   * receive until either {@link ReceiveStrategy#isRetryable()} returns false, a message is
   * received, or the consumer is stopped.
   *
   * @param strategy the strategy to use for receiving the message and determining retries
   * @return the message or null if no message was received
   */
  private HazelcastMQMessage doReceive(ReceiveStrategy strategy) {

    HazelcastMQMessage msg = null;

    do {
      receiveLock.lock();
      try {

        IQueue<byte[]> queue = hazelcastMQContext.resolveQueue(destination);

        if (queue == null && topicListener == null) {
          throw new HazelcastMQException(
              format("Destination cannot be resolved [%s].", destination));
        } else if (queue == null) {
          queue = topicListener.getQueue();
        }

        byte[] msgData = strategy.receive(queue);
        if (msgData != null) {
          msg = config.getMessageConverter().toMessage(msgData);
        }

        // Check for message expiration if we have a message with expiration
        // time.
        if (msg != null && msg.getHeaders().get(Headers.EXPIRATION) != null) {
          long expirationTime = Long.parseLong(msg.getHeaders().get(Headers.EXPIRATION));

          if (expirationTime != 0 && expirationTime <= System.currentTimeMillis()) {
            log.info("Dropping message [{}] because it has expired.", msg.getId());
            msg = null;
          }
        }
      } finally {
        receiveLock.unlock();
      }
    } while (msg == null && !closed && strategy.isRetryable());

    return msg;
  }