org.cleverbus.component.externalcall.ExternalCallComponentTest.java Source code

Java tutorial

Introduction

Here is the source code for org.cleverbus.component.externalcall.ExternalCallComponentTest.java

Source

/*
 * Copyright (C) 2015
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.cleverbus.component.externalcall;

import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeThat;
import static org.junit.Assume.assumeTrue;

import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import javax.annotation.Nullable;

import org.cleverbus.api.asynch.AsynchConstants;
import org.cleverbus.api.entity.ExternalCall;
import org.cleverbus.api.entity.ExternalCallStateEnum;
import org.cleverbus.api.entity.Message;
import org.cleverbus.api.exception.IntegrationException;
import org.cleverbus.api.exception.InternalErrorEnum;
import org.cleverbus.api.exception.LockFailureException;
import org.cleverbus.api.extcall.ExtCallComponentParams;
import org.cleverbus.common.log.Log;
import org.cleverbus.component.AbstractComponentsDbTest;
import org.cleverbus.core.common.asynch.AsynchMessageRoute;
import org.cleverbus.core.common.dao.ExternalCallDao;
import org.cleverbus.test.ActiveRoutes;
import org.cleverbus.test.ExternalSystemTestEnum;
import org.cleverbus.test.ServiceTestEnum;

import org.apache.camel.EndpointInject;
import org.apache.camel.Exchange;
import org.apache.camel.Processor;
import org.apache.camel.Produce;
import org.apache.camel.ProducerTemplate;
import org.apache.camel.component.mock.MockEndpoint;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;

/**
 * Test suite for {@link ExternalCallComponent}.
 */
@ActiveRoutes(classes = { AsynchMessageRoute.class })
public class ExternalCallComponentTest extends AbstractComponentsDbTest {

    private static final String REQUEST_XML = "  <cus:setCustomerRequest xmlns=\"http://cleverbus.org/ws/Customer-v1\""
            + "         xmlns:cus=\"http://cleverbus.org/ws/CustomerService-v1\">" + "         <cus:customer>"
            + "            <externalCustomerID>12</externalCustomerID>" + "            <customerNo>23</customerNo>"
            + "            <customerTypeID>2</customerTypeID>" + "            <lastName>Juza</lastName>"
            + "            <firstName>Petr</firstName>" + "         </cus:customer>"
            + "  </cus:setCustomerRequest>";

    @Autowired
    private ExternalCallDao externalCallDao;

    @Produce
    private ProducerTemplate producer;

    @EndpointInject(uri = "mock:test")
    private MockEndpoint mockEndpoint;

    private Long extCallId;

    @Value("${asynch.externalCall.skipUriPattern}")
    private String skipUriPattern;

    @Before
    public void resetExtCallId() {
        extCallId = null;
    }

    @Test
    public void testExternalCallOK() throws Exception {
        final Message msg = messages(1)[0];

        // mock response and in-process asserts
        mockEndpoint.whenAnyExchangeReceived(
                recordCallIdAndAnswer("external call reply body", "mock:test", "ok123456"));

        // send message
        mockEndpoint.expectedMessageCount(1);
        String reply = requestViaExternalCall(msg, "mock:test", "ok123456", "external call original body");
        mockEndpoint.assertIsSatisfied();

        // verify result
        assertEquals("external call reply body", reply);

        // check the call is now in DB as OK and with the correct timestamp
        assertExtCallStateInDB(extCallId, ExternalCallStateEnum.OK, msg);
    }

    @Test
    public void testExternalCallObjectEntityIdOK() throws Exception {
        final Message msg = messages(1)[0];

        // mock response and in-process asserts
        mockEndpoint.whenAnyExchangeReceived(recordCallIdAndAnswer("external call reply body", "mock:test",
                "CRM_" + msg.getCorrelationId() + "_[CustomEntityId]:123654"));

        // send message
        mockEndpoint.expectedMessageCount(1);
        String reply = requestViaExternalCall(msg, ExternalCallKeyType.MESSAGE, "mock:test",
                new CustomEntityId("123654"), "external call original body");
        mockEndpoint.assertIsSatisfied();

        // verify result
        assertEquals("external call reply body", reply);

        // check the call is now in DB as OK and with the correct timestamp
        assertExtCallStateInDB(extCallId, ExternalCallStateEnum.OK, msg);
    }

    @Test
    public void testExternalCallFailedIOException() throws Exception {
        Message msg = messages(1)[0];

        // simulate failure
        mockEndpoint.whenAnyExchangeReceived(recordCallIdAndThrow(
                new IOException("test exception to simulate failure"), "mock:test", "fail123456"));

        // send message
        mockEndpoint.expectedMessageCount(1);
        try {
            requestViaExternalCall(msg, "mock:test", "fail123456", "external call original body");
            fail("Should've gotten an exception by now");
        } catch (Exception exc) {
            // verify failure
            assertThat(exc, is(instanceOf(IOException.class)));
        }
        mockEndpoint.assertIsSatisfied();

        // check the call is now in DB as FAILED and with the correct timestamp
        assertExtCallStateInDB(extCallId, ExternalCallStateEnum.FAILED, msg);
    }

    @Test
    public void testExternalCallFailedIntegrationException() throws Exception {
        Message msg = messages(1)[0];

        // simulate failure
        mockEndpoint.whenAnyExchangeReceived(recordCallIdAndThrow(
                new IntegrationException(InternalErrorEnum.E100, "test exception to simulate failure"), "mock:test",
                "fail123456"));

        // send message
        mockEndpoint.expectedMessageCount(1);
        try {
            requestViaExternalCall(msg, "mock:test", "fail123456", "external call original body");
            fail("Should've gotten an exception by now");
        } catch (Exception exc) {
            // verify failure
            assertThat(exc, is(instanceOf(IntegrationException.class)));
        }
        mockEndpoint.assertIsSatisfied();

        // check the call is now in DB as FAILED and with the correct timestamp
        assertExtCallStateInDB(extCallId, ExternalCallStateEnum.FAILED, msg);
    }

    @Test
    public void testExternalCallDuplicate() throws Exception {
        Message msg = messages(1)[0];

        mockEndpoint.whenAnyExchangeReceived(
                recordCallIdAndAnswer("external call reply body", "mock:test", "dupe123456"));

        // send first message
        mockEndpoint.expectedMessageCount(1);
        String reply = requestViaExternalCall(msg, "mock:test", "dupe123456", "external call original body");
        mockEndpoint.assertIsSatisfied();

        // verify result
        assertEquals("external call reply body", reply); // 1st reply is as expected

        // check the call is now in DB as OK and with the correct timestamp
        ExternalCallStateEnum state = ExternalCallStateEnum.OK;
        Long callId = extCallId;
        assertExtCallStateInDB(callId, state, msg);

        // send a duplicate message
        reply = requestViaExternalCall(msg, "mock:test", "dupe123456", "external call original body");
        mockEndpoint.assertIsSatisfied();

        assertEquals("external call original body", reply); // 2nd returned original body unchanged -- no call, no reply

        // check the call is in DB completely unchanged
        assertExtCallStateInDB(extCallId, ExternalCallStateEnum.OK, msg);
    }

    @Test
    public void testExternalCallDuplicateFail() throws Exception {
        Message msg = messages(1)[0];

        mockEndpoint.whenExchangeReceived(1,
                recordCallIdAndThrow(new IOException("Simulated Failure for Testing Duplicate of Failure"),
                        "mock:test", "dupeFail123456"));

        mockEndpoint.whenExchangeReceived(2,
                recordCallIdAndAnswer("external call reply body", "mock:test", "dupeFail123456"));

        // send first message
        mockEndpoint.expectedMessageCount(1);
        try {
            requestViaExternalCall(msg, "mock:test", "dupeFail123456", "external call original body");
            // verify result
            fail("Should've failed due to exception by now");
        } catch (Exception exc) {
            // verify failure
            assertThat(exc, is(instanceOf(IOException.class)));
        }
        mockEndpoint.assertIsSatisfied();

        // check the call is now in DB as OK and with the correct timestamp
        assertExtCallStateInDB(extCallId, ExternalCallStateEnum.FAILED, msg);

        // send a duplicate message
        mockEndpoint.expectedMessageCount(2);
        String reply = requestViaExternalCall(msg, "mock:test", "dupeFail123456", "external call original body");
        mockEndpoint.assertIsSatisfied();

        assertEquals("external call reply body", reply); // 2nd returned reply, since it was called again after 1st fail

        // check the call is in DB now as OK
        assertExtCallStateInDB(extCallId, ExternalCallStateEnum.OK, msg);
    }

    @Test
    public void testExternalCallNewer() throws Exception {
        Message[] msg = messages(2);
        assumeTrue("For this test the 2nd message must be older",
                msg[0].getMsgTimestamp().compareTo(msg[1].getMsgTimestamp()) < 0);

        mockEndpoint.whenAnyExchangeReceived(
                recordCallIdAndAnswer("external call reply body", "mock:test", "twiceKey"));

        // send 1st message
        mockEndpoint.expectedMessageCount(1);
        String reply = requestViaExternalCall(msg[0], "mock:test", "twiceKey", "external call original body");
        mockEndpoint.assertIsSatisfied();

        // verify result
        assertEquals("external call reply body", reply); // 1st reply is as expected

        // check the call is now in DB as OK and with the correct timestamp
        assertExtCallStateInDB(extCallId, ExternalCallStateEnum.OK, msg[0]);

        // send 2nd message
        mockEndpoint.expectedMessageCount(2);
        reply = requestViaExternalCall(msg[1], "mock:test", "twiceKey", "external call original body 2");
        mockEndpoint.assertIsSatisfied();

        // verify the call worked, since the 2nd message is newer
        assertEquals("external call reply body", reply); // 2nd reply is as expected too

        // check the call is in DB with the new timestamp
        assertExtCallStateInDB(extCallId, ExternalCallStateEnum.OK, msg[1]);
    }

    @Test
    public void testExternalCallOlder() throws Exception {
        Message[] msg = messages(2);
        assumeTrue("For this test the 2nd message must be older",
                msg[0].getMsgTimestamp().compareTo(msg[1].getMsgTimestamp()) <= 0);

        mockEndpoint.whenAnyExchangeReceived(
                recordCallIdAndAnswer("external call reply body", "mock:test", "older123456"));

        // send 2nd message - reverse order
        mockEndpoint.expectedMessageCount(1);
        String reply = requestViaExternalCall(msg[1], "mock:test", "older123456", "external call original body");
        mockEndpoint.assertIsSatisfied();

        // verify result
        assertEquals("external call reply body", reply); // 1st reply is as expected

        // check the call is now in DB as OK and with the correct msg1 NEWER timestamp
        assertExtCallStateInDB(extCallId, ExternalCallStateEnum.OK, msg[1]);

        // send 1st message - reverse order
        reply = requestViaExternalCall(msg[0], "mock:test", "older123456", "external call original body 2");
        mockEndpoint.assertIsSatisfied();

        // verify the 2nd call was skipped, since the 2nd message is older
        assertEquals("external call original body 2", reply);

        // check the call is now in DB as OK, still with msg1 NEWER timestamp
        assertExtCallStateInDB(extCallId, ExternalCallStateEnum.OK, msg[1]);
    }

    @Test
    public void testExternalCallOlderFail() throws Exception {
        Message[] msg = messages(2);
        assumeTrue("For this test the 2nd message must be newer",
                msg[0].getMsgTimestamp().compareTo(msg[1].getMsgTimestamp()) <= 0);

        mockEndpoint.whenExchangeReceived(1, recordCallIdAndThrow(
                new IOException("Simulated External Call Failure"), "mock:test", "olderFail123456"));
        mockEndpoint.whenExchangeReceived(2,
                recordCallIdAndAnswer("external call reply body", "mock:test", "olderFail123456"));

        // send 2nd message - reverse order
        mockEndpoint.expectedMessageCount(1);
        try {
            requestViaExternalCall(msg[1], "mock:test", "olderFail123456", "external call original body");
            fail("Should've failed due to an exception by now");
        } catch (Exception exc) {
            // verify failure
            assertThat(exc, is(instanceOf(IOException.class)));
        }
        mockEndpoint.assertIsSatisfied();

        // check the call is now in DB as FAILED and with the correct msg1 NEWER timestamp
        assertExtCallStateInDB(extCallId, ExternalCallStateEnum.FAILED, msg[1]);

        // send 1st message - reverse order
        String reply = requestViaExternalCall(msg[0], "mock:test", "olderFail123456",
                "external call original body 2");
        mockEndpoint.assertIsSatisfied();

        // verify the 2nd call was NOT made, since the 1st failed call is newer
        assertEquals("external call original body 2", reply);

        // check the call remains unchanged
        assertExtCallStateInDB(extCallId, ExternalCallStateEnum.FAILED, msg[1]);
    }

    @Test
    public void testExternalCallOlderFailBetween() throws Exception {
        Message[] msg = messages(3);
        assumeTrue("For this test the 2nd message must be newer than 1st",
                msg[0].getMsgTimestamp().compareTo(msg[1].getMsgTimestamp()) <= 0);
        assumeTrue("For this test the 3nd message must be newest - newer than 1st and 2nd",
                msg[1].getMsgTimestamp().compareTo(msg[2].getMsgTimestamp()) <= 0);

        mockEndpoint.whenExchangeReceived(1, recordCallIdAndThrow(
                new IOException("Simulated External Call Failure"), "mock:test", "olderFailBetween123456"));
        mockEndpoint.whenExchangeReceived(2,
                recordCallIdAndAnswer("external call reply body", "mock:test", "olderFailBetween123456"));

        // send 2nd message to fail, but trigger external call update
        mockEndpoint.expectedMessageCount(1);
        try {
            requestViaExternalCall(msg[1], "mock:test", "olderFailBetween123456", "external call original body");
            fail("Should've failed due to an exception by now");
        } catch (Exception exc) {
            // verify failure
            assertThat(exc, is(instanceOf(IOException.class)));
        }
        mockEndpoint.assertIsSatisfied();
        // check the call is now in DB as FAILED and with the correct msg1 NEWER timestamp
        assertExtCallStateInDB(extCallId, ExternalCallStateEnum.FAILED, msg[1]);

        // send 1st (oldest) message - should be ignored
        mockEndpoint.expectedMessageCount(1);
        String reply = requestViaExternalCall(msg[0], "mock:test", "olderFailBetween123456",
                "external call original body 2");
        mockEndpoint.assertIsSatisfied();

        // verify the 2nd call was NOT made, since the 1st failed call is newer
        assertEquals("external call original body 2", reply);

        // check the call remains unchanged
        assertExtCallStateInDB(extCallId, ExternalCallStateEnum.FAILED, msg[1]);

        // send 3rd (latest) message - should be allowed
        mockEndpoint.expectedMessageCount(2);
        reply = requestViaExternalCall(msg[2], "mock:test", "olderFailBetween123456",
                "external call original body 3");
        mockEndpoint.assertIsSatisfied();

        // verify the 3rd call was made, since it's the newest call
        assertEquals("external call reply body", reply);

        // check the call remains unchanged
        assertExtCallStateInDB(extCallId, ExternalCallStateEnum.OK, msg[2]);
    }

    @Test
    public void testExternalCallSkipUriPattern() throws Exception {
        // for this test the following must be set in application0.cfg:
        // asynch.externalCall.skipUriPattern = mock:(//)?ignoreTestEndpointUri.*
        assumeThat(skipUriPattern, equalTo("mock:(//)?ignoreTestEndpointUri.*"));

        final Message msg = messages(1)[0];

        MockEndpoint mockIgnore = getCamelContext().getEndpoint("mock://ignoreTestEndpointUri_something",
                MockEndpoint.class);
        mockIgnore.expectedMessageCount(0);

        // send message
        String reply = requestViaExternalCall(msg, "mock:ignoreTestEndpointUri_something", "skip123456",
                "external call original body");

        mockIgnore.assertIsSatisfied();

        // verify result
        assertEquals("external call original body", reply);
    }

    @Test(timeout = 60000)
    public void testExternalCallLockFailure() throws Exception {
        final int messageCount = 21; // how many messages total should be sent
        final int batchSize = 3; // how many messages should be sent together with verifications after each batch
        final int responseDelay = 10; // delay before mock responds, in millis
        boolean lockFailureEncountered = false; // test is pointless if it was never encountered

        Message[] messages = messages(messageCount);

        // set up mock to reply and to verify that the external call is reused
        mockEndpoint.whenAnyExchangeReceived(
                recordCallIdAndAnswer("external call reply body", responseDelay, "mock:test", "concurrentKey"));

        for (int batchStart = 0; batchStart < messages.length; batchStart += batchSize) {
            Message[] batch = Arrays.copyOfRange(messages, batchStart,
                    Math.min(messages.length, batchStart + batchSize));
            lockFailureEncountered |= sendAndVerifyBatch(batch);
        }

        assumeTrue("This test is pointless if lock failure is never encountered", lockFailureEncountered);
    }

    private boolean sendAndVerifyBatch(Message[] messages) throws Exception {
        boolean lockFailureEncountered = false;
        HashMap<Message, Future<String>> replies = new HashMap<Message, Future<String>>();
        // send messages that have no reply, resend messages that have LockFailureException instead of a reply
        // verify results and re-send failures - test has timeout set because this is potentially endless
        Queue<Message> unverifiedMessages = new LinkedList<Message>(Arrays.asList(messages));
        while (!unverifiedMessages.isEmpty()) {
            Message message = unverifiedMessages.poll();
            boolean replyAvailable = replies.containsKey(message);
            if (replyAvailable) {
                Future<String> reply = replies.get(message);
                try {
                    reply.get(); // this will throw an exception if it occurred during processing
                } catch (Exception exc) {
                    if (ExceptionUtils.indexOfType(exc, LockFailureException.class) != -1) {
                        // expected cause - this test verifies that this scenario happens and is handled properly
                        lockFailureEncountered = true;
                        replyAvailable = false; // mark reply unavailable to resend the original message
                    } else {
                        // fail by rethrowing
                        Log.error("Unexpected failure for message {} --", message, exc);
                        throw exc;
                    }
                }
            }
            if (!replyAvailable) {
                unverifiedMessages.add(message); // mark message as still unverified
                replies.put(message, requestViaExternalCallAsync(message, "mock:test", "concurrentKey",
                        "external call original body"));
            }
        }
        // check the call is now in DB as OK and with the correct LAST msg timestamp
        assertExtCallStateInDB(extCallId, ExternalCallStateEnum.OK, messages[messages.length - 1]);
        return lockFailureEncountered;
    }

    private Future<String> requestViaExternalCallAsync(final Message msg, final String targetURI, final Object key,
            final String body) throws Exception {
        return getStringBodyFuture(
                producer.asyncSend("extcall:custom:" + targetURI, prepareExternalCallProcessor(msg, key, body)));

    }

    private String requestViaExternalCall(final Message msg, final String targetURI, final Object key,
            final String body) throws Exception {
        return requestViaExternalCall(msg, ExternalCallKeyType.CUSTOM, targetURI, key, body);
    }

    private String requestViaExternalCall(final Message msg,
            @Nullable final ExternalCallKeyType externalCallKeyType, final String targetURI, final Object key,
            final String body) throws Exception {
        ExternalCallKeyType keyType = (externalCallKeyType == null) ? ExternalCallKeyType.CUSTOM
                : externalCallKeyType;
        Exchange reply = producer.request("extcall:" + keyType.toString() + ":" + targetURI,
                prepareExternalCallProcessor(msg, key, body));
        Exception exc = reply.getProperty(Exchange.EXCEPTION_CAUGHT, Exception.class);
        if (exc != null) {
            throw exc;
        }
        exc = reply.getException();
        if (exc != null) {
            throw exc;
        }
        return reply.hasOut() ? reply.getOut().getBody(String.class) : reply.getIn().getBody(String.class);
    }

    private Processor prepareExternalCallProcessor(final Message msg, final Object key, final String body) {
        return new Processor() {
            @Override
            public void process(Exchange exchange) throws Exception {
                exchange.getIn().setHeaders(headers(msg));
                exchange.getIn().setBody(body);
                exchange.setProperty(ExtCallComponentParams.EXTERNAL_CALL_KEY, key);
            }
        };
    }

    /**
     * Returns a processor that answers with the specified answer body.
     * The received exchange is verified to have {@link Message} in {@link AsynchConstants#MSG_HEADER}.
     * <p/>
     * It also records the external call ID into the instance field {@link #extCallId}
     * or verifies the new one matches the old one, if one is already recorded.
     *
     * @param answerBody    the body to set for the exchange IN message
     * @param operationName
     * @param entityId
     * @return processor, e.g. for use with mocks
     */
    private Processor recordCallIdAndAnswer(final Object answerBody, String operationName, String entityId) {
        return recordCallIdAndAnswer(answerBody, 0, operationName, entityId);
    }

    /**
     * Same as {@link #recordCallIdAndAnswer(Object, String, String)}, but throws an exception instead.
     *
     * @param answerException the exception to throw
     * @param operationName
     * @param entityId
     * @return processor, e.g. for use with mocks
     */
    private Processor recordCallIdAndThrow(final Exception answerException, final String operationName,
            final String entityId) {
        return new Processor() {
            @Override
            public void process(Exchange exchange) throws Exception {
                verifyAndRecordCallId(exchange, operationName, entityId);
                throw answerException;
            }
        };
    }

    /**
     * Same as {@link #recordCallIdAndAnswer(Object, String, String)}, but waits for the specified delay before answering.
     *
     * @param answerBody    the body to set for the exchange IN message
     * @param responseDelay delay in milliseconds to wait before answering
     * @param operationName
     * @param entityId
     * @return processor, e.g. for use with mocks
     */
    private Processor recordCallIdAndAnswer(final Object answerBody, final int responseDelay,
            final String operationName, final String entityId) {
        return new Processor() {
            @Override
            public void process(Exchange exchange) throws Exception {
                verifyAndRecordCallId(exchange, operationName, entityId);
                //simulate delay, then respond
                Thread.sleep(responseDelay);
                exchange.getIn().setBody(answerBody);
            }
        };
    }

    private void verifyAndRecordCallId(Exchange exchange, String operationName, String entityId) {
        Message msg = exchange.getIn().getHeader(AsynchConstants.MSG_HEADER, Message.class);
        ExternalCall extCall = externalCallDao.getExternalCall(operationName, entityId);
        Log.info("Processing ExternalCall={} for Message={}", extCall, msg);

        assertNotNull(extCall);
        assertEquals(extCall.getState(), ExternalCallStateEnum.PROCESSING);
        // check it's also in the DB in the correct state
        assertExtCallStateInDB(extCall.getId(), ExternalCallStateEnum.PROCESSING, msg);

        Long newExtCallId = extCall.getId();
        if (extCallId == null) {
            extCallId = newExtCallId;
        } else {
            assertEquals(extCallId, newExtCallId);
        }
    }

    /** Verifies there's an ExternalCall instance in DB with the specified ID, state and msgId + msgTimestamp. */
    private void assertExtCallStateInDB(Long callId, ExternalCallStateEnum state, Message message) {
        ExternalCall extCall = em.find(ExternalCall.class, callId);
        assertNotNull(String.format("ExternalCall with ID [%s] doesn't exist in the DB", callId), extCall);
        assertEquals(String.format("ExternalCall [%s]%ndoesn't have the expected state [%s]", extCall, state),
                state, extCall.getState());
        assertEquals(
                String.format("ExternalCall [%s]%ndoesn't reference the expected message [%s]", extCall, message),
                message.getMsgId(), extCall.getMsgId());
        assertEquals(
                String.format(
                        "ExternalCall msgTimestamp [%s] doesn't match expected msgTimestamp [%s] of message [%s]",
                        extCall.getMsgTimestamp(), message.getMsgTimestamp(), message),
                message.getMsgTimestamp().getTime(), extCall.getMsgTimestamp().getTime());
    }

    private Message[] messages(final int messageCount) throws Exception {
        return createAndSaveMessages(messageCount, ExternalSystemTestEnum.CRM, ServiceTestEnum.CUSTOMER,
                "setCustomer", REQUEST_XML);
    }

    private Map<String, Object> headers(Message msg) {
        Map<String, Object> headers = new HashMap<String, Object>();
        headers.put(AsynchConstants.MSG_HEADER, msg);
        headers.put(AsynchConstants.ASYNCH_MSG_HEADER, true);
        return headers;
    }

    private Future<String> getStringBodyFuture(final Future<Exchange> reply) {
        return new Future<String>() {
            @Override
            public boolean cancel(boolean mayInterruptIfRunning) {
                return reply.cancel(mayInterruptIfRunning);
            }

            @Override
            public boolean isCancelled() {
                return reply.isCancelled();
            }

            @Override
            public boolean isDone() {
                return reply.isDone();
            }

            @Override
            public String get() throws InterruptedException, ExecutionException {
                return getReplyString(reply.get());
            }

            @Override
            public String get(long timeout, TimeUnit unit)
                    throws InterruptedException, ExecutionException, TimeoutException {
                return getReplyString(reply.get(timeout, unit));
            }

            private String getReplyString(Exchange exchange) throws InterruptedException, ExecutionException {
                throwExceptionOptionally(exchange.getProperty(Exchange.EXCEPTION_CAUGHT, Exception.class));
                throwExceptionOptionally(exchange.getException());
                return exchange.getOut().getBody(String.class);
            }

            private void throwExceptionOptionally(Exception exc) throws InterruptedException, ExecutionException {
                if (exc != null) {
                    if (exc instanceof InterruptedException) {
                        throw (InterruptedException) exc;
                    } else if (exc instanceof ExecutionException) {
                        throw (ExecutionException) exc;
                    } else {
                        throw new ExecutionException(exc);
                    }
                }
            }
        };
    }

    private class CustomEntityId {

        private String entityId;

        public CustomEntityId(String entityId) {
            this.entityId = entityId;
        }

        @Override
        public String toString() {
            return "[CustomEntityId]:" + entityId;
        }
    }
}