/**
   * Reads a message from a destination up to some specific timeout period
   *
   * @param client The webclient
   * @param request
   * @param response
   * @throws ServletException
   * @throws IOException
   */
  protected void doMessages(
      AjaxWebClient client, HttpServletRequest request, HttpServletResponse response)
      throws JMSException, IOException {

    int messages = 0;
    // This is a poll for any messages

    long timeout = getReadTimeout(request);
    if (LOG.isDebugEnabled()) {
      LOG.debug("doMessage timeout=" + timeout);
    }

    // this is non-null if we're resuming the continuation.
    // attributes set in AjaxListener
    UndeliveredAjaxMessage undelivered_message = null;
    Message message = null;
    undelivered_message = (UndeliveredAjaxMessage) request.getAttribute("undelivered_message");
    if (undelivered_message != null) {
      message = (Message) undelivered_message.getMessage();
    }

    synchronized (client) {
      List<MessageConsumer> consumers = client.getConsumers();
      MessageAvailableConsumer consumer = null;
      if (undelivered_message != null) {
        consumer = (MessageAvailableConsumer) undelivered_message.getConsumer();
      }

      if (message == null) {
        // Look for a message that is ready to go
        for (int i = 0; message == null && i < consumers.size(); i++) {
          consumer = (MessageAvailableConsumer) consumers.get(i);
          if (consumer.getAvailableListener() == null) {
            continue;
          }

          // Look for any available messages
          message = consumer.receive(10);
          if (LOG.isDebugEnabled()) {
            LOG.debug("received " + message + " from " + consumer);
          }
        }
      }

      // prepare the response
      response.setContentType("text/xml");
      response.setHeader("Cache-Control", "no-cache");

      if (message == null && client.getListener().getUndeliveredMessages().size() == 0) {
        Continuation continuation = ContinuationSupport.getContinuation(request);

        if (continuation.isExpired()) {
          response.setStatus(HttpServletResponse.SC_OK);
          StringWriter swriter = new StringWriter();
          PrintWriter writer = new PrintWriter(swriter);
          writer.println("<ajax-response>");
          writer.print("</ajax-response>");

          writer.flush();
          String m = swriter.toString();
          response.getWriter().println(m);

          return;
        }

        continuation.setTimeout(timeout);
        continuation.suspend();
        LOG.debug("Suspending continuation " + continuation);

        // Fetch the listeners
        AjaxListener listener = client.getListener();
        listener.access();

        // register this continuation with our listener.
        listener.setContinuation(continuation);

        return;
      }

      StringWriter swriter = new StringWriter();
      PrintWriter writer = new PrintWriter(swriter);

      Map<MessageAvailableConsumer, String> consumerIdMap = client.getIdMap();
      Map<MessageAvailableConsumer, String> consumerDestinationNameMap =
          client.getDestinationNameMap();
      response.setStatus(HttpServletResponse.SC_OK);
      writer.println("<ajax-response>");

      // Send any message we already have
      if (message != null) {
        String id = consumerIdMap.get(consumer);
        String destinationName = consumerDestinationNameMap.get(consumer);
        LOG.debug("sending pre-existing message");
        writeMessageResponse(writer, message, id, destinationName);

        messages++;
      }

      // send messages buffered while continuation was unavailable.
      LinkedList<UndeliveredAjaxMessage> undeliveredMessages =
          ((AjaxListener) consumer.getAvailableListener()).getUndeliveredMessages();
      LOG.debug("Send " + undeliveredMessages.size() + " unconsumed messages");
      synchronized (undeliveredMessages) {
        for (Iterator<UndeliveredAjaxMessage> it = undeliveredMessages.iterator(); it.hasNext(); ) {
          messages++;
          UndeliveredAjaxMessage undelivered = it.next();
          Message msg = (Message) undelivered.getMessage();
          consumer = (MessageAvailableConsumer) undelivered.getConsumer();
          String id = consumerIdMap.get(consumer);
          String destinationName = consumerDestinationNameMap.get(consumer);
          LOG.debug("sending undelivered/buffered messages");
          LOG.debug("msg:" + msg + ", id:" + id + ", destinationName:" + destinationName);
          writeMessageResponse(writer, msg, id, destinationName);
          it.remove();
          if (messages >= maximumMessages) {
            break;
          }
        }
      }

      // Send the rest of the messages
      for (int i = 0; i < consumers.size() && messages < maximumMessages; i++) {
        consumer = (MessageAvailableConsumer) consumers.get(i);
        if (consumer.getAvailableListener() == null) {
          continue;
        }

        // Look for any available messages
        while (messages < maximumMessages) {
          message = consumer.receiveNoWait();
          if (message == null) {
            break;
          }
          messages++;
          String id = consumerIdMap.get(consumer);
          String destinationName = consumerDestinationNameMap.get(consumer);
          LOG.debug("sending final available messages");
          writeMessageResponse(writer, message, id, destinationName);
        }
      }

      writer.print("</ajax-response>");

      writer.flush();
      String m = swriter.toString();
      response.getWriter().println(m);
    }
  }
  /**
   * Sends a message to a destination or manage subscriptions. If the the content type of the POST
   * is <code>application/x-www-form-urlencoded</code>, then the form parameters "destination",
   * "message" and "type" are used to pass a message or a subscription. If multiple messages or
   * subscriptions are passed in a single post, then additional parameters are shortened to "dN",
   * "mN" and "tN" where N is an index starting from 1. The type is either "send", "listen" or
   * "unlisten". For send types, the message is the text of the TextMessage, otherwise it is the ID
   * to be used for the subscription. If the content type is not <code>
   * application/x-www-form-urlencoded</code>, then the body of the post is sent as the message to a
   * destination that is derived from a query parameter, the URL or the default destination.
   *
   * @param request
   * @param response
   * @throws ServletException
   * @throws IOException
   */
  protected void doPost(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {

    // lets turn the HTTP post into a JMS Message
    AjaxWebClient client = getAjaxWebClient(request);
    String messageIds = "";

    synchronized (client) {
      if (LOG.isDebugEnabled()) {
        LOG.debug(
            "POST client="
                + client
                + " session="
                + request.getSession().getId()
                + " clientId="
                + request.getParameter("clientId")
                + " info="
                + request.getPathInfo()
                + " contentType="
                + request.getContentType());
        // dump(request.getParameterMap());
      }

      int messages = 0;

      // loop until no more messages
      while (true) {
        // Get the message parameters. Multiple messages are encoded
        // with more compact parameter names.
        String destinationName =
            request.getParameter(messages == 0 ? "destination" : ("d" + messages));

        if (destinationName == null) {
          destinationName = request.getHeader("destination");
        }

        String message = request.getParameter(messages == 0 ? "message" : ("m" + messages));
        String type = request.getParameter(messages == 0 ? "type" : ("t" + messages));

        if (destinationName == null || message == null || type == null) {
          break;
        }

        try {
          Destination destination = getDestination(client, request, destinationName);

          if (LOG.isDebugEnabled()) {
            LOG.debug(
                messages
                    + " destination="
                    + destinationName
                    + " message="
                    + message
                    + " type="
                    + type);
            LOG.debug(destination + " is a " + destination.getClass().getName());
          }

          messages++;

          if ("listen".equals(type)) {
            AjaxListener listener = client.getListener();
            Map<MessageAvailableConsumer, String> consumerIdMap = client.getIdMap();
            Map<MessageAvailableConsumer, String> consumerDestinationNameMap =
                client.getDestinationNameMap();
            client.closeConsumer(destination); // drop any existing
            // consumer.
            MessageAvailableConsumer consumer =
                (MessageAvailableConsumer)
                    client.getConsumer(destination, request.getHeader(WebClient.selectorName));

            consumer.setAvailableListener(listener);
            consumerIdMap.put(consumer, message);
            consumerDestinationNameMap.put(consumer, destinationName);
            if (LOG.isDebugEnabled()) {
              LOG.debug("Subscribed: " + consumer + " to " + destination + " id=" + message);
            }
          } else if ("unlisten".equals(type)) {
            Map<MessageAvailableConsumer, String> consumerIdMap = client.getIdMap();
            Map<MessageAvailableConsumer, String> consumerDestinationNameMap =
                client.getDestinationNameMap();
            MessageAvailableConsumer consumer =
                (MessageAvailableConsumer)
                    client.getConsumer(destination, request.getHeader(WebClient.selectorName));

            consumer.setAvailableListener(null);
            consumerIdMap.remove(consumer);
            consumerDestinationNameMap.remove(consumer);
            client.closeConsumer(destination);
            if (LOG.isDebugEnabled()) {
              LOG.debug("Unsubscribed: " + consumer);
            }
          } else if ("send".equals(type)) {
            TextMessage text = client.getSession().createTextMessage(message);
            appendParametersToMessage(request, text);

            client.send(destination, text);
            messageIds += text.getJMSMessageID() + "\n";
            if (LOG.isDebugEnabled()) {
              LOG.debug("Sent " + message + " to " + destination);
            }
          } else {
            LOG.warn("unknown type " + type);
          }

        } catch (JMSException e) {
          LOG.warn("jms", e);
        }
      }
    }

    if ("true".equals(request.getParameter("poll"))) {
      try {
        // TODO return message IDs
        doMessages(client, request, response);
      } catch (JMSException e) {
        throw new ServletException("JMS problem: " + e, e);
      }
    } else {
      // handle simple POST of a message
      if (request.getContentLength() != 0
          && (request.getContentType() == null
              || !request
                  .getContentType()
                  .toLowerCase()
                  .startsWith("application/x-www-form-urlencoded"))) {
        try {
          Destination destination = getDestination(client, request);
          String body = getPostedMessageBody(request);
          TextMessage message = client.getSession().createTextMessage(body);
          appendParametersToMessage(request, message);

          client.send(destination, message);
          if (LOG.isDebugEnabled()) {
            LOG.debug("Sent to destination: " + destination + " body: " + body);
          }
          messageIds += message.getJMSMessageID() + "\n";
        } catch (JMSException e) {
          throw new ServletException(e);
        }
      }

      response.setContentType("text/plain");
      response.setHeader("Cache-Control", "no-cache");
      response.getWriter().print(messageIds);
    }
  }