/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.activemq.bugs;

import java.util.concurrent.TimeoutException;

import javax.jms.Connection;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageProducer;
import javax.jms.Session;

import org.apache.activemq.EmbeddedBrokerTestSupport;
import org.apache.activemq.broker.region.Queue;
import org.apache.activemq.command.ActiveMQDestination;
import org.junit.Assert;

public class DoubleExpireTest extends EmbeddedBrokerTestSupport {

   private static final long MESSAGE_TTL_MILLIS = 1000;
   private static final long MAX_TEST_TIME_MILLIS = 60000;

   public void setUp() throws Exception {
      setAutoFail(true);
      setMaxTestTime(MAX_TEST_TIME_MILLIS);
      super.setUp();
   }

   /**
    * This test verifies that a message that expires can be be resent to queue
    * with a new expiration and that it will be processed as a new message and
    * allowed to re-expire.
    * <p>
    * <b>NOTE:</b> This test fails on AMQ 5.4.2 because the originalExpiration
    * timestamp is not cleared when the message is resent.
    */
   public void testDoubleExpireWithoutMove() throws Exception {
      // Create the default dead letter queue.
      final ActiveMQDestination DLQ = createDestination("ActiveMQ.DLQ");

      Connection conn = createConnection();
      try {
         conn.start();
         Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);

         // Verify that the test queue and DLQ are empty.
         Assert.assertEquals(0, getSize(destination));
         Assert.assertEquals(0, getSize(DLQ));

         // Enqueue a message to the test queue that will expire after 1s.
         MessageProducer producer = session.createProducer(destination);
         Message testMessage = session.createTextMessage("test message");
         producer.send(testMessage, Message.DEFAULT_DELIVERY_MODE, Message.DEFAULT_PRIORITY, MESSAGE_TTL_MILLIS);
         Assert.assertEquals(1, getSize(destination));

         // Wait for the message to expire.
         waitForSize(destination, 0, MAX_TEST_TIME_MILLIS);
         Assert.assertEquals(1, getSize(DLQ));

         // Consume the message from the DLQ and re-enqueue it to the test
         // queue so that it expires after 1s.
         MessageConsumer consumer = session.createConsumer(DLQ);
         Message expiredMessage = consumer.receive();
         Assert.assertEquals(testMessage.getJMSMessageID(), expiredMessage.getJMSMessageID());

         producer.send(expiredMessage, Message.DEFAULT_DELIVERY_MODE, Message.DEFAULT_PRIORITY, MESSAGE_TTL_MILLIS);
         Assert.assertEquals(1, getSize(destination));
         Assert.assertEquals(0, getSize(DLQ));

         // Verify that the resent message is "different" in that it has
         // another ID.
         Assert.assertNotSame(testMessage.getJMSMessageID(), expiredMessage.getJMSMessageID());

         // Wait for the message to re-expire.
         waitForSize(destination, 0, MAX_TEST_TIME_MILLIS);
         Assert.assertEquals(1, getSize(DLQ));

         // Re-consume the message from the DLQ.
         Message reexpiredMessage = consumer.receive();
         Assert.assertEquals(expiredMessage.getJMSMessageID(), reexpiredMessage.getJMSMessageID());
      }
      finally {
         conn.close();
      }
   }

   /**
    * A helper method that returns the embedded broker's implementation of a
    * JMS queue.
    */
   private Queue getPhysicalDestination(ActiveMQDestination destination) throws Exception {
      return (Queue) broker.getAdminView().getBroker().getDestinationMap().get(destination);
   }

   /**
    * A helper method that returns the size of the specified queue/topic.
    */
   private long getSize(ActiveMQDestination destination) throws Exception {
      return getPhysicalDestination(destination) != null ? getPhysicalDestination(destination).getDestinationStatistics().getMessages().getCount() : 0;
   }

   /**
    * A helper method that waits for a destination to reach a certain size.
    */
   private void waitForSize(ActiveMQDestination destination,
                            int size,
                            long timeoutMillis) throws Exception, TimeoutException {
      long startTimeMillis = System.currentTimeMillis();

      while (getSize(destination) != size && System.currentTimeMillis() < (startTimeMillis + timeoutMillis)) {
         Thread.sleep(250);
      }

      if (getSize(destination) != size) {
         throw new TimeoutException("Destination " + destination.getPhysicalName() + " did not reach size " + size + " within " + timeoutMillis + "ms.");
      }
   }
}
