public static int processNotification(String queueArn, String remoteAddress) {

    int messageCount = 0;

    long ts1 = System.currentTimeMillis();
    CMBControllerServlet.valueAccumulator.initializeAllCounters();

    contextQueues.putIfAbsent(queueArn, new ConcurrentLinkedQueue<AsyncContext>());
    ConcurrentLinkedQueue<AsyncContext> contextQueue = contextQueues.get(queueArn);

    AsyncContext asyncContext = contextQueue.poll();

    if (asyncContext == null) {
      logger.debug(
          "event=no_pending_receive queue_arn=" + queueArn + " remote_address=" + remoteAddress);
      return messageCount;
    }

    if (asyncContext.getRequest() == null) {
      logger.info(
          "event=skipping_invalid_context queue_arn="
              + queueArn
              + " remote_address="
              + remoteAddress);
      return messageCount;
    }

    if (!(asyncContext.getRequest() instanceof CQSHttpServletRequest)) {
      logger.info(
          "event=skipping_invalid_request queue_arn="
              + queueArn
              + " remote_address="
              + remoteAddress);
      return messageCount;
    }

    CQSHttpServletRequest request = (CQSHttpServletRequest) asyncContext.getRequest();

    // skip if request is already finished or outdated

    if (!request.isActive()
        || System.currentTimeMillis() - request.getRequestReceivedTimestamp()
            > request.getWaitTime()) {
      logger.info(
          "event=skipping_outdated_context queue_arn="
              + queueArn
              + " remote_address="
              + remoteAddress);
      return messageCount;
    }

    logger.debug(
        "event=notification_received queue_arn=" + queueArn + " remote_address=" + remoteAddress);

    try {

      CQSQueue queue = request.getQueue();
      List<CQSMessage> messageList =
          PersistenceFactory.getCQSMessagePersistence()
              .receiveMessage(queue, request.getReceiveAttributes());

      if (messageList.size() > 0) {

        messageCount = messageList.size();

        List<String> receiptHandles = new ArrayList<String>();

        for (CQSMessage message : messageList) {
          receiptHandles.add(message.getReceiptHandle());
        }

        request.setReceiptHandles(receiptHandles);
        request.setAttribute("lp", "yy"); // found lp call with messages
        CQSMonitor.getInstance()
            .addNumberOfMessagesReturned(queue.getRelativeUrl(), messageList.size());
        String out =
            CQSMessagePopulator.getReceiveMessageResponseAfterSerializing(
                messageList, request.getFilterAttributes(), request.getFilterMessageAttributes());
        Action.writeResponse(out, (HttpServletResponse) asyncContext.getResponse());
        long lp_ms = System.currentTimeMillis() - ts1;
        request.setAttribute("lp_ms", lp_ms);
        String cass_msString =
            String.valueOf(
                CQSControllerServlet.valueAccumulator.getCounter(AccumulatorName.CassandraTime));
        request.setAttribute("cass_ms", cass_msString);
        request.setAttribute(
            "cass_num_rd",
            CQSControllerServlet.valueAccumulator.getCounter(AccumulatorName.CassandraRead));
        request.setAttribute(
            "cass_num_wr",
            CQSControllerServlet.valueAccumulator.getCounter(AccumulatorName.CassandraWrite));
        request.setAttribute(
            "redis_ms",
            CQSControllerServlet.valueAccumulator.getCounter(AccumulatorName.RedisTime));
        request.setAttribute(
            "io_ms", CQSControllerServlet.valueAccumulator.getCounter(AccumulatorName.IOTime));

        asyncContext.complete();

      } else {

        // if there's longpoll time left, put back on queue

        if (request.getWaitTime()
                - System.currentTimeMillis()
                + request.getRequestReceivedTimestamp()
            > 0) {
          logger.info(
              "event=no_messages_found_for_longpoll_receive action=re_queueing time_left_ms="
                  + (request.getWaitTime()
                      - System.currentTimeMillis()
                      + request.getRequestReceivedTimestamp())
                  + " queue_arn="
                  + queueArn
                  + " remote_address="
                  + remoteAddress);
          contextQueue.offer(asyncContext);
        }
      }

    } catch (Exception ex) {
      logger.error("event=longpoll_queue_error queue_arn=" + queueArn, ex);
    } finally {
      CMBControllerServlet.valueAccumulator.deleteAllCounters();
    }

    return messageCount;
  }
  @Override
  public boolean doAction(User user, AsyncContext asyncContext) throws Exception {

    CQSHttpServletRequest request = (CQSHttpServletRequest) asyncContext.getRequest();
    HttpServletResponse response = (HttpServletResponse) asyncContext.getResponse();

    CQSQueue queue = CQSCache.getCachedQueue(user, request);
    List<CQSMessage> msgList = new ArrayList<CQSMessage>();
    List<String> idList = new ArrayList<String>();
    List<CQSBatchResultErrorEntry> invalidBodyIdList = new ArrayList<CQSBatchResultErrorEntry>();

    int totalMessageSize = 0;
    int index = 1;

    String suppliedId =
        request.getParameter(this.actionName + CQSConstants.REQUEST_ENTRY + index + ".Id");
    String messageBody =
        request.getParameter(
            this.actionName + CQSConstants.REQUEST_ENTRY + index + "." + CQSConstants.MESSAGE_BODY);

    while (suppliedId != null) {

      if (!Util.isValidId(suppliedId)) {
        throw new CMBException(
            CQSErrorCodes.InvalidBatchEntryId,
            "Id "
                + suppliedId
                + " is invalid. Only alphanumeric, hyphen, and underscore are allowed. It can be at most "
                + CMBProperties.getInstance().getCQSMaxMessageSuppliedIdLength()
                + " letters long.");
      }

      if (idList.contains(suppliedId)) {
        throw new CMBException(
            CQSErrorCodes.BatchEntryIdsNotDistinct, "Id " + suppliedId + " repeated");
      }

      idList.add(suppliedId);

      if (messageBody == null || messageBody.isEmpty()) {
        invalidBodyIdList.add(
            new CQSBatchResultErrorEntry(
                suppliedId,
                true,
                "EmptyValue",
                "No value found for "
                    + this.actionName
                    + CQSConstants.REQUEST_ENTRY
                    + index
                    + "."
                    + CQSConstants.MESSAGE_BODY));
      } else if (!com.comcast.cmb.common.util.Util.isValidUnicode(messageBody)) {
        invalidBodyIdList.add(
            new CQSBatchResultErrorEntry(
                suppliedId,
                true,
                "InvalidMessageContents",
                "Invalid character was found in the message body."));
      } else {

        HashMap<String, String> attributes = new HashMap<String, String>();
        String delaySecondsStr =
            request.getParameter(
                this.actionName
                    + CQSConstants.REQUEST_ENTRY
                    + index
                    + "."
                    + CQSConstants.DELAY_SECONDS);

        if (delaySecondsStr != null) {

          Integer delaySeconds = 0;

          try {
            delaySeconds = Integer.parseInt(delaySecondsStr);
          } catch (NumberFormatException ex) {
            throw new CMBException(
                CMBErrorCodes.InvalidParameterValue, "DelaySeconds must be integer value");
          }

          if (delaySeconds < 0
              || delaySeconds > CMBProperties.getInstance().getCQSMaxMessageDelaySeconds()) {
            throw new CMBException(
                CMBErrorCodes.InvalidParameterValue,
                "DelaySeconds should be from 0 to "
                    + CMBProperties.getInstance().getCQSMaxMessageDelaySeconds());
          } else {
            attributes.put(CQSConstants.DELAY_SECONDS, "" + delaySeconds);
          }
        }

        attributes.put(CQSConstants.SENDER_ID, user.getUserId());
        attributes.put(CQSConstants.SENT_TIMESTAMP, "" + Calendar.getInstance().getTimeInMillis());
        attributes.put(CQSConstants.APPROXIMATE_RECEIVE_COUNT, "0");
        attributes.put(CQSConstants.APPROXIMATE_FIRST_RECEIVE_TIMESTAMP, "");

        CQSMessage msg = new CQSMessage(messageBody, attributes);

        msg.setSuppliedMessageId(suppliedId);
        msgList.add(msg);
      }

      if (msgList.size() > CMBProperties.getInstance().getCQSMaxMessageCountBatch()) {
        throw new CMBException(
            CQSErrorCodes.TooManyEntriesInBatchRequest,
            "Maximum number of entries per request are "
                + CMBProperties.getInstance().getCQSMaxMessageCountBatch()
                + ". You have sent "
                + msgList.size()
                + ".");
      }

      totalMessageSize += messageBody == null ? 0 : messageBody.length();

      if (totalMessageSize > CMBProperties.getInstance().getCQSMaxMessageSizeBatch()) {
        throw new CMBException(
            CQSErrorCodes.BatchRequestTooLong,
            "Batch requests cannot be longer than "
                + CMBProperties.getInstance().getCQSMaxMessageSizeBatch()
                + " bytes");
      }

      index++;

      suppliedId =
          request.getParameter(this.actionName + CQSConstants.REQUEST_ENTRY + index + ".Id");
      messageBody =
          request.getParameter(
              this.actionName
                  + CQSConstants.REQUEST_ENTRY
                  + index
                  + "."
                  + CQSConstants.MESSAGE_BODY);
    }

    if (msgList.size() == 0) {
      throw new CMBException(
          CMBErrorCodes.InvalidQueryParameter,
          "Both user supplied message Id and message body are required");
    }

    int shard = 0;

    if (queue.getNumberOfShards() > 1) {
      shard = rand.nextInt(queue.getNumberOfShards());
    }

    Map<String, String> result =
        PersistenceFactory.getCQSMessagePersistence().sendMessageBatch(queue, shard, msgList);

    try {
      CQSLongPollSender.send(queue.getArn());
    } catch (Exception ex) {
      logger.warn("event=failed_to_send_longpoll_notification", ex);
    }

    List<String> receiptHandles = new ArrayList<String>();

    for (CQSMessage message : msgList) {
      message.setMessageId(result.get(message.getSuppliedMessageId()));
      message.setReceiptHandle(result.get(message.getSuppliedMessageId()));
      receiptHandles.add(message.getReceiptHandle());
    }

    request.setReceiptHandles(receiptHandles);
    String out = CQSMessagePopulator.getSendMessageBatchResponse(msgList, invalidBodyIdList);
    writeResponse(out, response);

    CQSMonitor.getInstance().addNumberOfMessagesReceived(queue.getRelativeUrl(), msgList.size());

    return true;
  }