/*
 *
 * 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.qpid.server.persistent;

import org.apache.qpid.test.utils.QpidBrokerTestCase;
import org.apache.qpid.client.AMQConnection;
import org.apache.qpid.client.AMQSession;
import org.apache.qpid.jms.ConnectionListener;
import org.apache.qpid.jms.BrokerDetails;
import org.apache.qpid.jms.ConnectionURL;
import org.apache.qpid.server.registry.ApplicationRegistry;
import org.apache.qpid.server.registry.ConfigurationFileApplicationRegistry;
import org.apache.qpid.server.store.DerbyMessageStore;
import org.apache.commons.configuration.XMLConfiguration;

import javax.jms.Connection;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.Session;
import javax.jms.Topic;
import javax.jms.TopicSubscriber;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

import java.util.concurrent.CountDownLatch;
import java.io.File;

/**
 * QPID-1813 : We do not store the client id with a message so on store restart
 * that information is lost and we are unable to perform no local checks.
 *
 * QPID-1813 highlights the lack of testing here as the broker will NPE as it
 * assumes that the client id of the publisher will always exist
 */
public class NoLocalAfterRecoveryTest extends QpidBrokerTestCase implements ConnectionListener
{
    protected final String MY_TOPIC_SUBSCRIPTION_NAME = this.getName();
    protected static final int SEND_COUNT = 10;
    private CountDownLatch _failoverComplete = new CountDownLatch(1);

    protected ConnectionURL _connectionURL;

    @Override
    protected void setUp() throws Exception
    {

        XMLConfiguration configuration = new XMLConfiguration(_configFile);
        configuration.setProperty("virtualhosts.virtualhost.test.store.class", "org.apache.qpid.server.store.DerbyMessageStore");
        configuration.setProperty("virtualhosts.virtualhost.test.store."+ DerbyMessageStore.ENVIRONMENT_PATH_PROPERTY,
                                  System.getProperty("QPID_WORK", System.getProperty("java.io.tmpdir")) + File.separator + "derbyDB-NoLocalAfterRecoveryTest");

        File tmpFile = File.createTempFile("configFile", "test");
        tmpFile.deleteOnExit();
        configuration.save(tmpFile);

        _configFile = tmpFile;
        _connectionURL = getConnectionURL();

        BrokerDetails details = _connectionURL.getBrokerDetails(0);

        // Due to the problem with SingleServer delaying on all connection
        // attempts. So using a high retry value.
        if (_broker.equals(VM))
        {
            // Local testing suggests InVM restart takes under a second
            details.setProperty(BrokerDetails.OPTIONS_RETRY, "5");
            details.setProperty(BrokerDetails.OPTIONS_CONNECT_DELAY, "200");
        }
        else
        {
            // This will attempt to failover for 3 seconds.
            // Local testing suggests failover takes 2 seconds
            details.setProperty(BrokerDetails.OPTIONS_RETRY, "10");
            details.setProperty(BrokerDetails.OPTIONS_CONNECT_DELAY, "500");
        }

        super.setUp();        
    }

    public void test() throws Exception
    {

        Connection connection = getConnection(_connectionURL);
        Session session = connection.createSession(true, Session.SESSION_TRANSACTED);

        Topic topic = (Topic) getInitialContext().lookup("topic");

        TopicSubscriber noLocalSubscriber = session.
                createDurableSubscriber(topic, MY_TOPIC_SUBSCRIPTION_NAME + "-NoLocal",
                                        null, true);

        TopicSubscriber normalSubscriber = session.
                createDurableSubscriber(topic, MY_TOPIC_SUBSCRIPTION_NAME + "-Normal",
                                        null, false);

        List<Message> sent = sendMessage(session, topic, SEND_COUNT);

        session.commit();

        assertEquals("Incorrect number of messages sent",
                     SEND_COUNT, sent.size());


        // Check messages can be received as expected.
        connection.start();

        assertTrue("No Local Subscriber is not a no-local subscriber",
                   noLocalSubscriber.getNoLocal());

        assertFalse("Normal Subscriber is a no-local subscriber",
                    normalSubscriber.getNoLocal());


        List<Message> received = receiveMessage(noLocalSubscriber, SEND_COUNT);
        assertEquals("No Local Subscriber Received messages", 0, received.size());

        received = receiveMessage(normalSubscriber, SEND_COUNT);
        assertEquals("Normal Subscriber Received no messages",
                     SEND_COUNT, received.size());


        ((AMQConnection)connection).setConnectionListener(this);

        restartBroker();


        //Await
        if (!_failoverComplete.await(4000L, TimeUnit.MILLISECONDS))
        {
            fail("Failover Failed to compelete");
        }

        session.rollback();

        //Failover will restablish our clients
        assertTrue("No Local Subscriber is not a no-local subscriber",
                   noLocalSubscriber.getNoLocal());

        assertFalse("Normal Subscriber is a no-local subscriber",
                    normalSubscriber.getNoLocal());


        // NOTE : here that the NO-local subscriber actually now gets ALL the
        // messages as the connection has failed and they are consuming on a
        // different connnection to the one that was published on.
        received = receiveMessage(noLocalSubscriber, SEND_COUNT);
        assertEquals("No Local Subscriber Received messages", SEND_COUNT, received.size());

        received = receiveMessage(normalSubscriber, SEND_COUNT);
        assertEquals("Normal Subscriber Received no messages",
                     SEND_COUNT, received.size());

        //leave the store in a clean state.
        session.commit();
    }

    protected List<Message> assertReceiveMessage(MessageConsumer messageConsumer,
                                                 int count) throws JMSException
    {

        List<Message> receivedMessages = new ArrayList<Message>(count);
        for (int i = 0; i < count; i++)
        {
            Message received = messageConsumer.receive(1000);

            if (received != null)
            {
                receivedMessages.add(received);
            }
            else
            {
                fail("Only "
                     + receivedMessages.size() + "/" + count + " received.");
            }
        }

        return receivedMessages;
    }

    protected List<Message> receiveMessage(MessageConsumer messageConsumer,
                                           int count) throws JMSException
    {

        List<Message> receivedMessages = new ArrayList<Message>(count);
        for (int i = 0; i < count; i++)
        {
            Message received = messageConsumer.receive(1000);

            if (received != null)
            {
                receivedMessages.add(received);
            }
            else
            {
                break;
            }
        }

        return receivedMessages;
    }

    public void bytesSent(long count)
    {

    }

    public void bytesReceived(long count)
    {

    }

    public boolean preFailover(boolean redirect)
    {
        return true;
    }

    public boolean preResubscribe()
    {
        return true;
    }

    public void failoverComplete()
    {
        _failoverComplete.countDown();
    }
}
