/** 
 * 
 * Copyright 2004 Protique Ltd
 * 
 * Licensed 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.activemq;

import java.util.ArrayList;

import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;

import org.activemq.test.JmsResourceProvider;
import org.activemq.test.TestSupport;

/**
 * @version $Revision: 1.2 $
 */
abstract public class JmsTransactionTestSupport extends TestSupport {

    protected ConnectionFactory connectionFactory;
    protected Connection connection;
    protected Session session;
    protected MessageConsumer consumer;
    protected MessageProducer producer;
    protected JmsResourceProvider resourceProvider;
    protected Destination destination;

    public JmsTransactionTestSupport() {
        super();
    }

    public JmsTransactionTestSupport(String name) {
        super(name);
    }

    public void testSendReceiveTransactedBatches() throws Exception {

        int batchCount = 10;
        int batchSize = 20;
        TextMessage message = session.createTextMessage("Batch Message");

        for( int j=0; j < batchCount; j++) {
            System.out.println("Producing bacth "+j+" of "+batchSize+" messages");
            for( int i=0; i < batchSize; i++) {
                producer.send(message);
            }        
            session.commit();

            System.out.println("Consuming bacth "+j+" of "+batchSize+" messages");
            for( int i=0; i < batchSize; i++) {
                message = (TextMessage) consumer.receive(1000*5);
                assertNotNull("Received only "+i+" messages in batch "+j, message);
                assertEquals("Batch Message", message.getText());
            }
            session.commit();
        }
    }

    public void testSendRollback() throws Exception {

        Message[] outbound = new Message[]{
            session.createTextMessage("First Message"),
            session.createTextMessage("Second Message")
        };

        producer.send(outbound[0]);
        session.commit();
        producer.send(session.createTextMessage("I'm going to get rolled back."));
        session.rollback();
        producer.send(outbound[1]);
        session.commit();

        ArrayList messages = new ArrayList();
        System.out.println("About to consume message 1");
        Message message = consumer.receive(1000);
        messages.add(message);
        System.out.println("Received: " + message);

        System.out.println("About to consume message 2");
        message = consumer.receive(4000);
        messages.add(message);
        System.out.println("Received: " + message);

        session.commit();

        Message inbound[] = new Message[messages.size()];
        messages.toArray(inbound);

        assertTextMessagesEqual("Rollback did not work.", outbound, inbound);
    }

    public void testReceiveRollback() throws Exception {

        Message[] outbound = new Message[]{
            session.createTextMessage("First Message"),
            session.createTextMessage("Second Message")
        };

        // lets consume any outstanding messages from previous test runs
        while (consumer.receive(1000) != null) {
        }
        session.commit();

        producer.send(outbound[0]);
        producer.send(outbound[1]);
        session.commit();

        System.out.println("Sent 0: " + outbound[0]);
        System.out.println("Sent 1: " + outbound[1]);

        ArrayList messages = new ArrayList();
        Message message = consumer.receive(1000);
        messages.add(message);
        assertEquals(outbound[0], message);
        session.commit();

        // rollback so we can get that last message again.
        message = consumer.receive(1000);
        assertNotNull(message);
        assertEquals(outbound[1], message);
        session.rollback();

        // Consume again.. the previous message should
        // get redelivered.
        message = consumer.receive(5000);
        assertNotNull("Should have re-received the message again!", message);
        messages.add(message);
        session.commit();

        Message inbound[] = new Message[messages.size()];
        messages.toArray(inbound);

        assertTextMessagesEqual("Rollback did not work", outbound, inbound);
    }

    public void testReceiveTwoThenRollback() throws Exception {

        Message[] outbound = new Message[]{
            session.createTextMessage("First Message"),
            session.createTextMessage("Second Message")
        };

        // lets consume any outstanding messages from previous test runs
        while (consumer.receive(1000) != null) {
        }
        session.commit();

        producer.send(outbound[0]);
        producer.send(outbound[1]);
        session.commit();

        System.out.println("Sent 0: " + outbound[0]);
        System.out.println("Sent 1: " + outbound[1]);

        ArrayList messages = new ArrayList();
        Message message = consumer.receive(1000);
        assertEquals(outbound[0], message);

        message = consumer.receive(1000);
        assertNotNull(message);
        assertEquals(outbound[1], message);
        session.rollback();

        // Consume again.. the previous message should
        // get redelivered.
        message = consumer.receive(5000);
        assertNotNull("Should have re-received the first message again!", message);
        messages.add(message);
        assertEquals(outbound[0], message);

        message = consumer.receive(5000);
        assertNotNull("Should have re-received the second message again!", message);
        messages.add(message);
        assertEquals(outbound[1], message);
        session.commit();

        Message inbound[] = new Message[messages.size()];
        messages.toArray(inbound);

        assertTextMessagesEqual("Rollback did not work", outbound, inbound);
    }
    
    public void testReceiveTwoThenRollbackManyTimes() throws Exception {
        for( int i=0; i < 5 ; i++ )
            testReceiveTwoThenRollback();
    }            

    public void testSendRollbackWithPrefetchOfOne() throws Exception {
        setPrefetchToOne();
        testSendRollback();
    }

    public void testReceiveRollbackWithPrefetchOfOne() throws Exception {
        setPrefetchToOne();
        testReceiveRollback();
    }
    
    /*
     * see http://jira.codehaus.org/browse/AMQ-143 
     * 
     */ 
    public void testCloseConsumerBeforeCommit() throws Exception {

        TextMessage[] outbound = new TextMessage[]{
            session.createTextMessage("First Message"),
            session.createTextMessage("Second Message")
        };

        // lets consume any outstanding messages from previous test runs
        while (consumer.receive(1000) != null) {
        }
        session.commit();

        producer.send(outbound[0]);
        producer.send(outbound[1]);
        session.commit();

        System.out.println("Sent 0: " + outbound[0]);
        System.out.println("Sent 1: " + outbound[1]);

        ArrayList messages = new ArrayList();
        TextMessage message = (TextMessage) consumer.receive(1000);
        assertEquals(outbound[0].getText(), message.getText());        
        // Close the consumer before the commit.  This should not cause the received message
        // to rollback.
        consumer.close();
        session.commit();

        // Create a new consumer
        consumer = resourceProvider.createConsumer(session, destination);
        System.out.println("Created consumer: " + consumer);

        message = (TextMessage) consumer.receive(1000);
        assertEquals(outbound[1].getText(), message.getText());
        session.commit();
    }
    
    protected abstract JmsResourceProvider getJmsResourceProvider();

    protected void setUp() throws Exception {
        super.setUp();

        resourceProvider = getJmsResourceProvider();
        topic = resourceProvider.isTopic();
        
        // We will be using transacted sessions.
        resourceProvider.setTransacted(true);

        connectionFactory = resourceProvider.createConnectionFactory();
        reconnect();
    }

    /**
     * @throws JMSException
     */
    protected void reconnect() throws JMSException {
        
        Connection t = resourceProvider.createConnection(connectionFactory);
        if( connection != null ) {
            // Close the previous connection.
            connection.close();
        }
        connection = t;
        
        session = resourceProvider.createSession(connection);
        destination = resourceProvider.createDestination(session, getSubject());
        producer = resourceProvider.createProducer(session, destination);
        consumer = resourceProvider.createConsumer(session, destination);
        connection.start();
    }

    protected void tearDown() throws Exception {
        //System.out.println("Test Done.  Stats");
        //((ActiveMQConnectionFactory) connectionFactory).getFactoryStats().dump(new IndentPrinter());
        System.out.println("Closing down connection");

        session.close();
        connection.close();
        System.out.println("Connection closed.");
    }

    protected void setPrefetchToOne() {
        ActiveMQPrefetchPolicy prefetchPolicy = ((ActiveMQConnection) connection).getPrefetchPolicy();
        prefetchPolicy.setQueuePrefetch(1);
        prefetchPolicy.setTopicPrefetch(1);
        prefetchPolicy.setDurableTopicPrefetch(1);
    }
}
