/**
 * 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 co.nordlander.a;

import static co.nordlander.a.A.*;
import static org.junit.Assert.*;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.jms.BytesMessage;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.DeliveryMode;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.MapMessage;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TextMessage;

import org.apache.activemq.broker.BrokerService;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.springframework.beans.factory.annotation.Autowired;

import com.fasterxml.jackson.databind.ObjectMapper;

/**
 * A base class with all the test cases.
 * The actual transport protocol has to be implemented as well as the broker implementation.
 * This is done in the real test classes. They could test any JMS complaint protocol and broker.
 *
 * This makes it easy to test that the basic functionality works with different ActiveMQ configurations.
 *
 * Created by petter on 2015-01-30.
 */
public abstract class BaseTest {

    protected static final String LN = System.getProperty("line.separator");
    protected static final long TEST_TIMEOUT = 2000L;
    protected Connection connection;
    protected Session session;
    protected ConnectionFactory cf;
    protected ExecutorService executor;
    protected A a;
    protected ATestOutput output;
    protected Destination testTopic, testQueue, sourceQueue, targetQueue;
    protected TextMessage testMessage;

    @Autowired
    protected BrokerService amqBroker;

    protected abstract ConnectionFactory getConnectionFactory();
    protected abstract String getConnectCommand();
    protected abstract void clearBroker() throws Exception;
    
    @Rule public TemporaryFolder tempFolder = new TemporaryFolder();


    @Before
    public void setupJMS() throws Exception {
        cf = getConnectionFactory();
        connection = cf.createConnection();
        session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);

        executor = Executors.newSingleThreadExecutor();
        a = new A();
        output = new ATestOutput();
        a.output = output;

        clearBroker();

        testTopic = session.createTopic("TEST.TOPIC");
        testQueue = session.createQueue("TEST.QUEUE");
        sourceQueue = session.createQueue("SOURCE.QUEUE");
        targetQueue = session.createQueue("TARGET.QUEUE");
        testMessage = session.createTextMessage("test");
        connection.start();

        clearQueue(targetQueue);
        clearQueue(testQueue);
        clearQueue(sourceQueue);
    }

    @After
    public void disconnectJMS() throws JMSException {
        session.close();
        connection.close();
        executor.shutdown();
    }

    @Test
    public void testPutQueue() throws Exception{
        String cmdLine = getConnectCommand() + "-" + CMD_PUT + " \"test\"" + " TEST.QUEUE";
        System.out.println("Testing cmd: " + cmdLine);
        a.run(cmdLine.split(" "));
        MessageConsumer mc = session.createConsumer(testQueue);
        TextMessage msg = (TextMessage)mc.receive(TEST_TIMEOUT);
        assertEquals("test",msg.getText());
    }
    
    @Test
    public void testPutBytesQueue() throws Exception {
    	String cmdLine = getConnectCommand() + "-" + CMD_PUT + " \"test\" -" + CMD_TYPE + " " + TYPE_BYTES + " TEST.QUEUE";
    	System.out.println("Testing cmd: " + cmdLine);
    	a.run(cmdLine.split(" "));

    	MessageConsumer mc = session.createConsumer(testQueue);
        BytesMessage msg = (BytesMessage)mc.receive(TEST_TIMEOUT);
        byte[] bytes = new byte[(int) msg.getBodyLength()];
        msg.readBytes(bytes);
        assertEquals("test",new String(bytes, StandardCharsets.UTF_8));
    
    }

    @Test
    public void testPutWithPriorityAndType() throws Exception{
        final int priority = 6;
        final String type = "MyType";
        String cmdLine = getConnectCommand() + "-" + CMD_PRIORITY + " " + priority + " -" + CMD_JMS_TYPE + " " + type 
                +  " -" + CMD_PUT + "\"test\"" + " TEST.QUEUE";
        a.run(cmdLine.split(" "));
        MessageConsumer mc = session.createConsumer(testQueue);
        TextMessage msg = (TextMessage)mc.receive(TEST_TIMEOUT);
        assertEquals("test",msg.getText());
        assertEquals(priority, msg.getJMSPriority());
        assertEquals(type, msg.getJMSType());
    }

    @Test
    public void testPutTopic() throws Exception{
        String cmdLine = getConnectCommand() + "-" + CMD_PUT + "\"test\"" + " topic://TEST.TOPIC";
        Future<TextMessage> resultMessage = executor.submit(() -> {
            MessageConsumer mc = session.createConsumer(testTopic);
            return (TextMessage)mc.receive(TEST_TIMEOUT + 8000L);
        });
        a.run(cmdLine.split(" "));
        assertEquals("test",resultMessage.get().getText());
    }

    // https://github.com/fmtn/a/issues/17
    @Test
    public void testPutQueueWithSlash() throws Exception {
        final String queueWithSlash = "MY/QUEUE";
        final String cmdLine = getConnectCommand() + "-" + CMD_PUT + " \"test\" " + queueWithSlash;
        System.out.println("Testing cmd: " + cmdLine);
        a.run(cmdLine.split(" "));
        MessageConsumer mc = session.createConsumer(session.createQueue(queueWithSlash));
        TextMessage msg = (TextMessage)mc.receive(TEST_TIMEOUT);
        assertEquals("test", msg.getText());
    }

    @Test
    public void testGetQueue() throws Exception{
        MessageProducer mp = session.createProducer(testQueue);
        mp.send(testMessage);
        String cmdLine = getConnectCommand() + "-" + CMD_GET + " -" +
                CMD_WAIT + " 2000" + " TEST.QUEUE";
        a.run(cmdLine.split(" "));
        String out = output.grab();
        assertTrue("Payload test expected",out.contains("Payload:"+LN+"test"));
    }
    
    @Test
    public void testGetQueueWithSelector() throws Exception{
        MessageProducer mp = session.createProducer(testQueue);
        
        Message theOne = session.createTextMessage("theOne"); // message 1
        theOne.setStringProperty("identity","theOne");
        Message theOther = session.createTextMessage("theOther"); // message 2
        theOther.setStringProperty("identity","theOther");
        
        mp.send(theOne);
        mp.send(theOther);
        
        String cmdLine = getConnectCommand() + "-" + CMD_GET + " -" + CMD_SELECTOR + " identity='theOne'" + " -" +
                CMD_WAIT + " 2000" + " TEST.QUEUE";
        a.run(cmdLine.split(" "));
        String out = output.grab();
        assertTrue("Payload test expected",out.contains("Payload:"+LN+"theOne"));
        assertFalse("The other not expected", out.contains("Payload:" + LN + "theOther"));
    }

    @Test
    public void testGetTopic() throws Exception{
        final String cmdLine = getConnectCommand() + "-" + CMD_GET + " -" +
                CMD_WAIT + " 4000" + " topic://TEST.TOPIC";
        Future<String> resultString = executor.submit(() -> {
            a.run(cmdLine.split(" "));
            return output.grab();
        });
        Thread.sleep(300); // TODO remove somehow?
        MessageProducer mp = session.createProducer(testTopic);
        mp.send(testMessage);
        String result = resultString.get();
        assertTrue("Payload test expected", result.contains("Payload:" + LN + "test"));
    }

    /**
     * Test that all messages are copied (not moved) from one queue to the other.
     * @throws Exception
     */
    @Test
    public void testCopyQueue() throws Exception{
        final String cmdLine = getConnectCommand() + "-" + CMD_COPY_QUEUE + " SOURCE.QUEUE TARGET.QUEUE";
        MessageProducer mp = session.createProducer(sourceQueue);
        mp.send(testMessage);
        mp.send(testMessage);
        a.run(cmdLine.split(" "));
        MessageConsumer mc = session.createConsumer(sourceQueue);
        TextMessage msg = null;
        // Verify messages are left on source queue
        msg = (TextMessage)mc.receive(TEST_TIMEOUT);
        assertNotNull(msg);
        msg = (TextMessage)mc.receive(TEST_TIMEOUT);
        assertNotNull(msg);
        msg = (TextMessage)mc.receive(TEST_TIMEOUT);
        assertNull(msg);
        // Verify messages are copied to target queue
        mc = session.createConsumer(targetQueue);
        msg = (TextMessage)mc.receive(TEST_TIMEOUT);
        assertNotNull(msg);
        msg = (TextMessage)mc.receive(TEST_TIMEOUT);
        assertNotNull(msg);
        msg = (TextMessage)mc.receive(TEST_TIMEOUT);
        assertNull(msg);
    }

    /**
     * Test that transforming messages with copy works.
     * @throws Exception
     */
    @Test
    public void testCopyQueueWithTransformer() throws Exception {
        final String script = "\"msg.stringProperties.put('changeme','new');\"";
        final String cmdLine = getConnectCommand() + "-" + CMD_COPY_QUEUE + " SOURCE.QUEUE -" + CMD_TRANSFORM_SCRIPT + " " + script + " TARGET.QUEUE";

        MessageProducer mp = session.createProducer(sourceQueue);
        mp.send(testMessage);
        a.run(cmdLine.split(" "));
        // Verify messages are moved to target queue
        MessageConsumer mc = session.createConsumer(targetQueue);
        TextMessage msg = (TextMessage)mc.receive(TEST_TIMEOUT);
        assertNotNull(msg);
        assertEquals("new", msg.getStringProperty("changeme"));
    }

    /**
     * Test that all messages are moved from one queue to the other.
     * @throws Exception
     */
    @Test
    public void testMoveQueue() throws Exception{
        final String cmdLine = getConnectCommand() + "-" + CMD_MOVE_QUEUE + " SOURCE.QUEUE TARGET.QUEUE";
        MessageProducer mp = session.createProducer(sourceQueue);
        mp.send(testMessage);
        mp.send(testMessage);
        a.run(cmdLine.split(" "));
        MessageConsumer mc = session.createConsumer(sourceQueue);
        TextMessage msg;
        // Verify NO messages are left on source queue
        msg = (TextMessage)mc.receive(TEST_TIMEOUT);
        assertNull(msg);
        // Verify messages are moved to target queue
        mc = session.createConsumer(targetQueue);
        msg = (TextMessage)mc.receive(TEST_TIMEOUT);
        assertNotNull(msg);
        msg = (TextMessage)mc.receive(TEST_TIMEOUT);
        assertNotNull(msg);
        msg = (TextMessage)mc.receive(TEST_TIMEOUT);
        assertNull(msg);

    }

    /**
     * Test that all messages are moved from one queue to the other.
     * @throws Exception
     */
    @Test
    public void testMoveQueueWithTransformer() throws Exception{
        final String script = "\"msg.stringProperties.put('changeme','new');\"";
        final String cmdLine = getConnectCommand() + "-" + CMD_MOVE_QUEUE + " SOURCE.QUEUE -" + CMD_TRANSFORM_SCRIPT + " " + script + " TARGET.QUEUE";
        MessageProducer mp = session.createProducer(sourceQueue);
        mp.send(testMessage);
        a.run(cmdLine.split(" "));
        // Verify messages are moved to target queue with changed property
        MessageConsumer mc = session.createConsumer(targetQueue);
        TextMessage msg = (TextMessage)mc.receive(TEST_TIMEOUT);
        assertNotNull(msg);
        assertEquals("new", msg.getStringProperty("changeme"));

    }
    
    /**
     * Test that all messages are moved from one queue to the other.
     * Input count = 0
     * @throws Exception
     */
    @Test
    public void testMoveZeroCountQueue() throws Exception{
        final String cmdLine = getConnectCommand() + "-" + CMD_MOVE_QUEUE + " SOURCE.QUEUE -" + CMD_COUNT + " 0 TARGET.QUEUE";
        MessageProducer mp = session.createProducer(sourceQueue);
        mp.send(testMessage);
        mp.send(testMessage);
        a.run(cmdLine.split(" "));
        MessageConsumer mc = session.createConsumer(sourceQueue);
        TextMessage msg;
        // Verify NO messages are left on source queue
        msg = (TextMessage)mc.receive(TEST_TIMEOUT);
        assertNull(msg);
        // Verify messages are moved to target queue
        mc = session.createConsumer(targetQueue);
        msg = (TextMessage)mc.receive(TEST_TIMEOUT);
        assertNotNull(msg);
        msg = (TextMessage)mc.receive(TEST_TIMEOUT);
        assertNotNull(msg);
        msg = (TextMessage)mc.receive(TEST_TIMEOUT);
        assertNull(msg);
    }

   /**
     * Test that all messages but one message is moved from one queue to the other.
     * @throws Exception
     */
    @Test
    public void testMoveCountQueue() throws Exception{
        final String cmdLine = getConnectCommand() + "-" + CMD_MOVE_QUEUE + " SOURCE.QUEUE " + "-c" + " 4 TARGET.QUEUE";
        MessageProducer mp = session.createProducer(sourceQueue);

        mp.send(testMessage);
        mp.send(testMessage);
        mp.send(testMessage);
        mp.send(testMessage);
        mp.send(testMessage);
	
        a.run(cmdLine.split(" "));
        MessageConsumer mc = session.createConsumer(sourceQueue);
        TextMessage msg = null;

        // Verify 1 messages are left on source queue
        msg = (TextMessage)mc.receive(TEST_TIMEOUT);
        assertNotNull(msg);

        // Verify NO messages are left on source queue
        msg = (TextMessage)mc.receive(TEST_TIMEOUT);
        assertNull(msg);

        // Verify 4 messages is moved to target queue
        mc = session.createConsumer(targetQueue);

        msg = (TextMessage)mc.receive(TEST_TIMEOUT);
        assertNotNull(msg);
        msg = (TextMessage)mc.receive(TEST_TIMEOUT);
        assertNotNull(msg);
        msg = (TextMessage)mc.receive(TEST_TIMEOUT);
        assertNotNull(msg);
        msg = (TextMessage)mc.receive(TEST_TIMEOUT);
        assertNotNull(msg);

	
        // Verify NO messages are left on target queue
        msg = (TextMessage)mc.receive(TEST_TIMEOUT);
        assertNull(msg);
    }


    @Test
    public void testGetCount() throws Exception{
        final String cmdLine = getConnectCommand() + "-" + CMD_GET + " -" + CMD_COUNT + "2 TEST.QUEUE";
        MessageProducer mp = session.createProducer(testQueue);
        mp.send(testMessage);
        mp.send(testMessage);
        a.run(cmdLine.split(" "));
        String out = output.grab().replaceFirst("Operation completed in .+","");

        final String expectedOut = "-----------------" + LN +
                "Message Properties" + LN +
                "Payload:" + LN +
                "test" + LN +
                "-----------------" + LN +
                "Message Properties" + LN +
                "Payload:" + LN +
                "test" + LN + LN;
        assertEquals(expectedOut,out);
    }

    @Test
    public void testMoveSelector() throws Exception{
        final String cmdLine = getConnectCommand() + "-" + CMD_MOVE_QUEUE + " SOURCE.QUEUE -s identity='theOne' TARGET.QUEUE";
        MessageProducer mp = session.createProducer(sourceQueue);

        Message theOne = session.createTextMessage("theOne"); // message
        theOne.setStringProperty("identity","theOne");
        Message theOther = session.createTextMessage("theOther"); // message
        theOther.setStringProperty("identity","theOther");

        mp.send(theOne);
        mp.send(theOther);

        a.run(cmdLine.split(" "));
        List<TextMessage> msgs = getAllMessages(session.createConsumer(sourceQueue));
        assertEquals(1,msgs.size());
        assertEquals("theOther",msgs.get(0).getText());

        msgs = getAllMessages(session.createConsumer(targetQueue));
        assertEquals(1,msgs.size());
        assertEquals("theOne",msgs.get(0).getText());
    }

    @Test
    public void testCopySelector() throws Exception{
        final String cmdLine = getConnectCommand() + "-" + CMD_COPY_QUEUE + " SOURCE.QUEUE -s \"identity='the One'\" TARGET.QUEUE";
        MessageProducer mp = session.createProducer(sourceQueue);

        Message theOne = session.createTextMessage("theOne"); // message
        theOne.setStringProperty("identity","the One");
        Message theOther = session.createTextMessage("theOther"); // message
        theOther.setStringProperty("identity","theOther");

        mp.send(theOne);
        mp.send(theOther);

        a.run(splitCmdLine(cmdLine));
        List<TextMessage> msgs = getAllMessages(session.createConsumer(sourceQueue));
        assertEquals(2,msgs.size());

        msgs = getAllMessages(session.createConsumer(targetQueue));
        assertEquals(1,msgs.size());
        assertEquals("theOne",msgs.get(0).getText());
    }

    @Test
    public void testSendMapMessage() throws Exception {
        File folder = tempFolder.newFolder();
        final String msgInJson = "{\"TYPE\":\"test\", \"ID\":1}";
        final File file = new File(folder, "file1.json");
        FileUtils.writeStringToFile(file, msgInJson, StandardCharsets.UTF_8);

        final String cmdLine = getConnectCommand() + "-" + CMD_PUT + "@" + file.getAbsolutePath() + " -" + CMD_TYPE + " " + TYPE_MAP + " TEST.QUEUE";
        a.run(cmdLine.split(" "));

        MessageConsumer mc = session.createConsumer(testQueue);
        MapMessage msg1 = (MapMessage)mc.receive(TEST_TIMEOUT);
        assertEquals(msg1.getString("TYPE"), "test");
        assertEquals(msg1.getInt("ID"), 1);
    }

    @Test
    public void testReadFolder() throws Exception {
    	File folder = tempFolder.newFolder();
    	final String file1 = "file1-content";
    	final String file2 = "file2-content";
    	final String file3 = "no-go";
    	FileUtils.writeStringToFile(new File(folder, "file1.txt"), file1, StandardCharsets.UTF_8);
    	FileUtils.writeStringToFile(new File(folder, "file2.txt"), file2, StandardCharsets.UTF_8);
    	FileUtils.writeStringToFile(new File(folder, "file3.dat"), file3, StandardCharsets.UTF_8);
    	Thread.sleep(2000L); // Saturate file age
    	final String fileFilter = folder.getAbsolutePath() + "/*.txt";
    	final String cmdLine = getConnectCommand() + "-" + CMD_READ_FOLDER + " " + fileFilter + " TEST.QUEUE";
    	a.run(cmdLine.split(" "));
    	
    	MessageConsumer mc = session.createConsumer(testQueue);
    	TextMessage msg1 = (TextMessage)mc.receive(TEST_TIMEOUT);
    	assertNotNull(msg1);
        assertNotEquals(file3, msg1.getText());
    	TextMessage msg2 = (TextMessage)mc.receive(TEST_TIMEOUT);
    	assertNotNull(msg2);
        assertNotEquals(file3, msg2.getText());
    	assertNull(mc.receive(TEST_TIMEOUT));
    	File[] remainingFiles = folder.listFiles();
    	assertEquals(1,remainingFiles.length); // one file left - the .dat one
    	assertEquals("file3.dat",remainingFiles[0].getName());
    }

    @Test
    public void testDumpMessages() throws Exception {
        final String testCorrId = "MyCorrelationId";
        final String stringPropertyValue = "String Value - å";
        final String utfText = "Utf-8 Text - 😁";
        final Queue replyQueue = session.createQueue("test.reply.queue");
        final TextMessage tm1 = session.createTextMessage(utfText);

        tm1.setStringProperty("myStringProperty", stringPropertyValue);
        tm1.setIntProperty("myIntProperty", 42);
        tm1.setDoubleProperty("myDoubleProperty", Math.PI);
        tm1.setJMSType("myJmsType");
        tm1.setJMSCorrelationID(testCorrId);
        tm1.setJMSDeliveryMode(DeliveryMode.PERSISTENT);
        tm1.setJMSPriority(2);
        tm1.setJMSReplyTo(replyQueue);

        BytesMessage bm1 = session.createBytesMessage();
        bm1.writeBytes(utfText.getBytes(StandardCharsets.UTF_8));

        MessageProducer mp = session.createProducer(testQueue);
        mp.send(tm1);
        mp.send(bm1);
        File folder = tempFolder.newFolder();
        File dumpFile = new File(folder, "dump.json");

        String cmdLine = getConnectCommand() + "-" + CMD_WRITE_DUMP + " " + dumpFile.getAbsolutePath() + " -" +
                CMD_WAIT + " 2000 -" + CMD_COUNT + " 2" + " TEST.QUEUE";
        a.run(cmdLine.split(" "));

        ObjectMapper om = new ObjectMapper();

        String result = FileUtils.readFileToString(dumpFile, StandardCharsets.UTF_8);
        System.out.println(result);
        List<MessageDump> resultMsgs = Arrays.asList(om.readValue(result, MessageDump[].class));
        assertEquals(2, resultMsgs.size());

        MessageDump resultMsg1 = resultMsgs.get(0);
        assertEquals("TextMessage", resultMsg1.type);
        assertEquals(utfText, resultMsg1.body);
        assertEquals(stringPropertyValue, resultMsg1.stringProperties.get("myStringProperty"));

        // decode obj property to List and check consistency.
        // TODO Actually only works with OpenWire, so ignoring this. Other implementations may only support String, Integer etc.
//        String objectPropertyString = resultMsg1.objectProperties.get("myObjectProperty");
        //       List<String> decodedObjProperty = SerializationUtils.deserialize(Base64.decodeBase64(objectPropertyString));
        //       assertEquals(testList, decodedObjProperty);

        assertEquals(Integer.valueOf(DeliveryMode.PERSISTENT), resultMsg1.JMSDeliveryMode);
        assertEquals(testCorrId, resultMsg1.JMSCorrelationID);

        MessageDump resultMsg2 = resultMsgs.get(1);
        assertEquals("BytesMessage", resultMsg2.type);
        assertEquals(utfText, new String(Base64.decodeBase64(resultMsg2.body), StandardCharsets.UTF_8));
    }

    @Test
    public void testDumpMessages_RollbackOnError() throws Exception{
        final String testCorrId = "MyCorrelationId";
        final String stringPropertyValue = "String Value - å";
        final String utfText = "Utf-8 Text - 😁";
        final Queue replyQueue = session.createQueue("test.reply.queue");
        final MessageProducer mp = session.createProducer(testQueue);
                mp.send(createTextMessage(testCorrId, stringPropertyValue, utfText, replyQueue));

        File folder = tempFolder.newFolder();
        File dumpFile = new File(folder, "dump.json");

        String cmdLine = getConnectCommand() + "-" + CMD_WRITE_DUMP + " " + dumpFile.getAbsolutePath() + " -" +
                CMD_WAIT + " 2000 -"  + CMD_TRANSFORM_SCRIPT + "dummy -" + CMD_COUNT + " " + 1 + " TEST.QUEUE";
        a.run(cmdLine.split(" "));
        assertTrue("Output should contain error message",
                output.grab().contains("Failed to write all messages to dump file"));

        // check that our message is still on the queue
        MessageConsumer consumer = session.createConsumer(testQueue);
        Message message = consumer.receive(TEST_TIMEOUT);
        assertTrue(message instanceof TextMessage);
        assertEquals(utfText, ((TextMessage) message).getText());
        assertEquals(stringPropertyValue, message.getStringProperty("myStringProperty"));
        assertEquals(DeliveryMode.PERSISTENT, message.getJMSDeliveryMode());
        assertEquals(testCorrId, message.getJMSCorrelationID());
    }

    /**
     * Simple load test for dumping a queue's content to file.
     * By default only loads and dumps 10 messages to avoid straining the CI server.
     */
    @Test
    public void testDumpManyMessages() throws Exception {
        final int numberOfMessages = 10; // increase to test with desired load
        final String testCorrId = "MyCorrelationId";
        final String stringPropertyValue = "String Value - å";
        final String utfText = "Utf-8 Text - 😁";
        final byte[] binaryData = IOUtils.toByteArray(getClass().getResourceAsStream("/Logo.png"));
        final Queue replyQueue = session.createQueue("test.reply.queue");
        final MessageProducer mp = session.createProducer(testQueue);
        for (int i = 0; i < numberOfMessages; i++){
            if (i % 2 == 0){
                mp.send(createTextMessage(testCorrId, stringPropertyValue, utfText, replyQueue));
            }else {
                mp.send(createBytesMessage(binaryData));
            }
        }

        File folder = tempFolder.newFolder();
        File dumpFile = new File(folder, "dump.json");
        
        String cmdLine = getConnectCommand() + "-" + CMD_WRITE_DUMP + " " + dumpFile.getAbsolutePath() + " -" +
                CMD_WAIT + " 2000 -" + CMD_COUNT + " " + (numberOfMessages + 10) + " TEST.QUEUE";
        System.out.println("Running a with " + cmdLine);
        a.run(cmdLine.split(" "));
     
        ObjectMapper om = new ObjectMapper();
        
        String result = FileUtils.readFileToString(dumpFile, StandardCharsets.UTF_8);
        List<MessageDump> resultMsgs = Arrays.asList(om.readValue(result, MessageDump[].class));
        assertEquals(numberOfMessages, resultMsgs.size());

        for (int i = 0; i < numberOfMessages; i++){
            if (i % 2 == 0){
                MessageDump resultMsg1 = resultMsgs.get(i);
                assertEquals("TextMessage", resultMsg1.type);
                assertEquals(utfText, resultMsg1.body);
                assertEquals(stringPropertyValue, resultMsg1.stringProperties.get("myStringProperty"));
                assertEquals(Integer.valueOf(DeliveryMode.PERSISTENT), resultMsg1.JMSDeliveryMode);
                assertEquals(testCorrId, resultMsg1.JMSCorrelationID);
            }else {
                MessageDump resultMsg2 = resultMsgs.get(1);
                assertEquals("BytesMessage", resultMsg2.type);
                assertArrayEquals(binaryData, Base64.decodeBase64(resultMsg2.body));
            }
        }
    }

    private BytesMessage createBytesMessage(byte[] bytes) throws JMSException {
        BytesMessage bm1 = session.createBytesMessage();
        bm1.writeBytes(bytes);
        return bm1;
    }

    private TextMessage createTextMessage(String testCorrId, String stringPropertyValue, String utfText, Queue replyQueue) throws JMSException {
        final TextMessage tm1 = session.createTextMessage(utfText);
        tm1.setStringProperty("myStringProperty", stringPropertyValue);
        tm1.setIntProperty("myIntProperty", 42);
        tm1.setDoubleProperty("myDoubleProperty", Math.PI);
        tm1.setJMSType("myJmsType");
        tm1.setJMSCorrelationID(testCorrId);
        tm1.setJMSDeliveryMode(DeliveryMode.PERSISTENT);
        tm1.setJMSPriority(2);
        tm1.setJMSReplyTo(replyQueue);
        return tm1;
    }

    @Test
    public void testRestoreDump() throws Exception {
    	// place file where it can be reached by a - that is on file system, not classpath.
    	File dumpFile = tempFolder.newFile("testdump.json");
    	InputStream jsonStream = BaseTest.class.getClassLoader().getResourceAsStream("testdump.json");
    	FileUtils.writeByteArrayToFile(dumpFile, IOUtils.toByteArray(jsonStream));
    	IOUtils.closeQuietly(jsonStream);
    	
    	final String utfText = "Utf-8 Text - 😁";
    	
    	String cmdLine = getConnectCommand() + "-" + CMD_RESTORE_DUMP + " " + dumpFile.getAbsolutePath() + " " + "TEST.QUEUE";
        a.run(cmdLine.split(" "));
    	
        MessageConsumer mc = session.createConsumer(testQueue);
        TextMessage msg1 = (TextMessage) mc.receive(TEST_TIMEOUT);
        assertNotNull(msg1);
        // msgId is always recreated in JMS - do not test!
        // JMS Timestamp also recreated - do not test!
        
        assertEquals("MyCorrelationId", msg1.getJMSCorrelationID());
        assertEquals(1, msg1.getJMSDeliveryMode());
        assertEquals(4, msg1.getJMSPriority());
        assertEquals("myJmsType", msg1.getJMSType());
        assertEquals(Math.PI, msg1.getDoubleProperty("myDoubleProperty"), 0.000000000000001);
        assertEquals(42, msg1.getIntProperty("myIntProperty"));
        assertEquals(utfText, msg1.getText());
        assertEquals("String Value - å", msg1.getStringProperty("myStringProperty"));
        BytesMessage msg2 = (BytesMessage) mc.receive(TEST_TIMEOUT);
        assertNotNull(msg2);
        byte[] msg2Data = new byte[(int) msg2.getBodyLength()];
        msg2.readBytes(msg2Data);
        assertEquals(utfText, new String(msg2Data, StandardCharsets.UTF_8));
        mc.close();
    }
    
    @Test
    public void testDumpMessagesAndTransform() throws Exception {
    	final String text = "A - JMS util";
    	final TextMessage tm1 = session.createTextMessage(text);
    	tm1.setStringProperty("changeme", "old value");
    	
    	MessageProducer mp = session.createProducer(testQueue);
        mp.send(tm1);
        File folder = tempFolder.newFolder();
        File dumpFile = new File(folder, "dump.json");
        String script = "\"msg.body=msg.body.replace('A','B');msg.stringProperties.put('changeme','new');\"";
        
        String cmdLine = getConnectCommand() + "-" + CMD_WRITE_DUMP + " " + dumpFile.getAbsolutePath() + " -" +
                CMD_WAIT + " 2000 -" + CMD_COUNT + " 1 -" + CMD_TRANSFORM_SCRIPT + " " + script + " TEST.QUEUE";
        a.run(cmdLine.split(" "));
     
        ObjectMapper om = new ObjectMapper();
        
        String result = FileUtils.readFileToString(dumpFile, StandardCharsets.UTF_8);
        System.out.println(result);
        List<MessageDump> resultMsgs = Arrays.asList(om.readValue(result, MessageDump[].class));
        assertEquals(1, resultMsgs.size());
        
        MessageDump resultMsg1 = resultMsgs.get(0);
        assertEquals("B - JMS util", resultMsg1.body);
        assertEquals("new", resultMsg1.stringProperties.get("changeme"));
    }

    @Test
    public void testBatch() throws Exception {
        File folder = tempFolder.newFolder();
        String batchContent = "a\nb\nc";
        File batchFile = new File(folder, "batch.txt");
        FileUtils.writeStringToFile(batchFile, batchContent, StandardCharsets.UTF_8);
        String script = "\"msg.body=msg.body.replace('PLACEHOLDER',entry);\"";

        String cmdLine = getConnectCommand() + "-" + CMD_PUT + " \"test-PLACEHOLDER\" -" + CMD_BATCH_FILE + " "
                + batchFile.getAbsolutePath() +  " -" + CMD_TRANSFORM_SCRIPT + " " +  script + " TEST.QUEUE";
        System.out.println("Testing cmd: " + cmdLine);
        a.run(cmdLine.split(" "));

        MessageConsumer mc = session.createConsumer(testQueue);
        String[] entries = batchContent.split("\\n");
        for (int i=0; i<entries.length; i++) {
            TextMessage msg = (TextMessage) mc.receive(TEST_TIMEOUT);
            assertNotNull(msg);
            assertEquals("test-" + entries[i], msg.getText());
        }

    }

    
    /**
     * Needed to split command line arguments by space, but not quoted.
     * @param cmdLine command line
     * @return the arguments.
     */
    protected String[] splitCmdLine(String cmdLine){
        List<String> matchList = new ArrayList<String>();
        Pattern regex = Pattern.compile("[^\\s\"]+|\"([^\"]*)\"");
        Matcher regexMatcher = regex.matcher(cmdLine);
        while (regexMatcher.find()) {
            if (regexMatcher.group(1) != null) {
                matchList.add(regexMatcher.group(1));
            } else {
                matchList.add(regexMatcher.group());
            }
        }
        return matchList.toArray(new String[0]);
    }

    protected List<TextMessage> getAllMessages(MessageConsumer mc) throws JMSException {
        TextMessage msg = null;
        List<TextMessage> msgs = new ArrayList<TextMessage>();
        while( (msg = (TextMessage) mc.receive(TEST_TIMEOUT))!=null){
            msgs.add(msg);
        }
        return msgs;
    }
    
    protected void clearQueue(final Destination dest) throws JMSException {
    	MessageConsumer mc = session.createConsumer(dest);
    	int cnt = 0;
    	while( mc.receive(1L) != null) {
    		cnt++;
    	}
    	mc.close();
    	System.out.println(cnt + " messages cleared from " + dest.toString());
    }
}