/*
 * 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.artemis.tests.extras.jms.bridge;

import javax.transaction.TransactionManager;

import com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionManagerImple;
import org.apache.activemq.artemis.api.core.ActiveMQException;
import org.apache.activemq.artemis.jms.bridge.ConnectionFactoryFactory;
import org.apache.activemq.artemis.jms.bridge.DestinationFactory;
import org.apache.activemq.artemis.jms.bridge.QualityOfServiceMode;
import org.apache.activemq.artemis.jms.bridge.impl.JMSBridgeImpl;
import org.junit.Before;
import org.junit.Test;

/**
 * A JMSBridgeClusteredTest
 * <br>
 * Tests of jms bridge using HA connection factories.
 */
public class JMSBridgeClusteredTest extends ClusteredBridgeTestBase {

   private ServerGroup sourceServer;
   private ServerGroup targetServer;

   private String sourceQueueName = "SourceQueue";
   private String targetQueueName = "TargetQueue";

   @Override
   @Before
   public void setUp() throws Exception {
      super.setUp();

      sourceServer = createServerGroup("source-server");
      targetServer = createServerGroup("target-server");

      sourceServer.start();
      targetServer.start();

      sourceServer.createQueue(sourceQueueName);
      targetServer.createQueue(targetQueueName);
   }

   @Test
   public void testBridgeOnFailoverXA() throws Exception {
      performSourceAndTargetCrashAndFailover(QualityOfServiceMode.ONCE_AND_ONLY_ONCE);
   }

   @Test
   public void testBridgeOnFailoverDupsOk() throws Exception {
      performSourceAndTargetCrashAndFailover(QualityOfServiceMode.DUPLICATES_OK);
   }

   @Test
   public void testBridgeOnFailoverAtMostOnce() throws Exception {
      performSourceAndTargetCrashAndFailover(QualityOfServiceMode.AT_MOST_ONCE);
   }

   @Test
   public void testCrashAndFailoverWithMessagesXA() throws Exception {
      performSourceAndTargetCrashAndFailoverWithMessages(QualityOfServiceMode.ONCE_AND_ONLY_ONCE);
   }

   //test messages are correctly bridged when failover happens during a batch send.
   //first send some messages, make sure bridge doesn't send it (below batch size)
   //then crash the live
   //then send more messages
   //then receive those messages, no more, no less.
   //this test are valid for ONCE_AND_ONLY_ONCE and AT_MOST_ONCE.
   //with DUPS_OK the test failed because some messages are delivered again
   //after failover, which is fine as in this mode duplication is allowed.
   public void performSourceAndTargetCrashAndFailoverWithMessages(QualityOfServiceMode mode) throws Exception {
      JMSBridgeImpl bridge = null;
      TransactionManager txMgr = null;

      try {
         ConnectionFactoryFactory sourceCFF = sourceServer.getConnectionFactoryFactory();
         ConnectionFactoryFactory targetCFF = targetServer.getConnectionFactoryFactory();
         DestinationFactory sourceQueueFactory = sourceServer.getDestinationFactory(sourceQueueName);
         DestinationFactory targetQueueFactory = targetServer.getDestinationFactory(targetQueueName);

         //even number
         final int batchSize = 4;
         bridge = new JMSBridgeImpl(sourceCFF, targetCFF, sourceQueueFactory, targetQueueFactory, null, null, null, null, null, 1000, -1, mode, batchSize, -1, null, null, false).setBridgeName("test-bridge");

         txMgr = newTransactionManager();
         bridge.setTransactionManager(txMgr);

         //start the bridge
         bridge.start();

         System.out.println("started bridge");

         final int NUM_MESSAGES = batchSize / 2;

         //send some messages to source
         sendMessages(sourceServer, sourceQueueName, NUM_MESSAGES);
         //receive from target, no message should be received.
         receiveMessages(targetServer, targetQueueName, 0);

         //now crash target server
         targetServer.crashLive();

         //send more
         sendMessages(sourceServer, sourceQueueName, NUM_MESSAGES);

         receiveMessages(targetServer, targetQueueName, batchSize);

         //send some again
         sendMessages(sourceServer, sourceQueueName, NUM_MESSAGES);
         //check no messages arrived.
         receiveMessages(targetServer, targetQueueName, 0);
         //now crash source server
         sourceServer.crashLive();

         //verify bridge still work
         sendMessages(sourceServer, sourceQueueName, NUM_MESSAGES);
         receiveMessages(targetServer, targetQueueName, batchSize);
      }
      finally {
         if (bridge != null) {
            bridge.stop();
         }
      }
   }

   /*
    * Deploy a bridge, source and target queues are in
    * separate live/backup pairs. Source and Target CF are ha.
    * Test the bridge work when the live servers crash.
    */
   private void performSourceAndTargetCrashAndFailover(QualityOfServiceMode mode) throws Exception {

      JMSBridgeImpl bridge = null;
      TransactionManager txMgr = null;

      try {
         ConnectionFactoryFactory sourceCFF = sourceServer.getConnectionFactoryFactory();
         ConnectionFactoryFactory targetCFF = targetServer.getConnectionFactoryFactory();
         DestinationFactory sourceQueueFactory = sourceServer.getDestinationFactory(sourceQueueName);
         DestinationFactory targetQueueFactory = targetServer.getDestinationFactory(targetQueueName);

         bridge = new JMSBridgeImpl(sourceCFF, targetCFF, sourceQueueFactory, targetQueueFactory, null, null, null, null, null, 1000, -1, mode, 10, 1000, null, null, false).setBridgeName("test-bridge");

         txMgr = newTransactionManager();
         bridge.setTransactionManager(txMgr);

         //start the bridge
         bridge.start();

         final int NUM_MESSAGES = 10;

         //send some messages to source
         sendMessages(sourceServer, sourceQueueName, NUM_MESSAGES);
         //receive from target
         receiveMessages(targetServer, targetQueueName, NUM_MESSAGES);

         //now crash target server
         targetServer.crashLive();

         //verify bridge still works
         sendMessages(sourceServer, sourceQueueName, NUM_MESSAGES);
         receiveMessages(targetServer, targetQueueName, NUM_MESSAGES);

         //now crash source server
         sourceServer.crashLive();

         //verify bridge still work
         sendMessages(sourceServer, sourceQueueName, NUM_MESSAGES);
         receiveMessages(targetServer, targetQueueName, NUM_MESSAGES, mode == QualityOfServiceMode.ONCE_AND_ONLY_ONCE);
      }
      finally {
         if (bridge != null) {
            bridge.stop();
         }
      }
   }

   private void sendMessages(ServerGroup server, String queueName, int num) throws ActiveMQException {
      server.sendMessages(queueName, num);
   }

   private void receiveMessages(ServerGroup server,
                                String queueName,
                                int num,
                                boolean checkDup) throws ActiveMQException {
      try {
         server.receiveMessages(queueName, num, checkDup);
      }
      catch (ActiveMQException e) {
         e.printStackTrace();
         throw e;
      }
   }

   private void receiveMessages(ServerGroup server, String queueName, int num) throws ActiveMQException {
      try {
         server.receiveMessages(queueName, num, false);
      }
      catch (ActiveMQException e) {
         e.printStackTrace();
         throw e;
      }
   }

   protected TransactionManager newTransactionManager() {
      return new TransactionManagerImple();
   }

}
