/**
 * 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.broker;

import javax.jms.DeliveryMode;

import junit.framework.Test;

import org.apache.activemq.broker.region.policy.PolicyEntry;
import org.apache.activemq.broker.region.policy.VMPendingSubscriberMessageStoragePolicy;
import org.apache.activemq.command.ActiveMQDestination;
import org.apache.activemq.command.ConnectionInfo;
import org.apache.activemq.command.ConsumerInfo;
import org.apache.activemq.command.LocalTransactionId;
import org.apache.activemq.command.Message;
import org.apache.activemq.command.MessageAck;
import org.apache.activemq.command.ProducerInfo;
import org.apache.activemq.command.SessionInfo;

public class MessageExpirationTest extends BrokerTestSupport {

   public ActiveMQDestination destination;
   public int deliveryMode = DeliveryMode.NON_PERSISTENT;
   public int prefetch;
   public byte destinationType = ActiveMQDestination.QUEUE_TYPE;
   public boolean durableConsumer;

   protected Message createMessage(ProducerInfo producerInfo,
                                   ActiveMQDestination destination,
                                   int deliveryMode,
                                   int timeToLive) {
      Message message = createMessage(producerInfo, destination, deliveryMode);
      long now = System.currentTimeMillis();
      message.setTimestamp(now);
      message.setExpiration(now + timeToLive);
      return message;
   }

   public void initCombosForTestMessagesWaitingForUssageDecreaseExpire() {
      addCombinationValues("deliveryMode", new Object[]{Integer.valueOf(DeliveryMode.NON_PERSISTENT), Integer.valueOf(DeliveryMode.PERSISTENT)});
      addCombinationValues("destinationType", new Object[]{Byte.valueOf(ActiveMQDestination.TEMP_QUEUE_TYPE), Byte.valueOf(ActiveMQDestination.TEMP_TOPIC_TYPE), Byte.valueOf(ActiveMQDestination.QUEUE_TYPE), Byte.valueOf(ActiveMQDestination.TOPIC_TYPE)});
   }

   @Override
   protected BrokerService createBroker() throws Exception {
      BrokerService broker = new BrokerService();
      broker.setPersistent(false);
      return broker;
   }

   @Override
   protected PolicyEntry getDefaultPolicy() {
      PolicyEntry policy = super.getDefaultPolicy();
      // disable spooling
      policy.setPendingSubscriberPolicy(new VMPendingSubscriberMessageStoragePolicy());
      // have aggressive expiry period to ensure no deadlock or clash
      policy.setExpireMessagesPeriod(100);

      return policy;
   }

   public void testMessagesWaitingForUsageDecreaseExpire() throws Exception {

      // Start a producer
      final StubConnection connection = createConnection();
      ConnectionInfo connectionInfo = createConnectionInfo();
      SessionInfo sessionInfo = createSessionInfo(connectionInfo);
      final ProducerInfo producerInfo = createProducerInfo(sessionInfo);
      connection.send(connectionInfo);
      connection.send(sessionInfo);
      connection.send(producerInfo);

      // Start a consumer..
      final StubConnection connection2 = createConnection();
      ConnectionInfo connectionInfo2 = createConnectionInfo();
      SessionInfo sessionInfo2 = createSessionInfo(connectionInfo2);
      connection2.send(connectionInfo2);
      connection2.send(sessionInfo2);

      destination = createDestinationInfo(connection2, connectionInfo2, destinationType);
      ConsumerInfo consumerInfo2 = createConsumerInfo(sessionInfo2, destination);
      consumerInfo2.setPrefetchSize(1);
      connection2.request(consumerInfo2);

      // Reduce the limit so that only 1 message can flow through the broker
      // at a time.
      broker.getSystemUsage().getMemoryUsage().setLimit(1);

      final Message m1 = createMessage(producerInfo, destination, deliveryMode);
      final Message m2 = createMessage(producerInfo, destination, deliveryMode, 1000);
      final Message m3 = createMessage(producerInfo, destination, deliveryMode);
      final Message m4 = createMessage(producerInfo, destination, deliveryMode, 1000);

      // Produce in an async thread since the producer will be getting blocked
      // by the usage manager..
      new Thread() {
         @Override
         public void run() {
            // m1 and m3 should not expire.. but the others should.
            try {
               connection.send(m1);
               connection.send(m2);
               connection.send(m3);
               connection.send(m4);
            } catch (Exception e) {
               e.printStackTrace();
            }
         }
      }.start();

      // Make sure only 1 message was delivered due to prefetch == 1
      Message m = receiveMessage(connection2);
      assertNotNull(m);
      assertEquals(m1.getMessageId(), m.getMessageId());
      assertNoMessagesLeft(connection);

      // Sleep before we ack so that the messages expire on the usage manager
      Thread.sleep(1500);
      connection2.send(createAck(consumerInfo2, m, 1, MessageAck.STANDARD_ACK_TYPE));

      // 2nd message received should be m3.. it should have expired 2nd
      // message sent.
      m = receiveMessage(connection2);
      assertNotNull(m);
      assertEquals(m3.getMessageId(), m.getMessageId());

      // Sleep before we ack so that the messages expire on the usage manager
      Thread.sleep(1500);
      connection2.send(createAck(consumerInfo2, m, 1, MessageAck.STANDARD_ACK_TYPE));

      // And there should be no messages left now..
      assertNoMessagesLeft(connection2);

      connection.send(closeConnectionInfo(connectionInfo));
      connection.send(closeConnectionInfo(connectionInfo2));
   }

   public void initCombosForTestMessagesInLongTransactionExpire() {
      addCombinationValues("deliveryMode", new Object[]{Integer.valueOf(DeliveryMode.PERSISTENT), Integer.valueOf(DeliveryMode.NON_PERSISTENT)});
      addCombinationValues("destinationType", new Object[]{Byte.valueOf(ActiveMQDestination.QUEUE_TYPE), Byte.valueOf(ActiveMQDestination.TOPIC_TYPE), Byte.valueOf(ActiveMQDestination.TEMP_QUEUE_TYPE), Byte.valueOf(ActiveMQDestination.TEMP_TOPIC_TYPE)});
   }

   public void testMessagesInLongTransactionExpire() throws Exception {

      // Start a producer and consumer
      StubConnection connection = createConnection();
      ConnectionInfo connectionInfo = createConnectionInfo();
      SessionInfo sessionInfo = createSessionInfo(connectionInfo);
      ProducerInfo producerInfo = createProducerInfo(sessionInfo);
      connection.send(connectionInfo);
      connection.send(sessionInfo);
      connection.send(producerInfo);

      destination = createDestinationInfo(connection, connectionInfo, destinationType);

      ConsumerInfo consumerInfo = createConsumerInfo(sessionInfo, destination);
      consumerInfo.setPrefetchSize(1000);
      connection.send(consumerInfo);

      // Start the tx..
      LocalTransactionId txid = createLocalTransaction(sessionInfo);
      connection.send(createBeginTransaction(connectionInfo, txid));

      // m1 and m3 should not expire.. but the others should.
      Message m1 = createMessage(producerInfo, destination, deliveryMode);
      m1.setTransactionId(txid);
      connection.send(m1);
      Message m = createMessage(producerInfo, destination, deliveryMode, 1000);
      m.setTransactionId(txid);
      connection.send(m);
      Message m3 = createMessage(producerInfo, destination, deliveryMode);
      m3.setTransactionId(txid);
      connection.send(m3);
      m = createMessage(producerInfo, destination, deliveryMode, 1000);
      m.setTransactionId(txid);
      connection.send(m);

      // Sleep before we commit so that the messages expire on the commit
      // list..
      Thread.sleep(1500);
      connection.send(createCommitTransaction1Phase(connectionInfo, txid));

      m = receiveMessage(connection);
      assertNotNull(m);
      assertEquals(m1.getMessageId(), m.getMessageId());
      connection.send(createAck(consumerInfo, m, 1, MessageAck.STANDARD_ACK_TYPE));

      // 2nd message received should be m3.. it should have expired 2nd
      // message sent.
      m = receiveMessage(connection);
      assertNotNull(m);
      assertEquals(m3.getMessageId(), m.getMessageId());
      connection.send(createAck(consumerInfo, m, 1, MessageAck.STANDARD_ACK_TYPE));

      // And there should be no messages left now..
      assertNoMessagesLeft(connection);

      connection.send(closeConnectionInfo(connectionInfo));
   }

   public void initCombosForTestMessagesInSubscriptionPendingListExpire() {
      addCombinationValues("deliveryMode", new Object[]{Integer.valueOf(DeliveryMode.NON_PERSISTENT), Integer.valueOf(DeliveryMode.PERSISTENT)});
      addCombinationValues("destinationType", new Object[]{Byte.valueOf(ActiveMQDestination.QUEUE_TYPE), Byte.valueOf(ActiveMQDestination.TOPIC_TYPE), Byte.valueOf(ActiveMQDestination.TEMP_QUEUE_TYPE), Byte.valueOf(ActiveMQDestination.TEMP_TOPIC_TYPE)});
   }

   public void testMessagesInSubscriptionPendingListExpire() throws Exception {

      // Start a producer and consumer
      StubConnection connection = createConnection();
      ConnectionInfo connectionInfo = createConnectionInfo();
      SessionInfo sessionInfo = createSessionInfo(connectionInfo);
      ProducerInfo producerInfo = createProducerInfo(sessionInfo);
      connection.send(connectionInfo);
      connection.send(sessionInfo);
      connection.send(producerInfo);

      destination = createDestinationInfo(connection, connectionInfo, destinationType);

      ConsumerInfo consumerInfo = createConsumerInfo(sessionInfo, destination);
      consumerInfo.setPrefetchSize(1);
      connection.send(consumerInfo);

      // m1 and m3 should not expire.. but the others should.
      Message m1 = createMessage(producerInfo, destination, deliveryMode);
      connection.send(m1);
      connection.send(createMessage(producerInfo, destination, deliveryMode, 1000));
      Message m3 = createMessage(producerInfo, destination, deliveryMode);
      connection.send(m3);
      connection.send(createMessage(producerInfo, destination, deliveryMode, 1000));

      // Make sure only 1 message was delivered due to prefetch == 1
      Message m = receiveMessage(connection);
      assertNotNull(m);
      assertEquals(m1.getMessageId(), m.getMessageId());
      assertNoMessagesLeft(connection);

      // Sleep before we ack so that the messages expire on the pending list..
      Thread.sleep(1500);
      connection.send(createAck(consumerInfo, m, 1, MessageAck.STANDARD_ACK_TYPE));

      // 2nd message received should be m3.. it should have expired 2nd
      // message sent.
      m = receiveMessage(connection);
      assertNotNull(m);
      assertEquals(m3.getMessageId(), m.getMessageId());
      connection.send(createAck(consumerInfo, m, 1, MessageAck.STANDARD_ACK_TYPE));

      // And there should be no messages left now..
      assertNoMessagesLeft(connection);

      connection.send(closeConnectionInfo(connectionInfo));
   }

   public static Test suite() {
      return suite(MessageExpirationTest.class);
   }

   public static void main(String[] args) {
      junit.textui.TestRunner.run(suite());
   }

}
